generated from mirzaev/pot
This commit is contained in:
parent
8627411c8e
commit
28d550d53c
|
@ -6,7 +6,10 @@ namespace mirzaev\notchat\controllers;
|
||||||
|
|
||||||
// Files of the project
|
// Files of the project
|
||||||
use mirzaev\notchat\views\templater,
|
use mirzaev\notchat\views\templater,
|
||||||
mirzaev\notchat\models\core as models;
|
mirzaev\notchat\models\core as models,
|
||||||
|
mirzaev\notchat\models\log,
|
||||||
|
mirzaev\notchat\models\enumerations\log as type,
|
||||||
|
mirzaev\notchat\models\firewall;
|
||||||
|
|
||||||
// Framework for PHP
|
// Framework for PHP
|
||||||
use mirzaev\minimal\controller;
|
use mirzaev\minimal\controller;
|
||||||
|
@ -30,7 +33,7 @@ class core extends controller
|
||||||
protected array $errors = [];
|
protected array $errors = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor of an instance
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param bool $initialize Initialize a controller?
|
* @param bool $initialize Initialize a controller?
|
||||||
*
|
*
|
||||||
|
@ -44,12 +47,49 @@ class core extends controller
|
||||||
if ($initialize) {
|
if ($initialize) {
|
||||||
// Initializing is requested
|
// Initializing is requested
|
||||||
|
|
||||||
// Initializing of models core
|
// Write to the log of connections
|
||||||
new models();
|
log::write(
|
||||||
|
type::CONNECTIONS,
|
||||||
|
trim("[{$_SERVER['REMOTE_ADDR']}] "
|
||||||
|
. (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ")
|
||||||
|
. (empty($_SERVER['HTTP_REFERER']) ? '' : "[{$_SERVER['HTTP_REFERER']}] ")
|
||||||
|
. (empty($_SERVER['HTTP_USER_AGENT']) ? '' : "[{$_SERVER['HTTP_USER_AGENT']}]"), ' ')
|
||||||
|
);
|
||||||
|
|
||||||
// Initializing of preprocessor of views
|
// Initializing of preprocessor of views
|
||||||
$this->view = new templater();
|
$this->view = new templater();
|
||||||
|
|
||||||
|
// Checking for ban
|
||||||
|
if (
|
||||||
|
(isset($_SERVER['HTTP_X_FORWARDED_FOR']) && firewall::banned($_SERVER['HTTP_X_FORWARDED_FOR']))
|
||||||
|
|| (isset($_SERVER['REMOTE_ADDR']) && firewall::banned($_SERVER['REMOTE_ADDR']))
|
||||||
|
) {
|
||||||
|
// IP-address is banned
|
||||||
|
|
||||||
|
// Sending a reply
|
||||||
|
echo $this->view->render('pages/ban.html');
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
die;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initializing of models core
|
||||||
|
new models();
|
||||||
|
|
||||||
|
// Initializing a response headers
|
||||||
|
header('Service-Worker-Allowed: /');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
// Analyze recent requests
|
||||||
|
firewall::analyze();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,5 +108,4 @@ class core extends controller
|
||||||
default => isset($this->{$name})
|
default => isset($this->{$name})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,9 @@ namespace mirzaev\notchat\controllers;
|
||||||
|
|
||||||
// Files of the project
|
// Files of the project
|
||||||
use mirzaev\notchat\controllers\core,
|
use mirzaev\notchat\controllers\core,
|
||||||
mirzaev\notchat\models\server;
|
mirzaev\notchat\models\dns,
|
||||||
|
mirzaev\notchat\models\server,
|
||||||
|
mirzaev\notchat\models\text;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Index controller
|
* Index controller
|
||||||
|
@ -23,6 +25,62 @@ final class index extends core
|
||||||
*/
|
*/
|
||||||
public function index(array $parameters = []): ?string
|
public function index(array $parameters = []): ?string
|
||||||
{
|
{
|
||||||
|
// Initializing a list with languages
|
||||||
|
$this->view->languages = text::list($this->errors);
|
||||||
|
|
||||||
|
// Инициализация бегущей строки
|
||||||
|
$this->view->hotline = [
|
||||||
|
'id' => 'hotline'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация параметров бегущей строки
|
||||||
|
$this->view->hotline = [
|
||||||
|
'parameters' => [
|
||||||
|
'step' => '0.3'
|
||||||
|
]
|
||||||
|
] + $this->view->hotline;
|
||||||
|
|
||||||
|
// Инициализация аттрибутов бегущей строки
|
||||||
|
$this->view->hotline = [
|
||||||
|
'attributes' => []
|
||||||
|
] + $this->view->hotline;
|
||||||
|
|
||||||
|
// Инициализация элементов бегущей строки
|
||||||
|
$this->view->hotline = [
|
||||||
|
'elements' => [
|
||||||
|
['html' => ''],
|
||||||
|
[
|
||||||
|
'tag' => 'article',
|
||||||
|
'attributes' => [
|
||||||
|
'class' => 'trash'
|
||||||
|
],
|
||||||
|
'html' => $this->view->render(DIRECTORY_SEPARATOR . 'hotline' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'trash.html', [
|
||||||
|
'id' => 'trash_1',
|
||||||
|
'title' => 'Linoleum',
|
||||||
|
'main' => '<p>Do you really like the rotting smell, dull sound and disgusting greasy shine of parquet-like fake pattern on a polymer toxic film? <b>Are you fucking insane?</b></p>',
|
||||||
|
'image' => [
|
||||||
|
'src' => 'https://virus.mirzaev.sexy/images/trash/linoleum.png',
|
||||||
|
'alt' => 'Linoleum'
|
||||||
|
]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
['html' => ''],
|
||||||
|
['html' => ''],
|
||||||
|
['html' => ''],
|
||||||
|
['html' => ''],
|
||||||
|
['html' => ''],
|
||||||
|
['html' => ''],
|
||||||
|
['html' => ''],
|
||||||
|
['html' => ''],
|
||||||
|
['html' => ''],
|
||||||
|
['html' => ''],
|
||||||
|
['html' => ''],
|
||||||
|
['html' => ''],
|
||||||
|
['html' => '']
|
||||||
|
]
|
||||||
|
] + $this->view->hotline;
|
||||||
|
|
||||||
|
|
||||||
// Exit (success)
|
// Exit (success)
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render('chats.html');
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render('chats.html');
|
||||||
else if ($_SERVER['REQUEST_METHOD'] === 'POST') return $this->view->render('chats.html');
|
else if ($_SERVER['REQUEST_METHOD'] === 'POST') return $this->view->render('chats.html');
|
||||||
|
@ -31,6 +89,106 @@ final class index extends core
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the servers section
|
||||||
|
*
|
||||||
|
* @param array $parameters Parameters of the request (POST + GET)
|
||||||
|
*
|
||||||
|
* @return void Generated JSON to the output buffer
|
||||||
|
*/
|
||||||
|
/* public function cache(array $parameters = []): void
|
||||||
|
{
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
// GET
|
||||||
|
|
||||||
|
if (file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'manifest')) {
|
||||||
|
// File found
|
||||||
|
|
||||||
|
// Clearing the output buffer
|
||||||
|
if (ob_get_level()) ob_end_clean();
|
||||||
|
|
||||||
|
// Initializing of the output buffer
|
||||||
|
ob_start();
|
||||||
|
|
||||||
|
// Initializing a response headers
|
||||||
|
header('Content-Type: text/cache-manifest');
|
||||||
|
|
||||||
|
// Generating the reponse
|
||||||
|
if ($file = fopen($path, 'r')) {
|
||||||
|
// File open
|
||||||
|
|
||||||
|
// Reading file
|
||||||
|
while (!feof($file)) echo fread($path, 1024);
|
||||||
|
|
||||||
|
// Closing file
|
||||||
|
fclose($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializing a response headers
|
||||||
|
header('Content-Length: ' . ob_get_length());
|
||||||
|
|
||||||
|
// Sending and deinitializing of the output buffer
|
||||||
|
ob_end_flush();
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return void Generated JSON to the output buffer
|
||||||
|
*/
|
||||||
|
/* public function cache(array $parameters = []): void
|
||||||
|
{
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
// GET
|
||||||
|
|
||||||
|
if (file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . 'cache.js')) {
|
||||||
|
// File found
|
||||||
|
|
||||||
|
// Clearing the output buffer
|
||||||
|
if (ob_get_level()) ob_end_clean();
|
||||||
|
|
||||||
|
// Initializing of the output buffer
|
||||||
|
ob_start();
|
||||||
|
|
||||||
|
// Initializing a response headers
|
||||||
|
header('Content-Type: application/javascript charset=utf-8');
|
||||||
|
|
||||||
|
// Generating the reponse
|
||||||
|
if ($file = fopen($path, 'r')) {
|
||||||
|
// File open
|
||||||
|
|
||||||
|
// Reading file
|
||||||
|
while (!feof($file)) echo fread($file, 1024);
|
||||||
|
|
||||||
|
// Closing file
|
||||||
|
fclose($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializing a response headers
|
||||||
|
header('Content-Length: ' . ob_get_length());
|
||||||
|
|
||||||
|
// Sending and deinitializing of the output buffer
|
||||||
|
ob_end_flush();
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the offline page
|
||||||
|
*/
|
||||||
|
public function offline(): ?string
|
||||||
|
{
|
||||||
|
// Initializing of the title
|
||||||
|
$this->view->title = 'bye';
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return $this->view->render('pages/offline.html');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the servers section
|
* Render the servers section
|
||||||
*
|
*
|
||||||
|
@ -54,7 +212,17 @@ final class index extends core
|
||||||
// Generating the reponse
|
// Generating the reponse
|
||||||
echo json_encode(
|
echo json_encode(
|
||||||
[
|
[
|
||||||
'html' => $this->view->render('sections/servers.html', ['current' => isset($parameters['server']) ? server::read($parameters['server'], errors: $this->errors) : null, 'servers' => server::all(100, errors: $this->errors) ?? []]),
|
'html' => $this->view->render(
|
||||||
|
'sections/servers.html',
|
||||||
|
[
|
||||||
|
'current' => isset($parameters['server'])
|
||||||
|
&& ($server = server::read(domain: dns::domain($parameters['server'], errors: $this->errors), errors: $this->errors))
|
||||||
|
? json_decode($server, true, 8)
|
||||||
|
: null,
|
||||||
|
'servers' => server::all(100, errors: $this->errors) ?? []
|
||||||
|
]
|
||||||
|
),
|
||||||
|
'status' => isset($server) ? 'connected' : 'disconnected',
|
||||||
'errors' => null
|
'errors' => null
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,13 @@ namespace mirzaev\notchat\controllers;
|
||||||
// Files of the project
|
// Files of the project
|
||||||
use mirzaev\notchat\controllers\core,
|
use mirzaev\notchat\controllers\core,
|
||||||
mirzaev\notchat\controllers\traits\errors,
|
mirzaev\notchat\controllers\traits\errors,
|
||||||
mirzaev\notchat\models\server as model;
|
mirzaev\notchat\models\dns,
|
||||||
|
mirzaev\notchat\models\server as model,
|
||||||
|
mirzaev\notchat\models\log,
|
||||||
|
mirzaev\notchat\models\enumerations\log as type;
|
||||||
|
|
||||||
|
// Built-in libraries
|
||||||
|
use exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server controller
|
* Server controller
|
||||||
|
@ -34,7 +40,7 @@ final class server extends core
|
||||||
// POST
|
// POST
|
||||||
|
|
||||||
// Create a file with server data
|
// Create a file with server data
|
||||||
model::write($parameters['domain'], file_get_contents('php://input'), $this->errors);
|
model::write(dns::domain($parameters['server'], errors: $this->errors), file_get_contents('php://input'), $this->errors);
|
||||||
|
|
||||||
// Initializing a response headers
|
// Initializing a response headers
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
@ -74,11 +80,39 @@ final class server extends core
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
// POST
|
// POST
|
||||||
|
|
||||||
// Read a file with server data
|
// Initializing of buffer of response
|
||||||
$server = json_decode(model::read(model::domain($parameters['server']), $this->errors), true, 8);
|
$return = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Decode of user input
|
||||||
|
$parameters['server'] = urldecode($parameters['server']);
|
||||||
|
|
||||||
|
// Validation of user input
|
||||||
|
if (mb_strlen($parameters['server']) > 512) throw new exception('Server address longer than 512 characters');
|
||||||
|
|
||||||
|
if ($domain = dns::domain($parameters['server'], errors: $this->errors)) {
|
||||||
|
if ($raw = model::read(domain: $domain)) {
|
||||||
|
// File found and read
|
||||||
|
|
||||||
|
// Decoding server data to remove protected parameters
|
||||||
|
$return['server'] = json_decode($raw, true, 8);
|
||||||
|
|
||||||
// Remove protected parameters
|
// Remove protected parameters
|
||||||
unset($server['key']);
|
unset($return['server']['key']);
|
||||||
|
} else throw new exception('Server offline');
|
||||||
|
} else throw new exception('Server not found');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Write to the buffer of errors
|
||||||
|
$this->errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// Initializing a response headers
|
// Initializing a response headers
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
@ -90,8 +124,7 @@ final class server extends core
|
||||||
|
|
||||||
// Generating the reponse
|
// Generating the reponse
|
||||||
echo json_encode(
|
echo json_encode(
|
||||||
[
|
$return + [
|
||||||
'server' => $server,
|
|
||||||
'errors' => static::text($this->errors)
|
'errors' => static::text($this->errors)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,6 +4,9 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace mirzaev\notchat\models;
|
namespace mirzaev\notchat\models;
|
||||||
|
|
||||||
|
// Files of the project
|
||||||
|
use mirzaev\notchat\models\enumerations\log as type;
|
||||||
|
|
||||||
// Framework for PHP
|
// Framework for PHP
|
||||||
use mirzaev\minimal\model;
|
use mirzaev\minimal\model;
|
||||||
|
|
||||||
|
@ -24,7 +27,12 @@ class core extends model
|
||||||
final public const POSTFIX = '';
|
final public const POSTFIX = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path to storage
|
* Path to public directory
|
||||||
|
*/
|
||||||
|
final public const PUBLIC = '..' . DIRECTORY_SEPARATOR . 'public';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path to storage directory
|
||||||
*/
|
*/
|
||||||
final public const STORAGE = '..' . DIRECTORY_SEPARATOR . 'storage';
|
final public const STORAGE = '..' . DIRECTORY_SEPARATOR . 'storage';
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,14 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace mirzaev\notchat\models;
|
namespace mirzaev\notchat\models;
|
||||||
|
|
||||||
// Framework for PHP
|
// Files of the project
|
||||||
use mirzaev\minimal\model;
|
use mirzaev\notchat\models\enumerations\log as type;
|
||||||
|
|
||||||
// Built-in libraries
|
// Built-in libraries
|
||||||
use exception,
|
use exception;
|
||||||
DirectoryIterator as parser;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core of DNS registry
|
* DNS registry
|
||||||
*
|
*
|
||||||
* @package mirzaev\notchat\models
|
* @package mirzaev\notchat\models
|
||||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
@ -41,9 +40,9 @@ class dns extends core
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Open file with DNS records
|
// Open file with DNS records
|
||||||
$dns = fopen(static::DNS, 'r');
|
$dns = fopen(static::DNS, 'c+');
|
||||||
|
|
||||||
while (($row = fgets($dns)) !== false) {
|
while (($row = fgets($dns, 512)) !== false) {
|
||||||
// Iterate over rows
|
// Iterate over rows
|
||||||
|
|
||||||
// Initializing values of the server data
|
// Initializing values of the server data
|
||||||
|
@ -52,27 +51,30 @@ class dns extends core
|
||||||
// Incrementing the line read counter
|
// Incrementing the line read counter
|
||||||
++$line;
|
++$line;
|
||||||
|
|
||||||
if ($domain === $_domain || $ip === $_ip || $port === $_port) {
|
if ($domain === $_domain || ($port && $ip === $_ip && $port === $_port) || (!$port && $ip === $_ip || $port === $_port)) {
|
||||||
// Server found
|
// Server found (domain, ip, ip + port)
|
||||||
|
|
||||||
// Close file with DNS
|
// Close file with DNS
|
||||||
fclose($dns);
|
fclose($dns);
|
||||||
|
|
||||||
// Exit (success)
|
// Exit (success)
|
||||||
return $record;
|
return array_combine(['domain', 'ip', 'port'], $record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close file with DNS
|
// Close file with DNS
|
||||||
fclose($dns);
|
fclose($dns);
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Write to buffer of errors
|
// Write to the buffer of errors
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
'line' => $e->getLine(),
|
'line' => $e->getLine(),
|
||||||
'stack' => $e->getTrace()
|
'stack' => $e->getTrace()
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit (fail)
|
// Exit (fail)
|
||||||
|
@ -100,23 +102,23 @@ class dns extends core
|
||||||
// Initializing part the file buffer (rows before target)
|
// Initializing part the file buffer (rows before target)
|
||||||
$after = [];
|
$after = [];
|
||||||
|
|
||||||
if (file_exists(static::DNS) && filesize(static::DNS) > 0) {
|
|
||||||
// File exists and not empty
|
|
||||||
|
|
||||||
// Initializing the status that the DNS record has been found
|
// Initializing the status that the DNS record has been found
|
||||||
$found = false;
|
$found = false;
|
||||||
|
|
||||||
// Open file with DNS records
|
if (file_exists(static::DNS) && filesize(static::DNS) > 0) {
|
||||||
$dns = fopen(static::DNS, 'r');
|
// File exists and not empty
|
||||||
|
|
||||||
while (($row = fgets($dns)) !== false) {
|
// Open file with DNS records
|
||||||
|
$dns = fopen(static::DNS, 'c+');
|
||||||
|
|
||||||
|
while (($row = fgets($dns, 512)) !== false) {
|
||||||
// Iterate over rows
|
// Iterate over rows
|
||||||
|
|
||||||
// Initializing values of the server data
|
// Initializing values of the server data
|
||||||
[$_domain] = explode(' ', $row);
|
[$_domain] = explode(' ', $row);
|
||||||
|
|
||||||
// Writing the row to the file buffer (except the target record)
|
// Writing the row to the file buffer (except the target record)
|
||||||
if ($domain === $_domain) $found = true;
|
if ($domain === $_domain) $found = $row;
|
||||||
else ${$found ? 'after' : 'before'}[] = $row;
|
else ${$found ? 'after' : 'before'}[] = $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +136,7 @@ class dns extends core
|
||||||
ftruncate($dns, 0);
|
ftruncate($dns, 0);
|
||||||
|
|
||||||
// Write a new record to the DNS registry
|
// Write a new record to the DNS registry
|
||||||
fwrite($dns, trim(implode("", $before)) . "\n$domain $ip $port\n" . trim(implode("", $after)));
|
fwrite($dns, (count($before) > 0 ? trim(implode("", $before)) . "\n" : '') . "$domain $ip $port" . (count($after) ? "\n" . trim(implode("", $after)) : ''));
|
||||||
|
|
||||||
// Apply changes
|
// Apply changes
|
||||||
fflush($dns);
|
fflush($dns);
|
||||||
|
@ -142,14 +144,20 @@ class dns extends core
|
||||||
// Unlock file
|
// Unlock file
|
||||||
flock($dns, LOCK_UN);
|
flock($dns, LOCK_UN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write to the log
|
||||||
|
log::write(type::DNS, $found ? "[UPDATE] $found -> $domain $ip $port" : "[CREATE] $domain $ip $port");
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Write to buffer of errors
|
// Write to the buffer of errors
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
'line' => $e->getLine(),
|
'line' => $e->getLine(),
|
||||||
'stack' => $e->getTrace()
|
'stack' => $e->getTrace()
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,32 +167,39 @@ class dns extends core
|
||||||
* Convert domain or IP-address to domain
|
* Convert domain or IP-address to domain
|
||||||
*
|
*
|
||||||
* @param string $server Domain or IP-address of the server
|
* @param string $server Domain or IP-address of the server
|
||||||
|
* @param bool $strict Check for port compliance?
|
||||||
* @param array &$errors Buffer of errors
|
* @param array &$errors Buffer of errors
|
||||||
*
|
*
|
||||||
* @return string|null Domain of the server
|
* @return string|null Domain of the server
|
||||||
*/
|
*/
|
||||||
public static function domain(string $server, &$errors = []): ?string
|
public static function domain(string $server, bool $strict = true, &$errors = []): ?string
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (preg_match('/^(https:\/\/)?\d+\..*\d\/?$/', $server) === 1) {
|
if (preg_match('/^(?:https:\/\/)?([\d\.]*)(?:$|:?(\d.*\d)?\/?$)/', $server, $matches) === 1) {
|
||||||
// IP-address
|
// IP-address
|
||||||
|
|
||||||
|
// Initializing of parts of address
|
||||||
|
@[, $ip, $port] = $matches;
|
||||||
|
|
||||||
// Exit (success)
|
// Exit (success)
|
||||||
return static::read(ip: $server, errors: $errors)['domain'];
|
return static::read(ip: $ip, port: $strict ? $port : null, errors: $errors)['domain'] ?? null;
|
||||||
} else {
|
} else {
|
||||||
// Domain (implied)
|
// Domain (implied)
|
||||||
|
|
||||||
// Exit (success)
|
// Exit (success)
|
||||||
return $server;
|
return $server ?? null;
|
||||||
}
|
}
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Write to buffer of errors
|
// Write to the buffer of errors
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
'line' => $e->getLine(),
|
'line' => $e->getLine(),
|
||||||
'stack' => $e->getTrace()
|
'stack' => $e->getTrace()
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit (fail)
|
// Exit (fail)
|
||||||
|
@ -204,25 +219,31 @@ class dns extends core
|
||||||
public static function ip(string $server, &$errors = []): ?string
|
public static function ip(string $server, &$errors = []): ?string
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (preg_match('/^(https:\/\/)?\d+\..*\d\/?$/', $server) === 1) {
|
if (preg_match('/^(?:https:\/\/)?(\d+\..*):?(\d.*\d)?\/?$/', $server, $matches) === 1) {
|
||||||
// IP-address
|
// IP-address
|
||||||
|
|
||||||
|
// Initializing of parts of address
|
||||||
|
[, $ip, $port] = $matches;
|
||||||
|
|
||||||
// Exit (success)
|
// Exit (success)
|
||||||
return $server;
|
return $ip ?? null;
|
||||||
} else {
|
} else {
|
||||||
// Domain (implied)
|
// Domain (implied)
|
||||||
|
|
||||||
// Exit (success)
|
// Exit (success)
|
||||||
return static::read(domain: $server, errors: $errors)['ip'];
|
return static::read(domain: $server, errors: $errors)['ip'] ?? null;
|
||||||
}
|
}
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Write to buffer of errors
|
// Write to the buffer of errors
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
'line' => $e->getLine(),
|
'line' => $e->getLine(),
|
||||||
'stack' => $e->getTrace()
|
'stack' => $e->getTrace()
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit (fail)
|
// Exit (fail)
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\notchat\models\enumerations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types of logs
|
||||||
|
*
|
||||||
|
* @package mirzaev\notchat\models\enumerations
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
enum log: string
|
||||||
|
{
|
||||||
|
case ERRORS = 'errors.log';
|
||||||
|
case CONNECTIONS = 'connections.log';
|
||||||
|
case BANS = 'bans.log';
|
||||||
|
case FIREWALL = 'firewall.log';
|
||||||
|
case SESSIONS = 'sessions.log';
|
||||||
|
case SERVERS = 'servers.log';
|
||||||
|
case DNS = 'dns.log';
|
||||||
|
}
|
|
@ -0,0 +1,211 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\notchat\models;
|
||||||
|
|
||||||
|
// Files of the project
|
||||||
|
use mirzaev\notchat\models\enumerations\log as type,
|
||||||
|
mirzaev\notchat\models\traits\file,
|
||||||
|
mirzaev\notchat\models\traits\log as read;
|
||||||
|
|
||||||
|
// Built-in libraries
|
||||||
|
use exception,
|
||||||
|
datetime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Firewall
|
||||||
|
*
|
||||||
|
* @package mirzaev\notchat\models
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
class firewall extends core
|
||||||
|
{
|
||||||
|
use file, read {
|
||||||
|
file::read as protected file;
|
||||||
|
read::ban as protected _ban;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write
|
||||||
|
*
|
||||||
|
* @param int $range Time range for reading last connections (seconds)
|
||||||
|
* @param int $limit Limit on the number of requests in the allotted time
|
||||||
|
* @param array &$errors Buffer of errors
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @todo
|
||||||
|
* 1. Dividing logs based on volume achieved
|
||||||
|
*/
|
||||||
|
public static function analyze(int $range = 10, int $limit = 20, &$errors = []): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Initializing of path to the file with connections
|
||||||
|
$path = log::LOGS . DIRECTORY_SEPARATOR . type::CONNECTIONS->value;
|
||||||
|
|
||||||
|
if (file_exists($path)) {
|
||||||
|
// The file exists
|
||||||
|
|
||||||
|
// Initializing of current time
|
||||||
|
$current = new datetime();
|
||||||
|
|
||||||
|
// Initializing of target past time
|
||||||
|
$past = (clone $current)->modify("-$range seconds");
|
||||||
|
|
||||||
|
// Initializing of the buffer of IP-addresses found in the connections log [ip => amount of connections per $time]
|
||||||
|
$ips = [];
|
||||||
|
|
||||||
|
// Open file with connections
|
||||||
|
$connections = fopen($path, 'r');
|
||||||
|
|
||||||
|
foreach (static::file($connections, 300, 0, -1) as $row) {
|
||||||
|
// Reading a file backwards (rows from end)
|
||||||
|
|
||||||
|
// Skipping of empty rows
|
||||||
|
if (empty($row)) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Deserializing a row
|
||||||
|
$parameters = static::connection($row, $errors);
|
||||||
|
|
||||||
|
if ($parameters !== null && is_array($parameters)) {
|
||||||
|
// Parameters have been initialized
|
||||||
|
|
||||||
|
// Initializing of parameters of connection
|
||||||
|
[, $date, $ip, $forwarded, $referer, $useragent] = $parameters;
|
||||||
|
|
||||||
|
// Initializing of date of connection
|
||||||
|
$date = DateTime::createFromFormat('Y.m.d H:i:s', $date);
|
||||||
|
|
||||||
|
if (0 <= $elapsed = $date->getTimestamp() - $past->getTimestamp()) {
|
||||||
|
// No more than $range seconds have passed since connection
|
||||||
|
|
||||||
|
// Initializing of counter of connections
|
||||||
|
$ips[$ip] ??= 0;
|
||||||
|
|
||||||
|
// Incrementing of counter of connections
|
||||||
|
++$ips[$ip];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (exception $e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close file with connections
|
||||||
|
fclose($connections);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ban IP-addresses that do not meet the conditions
|
||||||
|
foreach ($ips ?? [] as $ip => $connections) if ($connections >= $limit) static::ban($ip, new datetime('+2 minutes'));
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Write to buffer of errors
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ban
|
||||||
|
*
|
||||||
|
* @param string $ip IP-address
|
||||||
|
* @param datetime $end Date for unban
|
||||||
|
* @param array &$errors Buffer of errors
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function ban(string $ip, datetime $end, &$errors = []): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Write to the log of bans
|
||||||
|
log::write(type::BANS, "[{$end->format('Y.m.d H:i:s')}] [$ip]");
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Write to buffer of errors
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for ban
|
||||||
|
*
|
||||||
|
* Search in the ban log
|
||||||
|
*
|
||||||
|
* @param string $ip IP-address
|
||||||
|
* @param array &$errors Buffer of errors
|
||||||
|
*
|
||||||
|
* @return bool IP-address is banned? (null - has errors)
|
||||||
|
*
|
||||||
|
* @todo Count rows of file for reading instead of constant value (500 rows)
|
||||||
|
*/
|
||||||
|
public static function banned(string $ip, &$errors = []): ?bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Initializing of path to the file with bans
|
||||||
|
$path = log::LOGS . DIRECTORY_SEPARATOR . type::BANS->value;
|
||||||
|
|
||||||
|
if (file_exists($path)) {
|
||||||
|
// The file exists
|
||||||
|
|
||||||
|
// Open file with bans
|
||||||
|
$bans = fopen($path, 'r');
|
||||||
|
|
||||||
|
foreach (static::file($bans, 500, 0, -1) as $row) {
|
||||||
|
// Reading a file backwards (rows from end)
|
||||||
|
|
||||||
|
// Skipping of empty rows
|
||||||
|
if (empty($row)) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Deserializing a row
|
||||||
|
$parameters = static::_ban($row, $errors);
|
||||||
|
|
||||||
|
if ($parameters !== null && is_array($parameters)) {
|
||||||
|
// Parameters have been initialized
|
||||||
|
|
||||||
|
// Initializing of parameters of connection
|
||||||
|
[, $from, $to, $_ip] = static::_ban($row, $errors);
|
||||||
|
|
||||||
|
// Check for ban and exit (success)
|
||||||
|
if ($ip === $_ip && (new datetime)->getTimestamp() - DateTime::createFromFormat('Y.m.d H:i:s', $to)->getTimestamp() < 0) return true;
|
||||||
|
}
|
||||||
|
} catch (exception $e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return false;
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Write to buffer of errors
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit (fail)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\notchat\models;
|
||||||
|
|
||||||
|
// Files of the project
|
||||||
|
use mirzaev\notchat\models\enumerations\log as type;
|
||||||
|
|
||||||
|
// Built-in libraries
|
||||||
|
use exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log
|
||||||
|
*
|
||||||
|
* @package mirzaev\notchat\models
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
class log extends core
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Path to DNS of storaged servers
|
||||||
|
*/
|
||||||
|
final public const LOGS = core::STORAGE . DIRECTORY_SEPARATOR . 'logs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write
|
||||||
|
*
|
||||||
|
* @param type $type Type of log
|
||||||
|
* @param string $value Text to write
|
||||||
|
* @param array &$errors Buffer of errors
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @todo
|
||||||
|
* 1. Dividing magazines based on volume achieved
|
||||||
|
*/
|
||||||
|
public static function write(type $type, string $value, &$errors = []): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Initializing of path to the file of the log
|
||||||
|
$path = static::LOGS . DIRECTORY_SEPARATOR . $type->value;
|
||||||
|
|
||||||
|
// Open file of the log
|
||||||
|
$log = fopen($path, 'a');
|
||||||
|
|
||||||
|
if (flock($log, LOCK_EX)) {
|
||||||
|
// File locked
|
||||||
|
|
||||||
|
// Initializing of date
|
||||||
|
$date = date_format(date_create(), 'Y.m.d H:i:s');
|
||||||
|
|
||||||
|
// Write to the log
|
||||||
|
fwrite($log, (filesize($path) === 0 ? '' : PHP_EOL) . "[$date] $value");
|
||||||
|
|
||||||
|
// Apply changes
|
||||||
|
fflush($log);
|
||||||
|
|
||||||
|
// Unlock file
|
||||||
|
flock($log, LOCK_UN);
|
||||||
|
}
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Write to buffer of errors
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,15 +4,15 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace mirzaev\notchat\models;
|
namespace mirzaev\notchat\models;
|
||||||
|
|
||||||
// Framework for PHP
|
// Files of the project
|
||||||
use mirzaev\minimal\model;
|
use mirzaev\notchat\models\enumerations\log as type;
|
||||||
|
|
||||||
// Built-in libraries
|
// Built-in libraries
|
||||||
use exception,
|
use exception,
|
||||||
DirectoryIterator as parser;
|
DirectoryIterator as parser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core of models
|
* Server
|
||||||
*
|
*
|
||||||
* @package mirzaev\notchat\models
|
* @package mirzaev\notchat\models
|
||||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
@ -38,6 +38,7 @@ class server extends core
|
||||||
public static function write(string $domain, string $json = '', array &$errors = []): void
|
public static function write(string $domain, string $json = '', array &$errors = []): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
//
|
||||||
if (strlen($domain) > 32) throw new exception('Domain cannot be longer than 32 characters');
|
if (strlen($domain) > 32) throw new exception('Domain cannot be longer than 32 characters');
|
||||||
|
|
||||||
// Initializing of path to file
|
// Initializing of path to file
|
||||||
|
@ -57,7 +58,7 @@ class server extends core
|
||||||
// File found
|
// File found
|
||||||
|
|
||||||
// Open file with server data
|
// Open file with server data
|
||||||
$file = fopen($path, "r");
|
$file = fopen($path, "c+");
|
||||||
|
|
||||||
// Read server data
|
// Read server data
|
||||||
$old = json_decode(fread($file, filesize($path)), true, 8);
|
$old = json_decode(fread($file, filesize($path)), true, 8);
|
||||||
|
@ -69,7 +70,7 @@ class server extends core
|
||||||
// The keys match or the file has not been updated for more than 3 days
|
// The keys match or the file has not been updated for more than 3 days
|
||||||
|
|
||||||
// Open file with server data
|
// Open file with server data
|
||||||
$file = fopen($path, "w");
|
$file = fopen($path, "c");
|
||||||
|
|
||||||
// Write server data
|
// Write server data
|
||||||
fwrite($file, json_encode($new));
|
fwrite($file, json_encode($new));
|
||||||
|
@ -79,12 +80,15 @@ class server extends core
|
||||||
|
|
||||||
// Write DNS record
|
// Write DNS record
|
||||||
dns::write(domain: $new['domain'], ip: $new['ip'], port: $new['port'], errors: $errors);
|
dns::write(domain: $new['domain'], ip: $new['ip'], port: $new['port'], errors: $errors);
|
||||||
|
|
||||||
|
// Write to the log of servers
|
||||||
|
log::write(type::SERVERS, "[UPDATE] {$old['domain']} {$old['ip']}:{$old['port']} -> {$new['domain']} {$new['ip']}:{$new['port']}");
|
||||||
} else throw new exception('Public keys do not match');
|
} else throw new exception('Public keys do not match');
|
||||||
} else {
|
} else {
|
||||||
// File is not found
|
// File is not found
|
||||||
|
|
||||||
// Open file with server data
|
// Open file with server data
|
||||||
$file = fopen($path, "w");
|
$file = fopen($path, "c");
|
||||||
|
|
||||||
// Write server data
|
// Write server data
|
||||||
fwrite($file, json_encode($new));
|
fwrite($file, json_encode($new));
|
||||||
|
@ -94,15 +98,21 @@ class server extends core
|
||||||
|
|
||||||
// Write DNS record
|
// Write DNS record
|
||||||
dns::write(domain: $new['domain'], ip: $new['ip'], port: $new['port'], errors: $errors);
|
dns::write(domain: $new['domain'], ip: $new['ip'], port: $new['port'], errors: $errors);
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::SERVERS, "[CREATE] {$new['domain']} {$new['ip']}:{$new['port']}");
|
||||||
}
|
}
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Write to buffer of errors
|
// Write to the buffer of errors
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
'line' => $e->getLine(),
|
'line' => $e->getLine(),
|
||||||
'stack' => $e->getTrace()
|
'stack' => $e->getTrace()
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,18 +122,25 @@ class server extends core
|
||||||
* Read JSON from file of server
|
* Read JSON from file of server
|
||||||
*
|
*
|
||||||
* @param string $domain Domain of the server
|
* @param string $domain Domain of the server
|
||||||
|
* @param int $time Number of seconds since the file was last edited (86400 seconds is 1 day)
|
||||||
* @param array &$errors Buffer of errors
|
* @param array &$errors Buffer of errors
|
||||||
*
|
*
|
||||||
* @return string|null JSON with data of the server
|
* @return string|null JSON with data of the server
|
||||||
*/
|
*/
|
||||||
public static function read(string $domain, &$errors = []): ?string
|
public static function read(string $domain, int $time = 86400, &$errors = []): ?string
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Initializing of path to file
|
// Initializing of path to file
|
||||||
$path = static::SERVERS . DIRECTORY_SEPARATOR . "$domain.json";
|
$path = static::SERVERS . DIRECTORY_SEPARATOR . "$domain.json";
|
||||||
|
|
||||||
|
if (file_exists($path) && filesize($path) > 0) {
|
||||||
|
// File exists and not empty
|
||||||
|
|
||||||
|
if (time() - filectime($path) < $time && is_readable($path)) {
|
||||||
|
// The file is actual (1 day by default) and writable
|
||||||
|
|
||||||
// Open file with server data
|
// Open file with server data
|
||||||
$file = fopen($path, "r");
|
$file = fopen($path, 'c+');
|
||||||
|
|
||||||
// Read server data
|
// Read server data
|
||||||
$server = fread($file, filesize($path));
|
$server = fread($file, filesize($path));
|
||||||
|
@ -133,14 +150,19 @@ class server extends core
|
||||||
|
|
||||||
// Exit (success)
|
// Exit (success)
|
||||||
return $server;
|
return $server;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Write to buffer of errors
|
// Write to the buffer of errors
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
'line' => $e->getLine(),
|
'line' => $e->getLine(),
|
||||||
'stack' => $e->getTrace()
|
'stack' => $e->getTrace()
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit (fail)
|
// Exit (fail)
|
||||||
|
@ -175,6 +197,8 @@ class server extends core
|
||||||
$skip = $page * $amount;
|
$skip = $page * $amount;
|
||||||
|
|
||||||
foreach (new parser(static::SERVERS) as $file) {
|
foreach (new parser(static::SERVERS) as $file) {
|
||||||
|
// Iterate through all files in a directory
|
||||||
|
|
||||||
// Skipping unnecessary files
|
// Skipping unnecessary files
|
||||||
if (--$skip > $amount) continue;
|
if (--$skip > $amount) continue;
|
||||||
|
|
||||||
|
@ -182,31 +206,39 @@ class server extends core
|
||||||
if ($file->isDot()) continue;
|
if ($file->isDot()) continue;
|
||||||
|
|
||||||
if (time() - $file->getCTime() < $time && $file->isReadable()) {
|
if (time() - $file->getCTime() < $time && $file->isReadable()) {
|
||||||
// The file is actual (3 days by default) and writable
|
// The file is actual (1 day by default) and readable
|
||||||
|
|
||||||
// Open file with server data
|
if (($size = $file->getSize()) > 0) {
|
||||||
$server = $file->openFile('r');
|
// The file is not empty
|
||||||
|
|
||||||
|
// Open the file with server data
|
||||||
|
$server = $file->openFile('c+');
|
||||||
|
|
||||||
// Write server data to the output buffer
|
// Write server data to the output buffer
|
||||||
$buffer[] = json_decode($server->fread($file->getSize()));
|
$buffer[] = json_decode($server->fread($size));
|
||||||
|
|
||||||
// Close file with server data
|
// Close the file with server data
|
||||||
unset($file);
|
unset($file);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
if (--$amount < 1) break;
|
if (--$amount < 1) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit (success)
|
// Exit (success)
|
||||||
return $buffer;
|
return $buffer;
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Write to buffer of errors
|
// Write to the buffer of errors
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
'line' => $e->getLine(),
|
'line' => $e->getLine(),
|
||||||
'stack' => $e->getTrace()
|
'stack' => $e->getTrace()
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit (fail)
|
// Exit (fail)
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\notchat\models;
|
||||||
|
|
||||||
|
// Files of the project
|
||||||
|
use mirzaev\notchat\models\enumerations\log as type;
|
||||||
|
|
||||||
|
// Built-in libraries
|
||||||
|
use exception,
|
||||||
|
DirectoryIterator as parser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text
|
||||||
|
*
|
||||||
|
* @package mirzaev\notchat\models
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
class text extends core
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Path to the directory with translation files (json)
|
||||||
|
*/
|
||||||
|
final public const LANGUAGES = core::PUBLIC . DIRECTORY_SEPARATOR . 'languages';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default language
|
||||||
|
*/
|
||||||
|
final public const LANGUAGE = 'english';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read
|
||||||
|
*
|
||||||
|
* @param string $id Identifier
|
||||||
|
* @param string $language Language (name of thw file without ".json")
|
||||||
|
* @param array &$errors Buffer of errors
|
||||||
|
*
|
||||||
|
* @return string|null Text, if found
|
||||||
|
*/
|
||||||
|
public static function read(string $id, string $language = 'english', &$errors = []): ?string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Initializing of path to the file of the log
|
||||||
|
$path = static::LANGUAGES . DIRECTORY_SEPARATOR . "$language.json";
|
||||||
|
|
||||||
|
if (file_exists($path)) {
|
||||||
|
// The file exists
|
||||||
|
|
||||||
|
// Open the file of translation
|
||||||
|
$json = file_get_contents($path);
|
||||||
|
|
||||||
|
if (!empty($json)) {
|
||||||
|
// The file is not empty
|
||||||
|
|
||||||
|
// Decoding JSON to Array
|
||||||
|
$text = json_decode($json, true, 8);
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return $text[$id] ?? throw new exception('Could not find the text in translation file');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Write to buffer of errors
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit (fail)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a list of available languages
|
||||||
|
*
|
||||||
|
* @param array &$errors Buffer of errors
|
||||||
|
*
|
||||||
|
* @return array|null Languages, if they found
|
||||||
|
*/
|
||||||
|
public static function list(&$errors = []): ?array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Initializing of the buffer of languages
|
||||||
|
$languages = [];
|
||||||
|
|
||||||
|
foreach (new parser(static::LANGUAGES) as $file) {
|
||||||
|
// Iterate through all files in the languages directory
|
||||||
|
|
||||||
|
// Skipping system shortcuts
|
||||||
|
if ($file->isDot()) continue;
|
||||||
|
|
||||||
|
if ($file->isReadable() && $file->getSize() > 0) {
|
||||||
|
// The file is readable and not empty
|
||||||
|
|
||||||
|
// Write a language to the buffer registry of available languages
|
||||||
|
$languages[] = $file->getBasename('.json');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return $languages;
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Write to buffer of errors
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit (fail)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\notchat\models\traits;
|
||||||
|
|
||||||
|
// Files of the project
|
||||||
|
use mirzaev\notchat\models\log,
|
||||||
|
mirzaev\notchat\models\enumerations\log as type;
|
||||||
|
|
||||||
|
// Built-in libraries
|
||||||
|
use exception,
|
||||||
|
generator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait of the file handler
|
||||||
|
*
|
||||||
|
* @package mirzaev\notchat\models\traits
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
trait file
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Read
|
||||||
|
*
|
||||||
|
* @param resource $file File pointer (fopen())
|
||||||
|
* @param int $limit Maximum limit of iterations (rows)
|
||||||
|
* @param int $position Initial cursor position
|
||||||
|
* @param int $step Row reading step
|
||||||
|
* @param array &$errors Buffer of errors
|
||||||
|
*
|
||||||
|
* @return generator|string|null
|
||||||
|
*/
|
||||||
|
private static function read($file, int $limit = 500, int $position = 0, int $step = 1, &$errors = []): ?generator
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
while ($limit-- > 0) {
|
||||||
|
// Recursive execution until $limit reaches 0
|
||||||
|
|
||||||
|
// Initializing of the buffer of row
|
||||||
|
$row = '';
|
||||||
|
|
||||||
|
// Initializing the character buffer to generate $row
|
||||||
|
$character = '';
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Iterate over rows
|
||||||
|
|
||||||
|
// End (or beginning) of file reached (success)
|
||||||
|
if (feof($file)) return;
|
||||||
|
|
||||||
|
// Reading a row
|
||||||
|
$row = $character . $row;
|
||||||
|
|
||||||
|
// Move to next position
|
||||||
|
fseek($file, $position += $step, SEEK_END);
|
||||||
|
|
||||||
|
// Read a character
|
||||||
|
$character = fgetc($file);
|
||||||
|
|
||||||
|
// Is the character a carriage return? (end of row)
|
||||||
|
} while ($character != PHP_EOL);
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
yield $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return null;
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Write to the buffer of errors
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit (fail)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\notchat\models\traits;
|
||||||
|
|
||||||
|
// Files of the project
|
||||||
|
use mirzaev\notchat\models\log as model,
|
||||||
|
mirzaev\notchat\models\enumerations\log as type;
|
||||||
|
|
||||||
|
// Built-in libraries
|
||||||
|
use exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait of the log handler
|
||||||
|
*
|
||||||
|
* @package mirzaev\notchat\models\traits
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
trait log
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Deserializing of type::CONNECTIONS
|
||||||
|
*
|
||||||
|
* @param string $row Row from the log type::CONNECTIONS
|
||||||
|
* @param array &$errors Buffer of errors
|
||||||
|
*
|
||||||
|
* @return array [$row, $date, $ip, $forwarded, $referer, $useragent]
|
||||||
|
*/
|
||||||
|
private static function connection(string $row, &$errors = []): ?array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Search for parameters of connection
|
||||||
|
preg_match('/(?:^\[(\d{4}\.\d{2}\.\d{2}\s\d{2}:\d{2}:\d{2})\]\s?)(?:\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]\s?)(?:\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]\s?)?(?:\[([^\]]+)\]\s[^$]?)?(?:\[([^\]]+)\]\s?)?$/', trim($row, PHP_EOL), $matches);
|
||||||
|
|
||||||
|
// Have all 5 parameters been detected?
|
||||||
|
if (count($matches) !== 6) throw new exception('Failed to deserialize row');
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return $matches;
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Write to the buffer of errors
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
model::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit (fail)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserializing of type::BANS
|
||||||
|
*
|
||||||
|
* @param string $row Row from the log type::BANS
|
||||||
|
* @param array &$errors Buffer of errors
|
||||||
|
*
|
||||||
|
* @return array [$row, $from, $to, $ip]
|
||||||
|
*/
|
||||||
|
private static function ban(string $row, &$errors = []): ?array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Search for parameters of ban
|
||||||
|
preg_match('/(?:^\[(\d{4}\.\d{2}\.\d{2}\s\d{2}:\d{2}:\d{2})\]\s?)(?:\[(\d{4}\.\d{2}\.\d{2}\s\d{2}:\d{2}:\d{2})\]\s?)(?:\[([^\]]+)\]\s?)$/', trim($row, PHP_EOL), $matches);
|
||||||
|
|
||||||
|
// Have all 3 parameters been detected?
|
||||||
|
if (count($matches) !== 4) throw new exception('Failed to deserialize row');
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return $matches;
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Write to the buffer of errors
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
|
||||||
|
// Write to the log of errors
|
||||||
|
model::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit (fail)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ define('STORAGE', realpath('..' . DIRECTORY_SEPARATOR . 'storage'));
|
||||||
define('INDEX', __DIR__);
|
define('INDEX', __DIR__);
|
||||||
|
|
||||||
// Автозагрузка
|
// Автозагрузка
|
||||||
require __DIR__ . DIRECTORY_SEPARATOR
|
require INDEX . DIRECTORY_SEPARATOR
|
||||||
. '..' . DIRECTORY_SEPARATOR
|
. '..' . DIRECTORY_SEPARATOR
|
||||||
. '..' . DIRECTORY_SEPARATOR
|
. '..' . DIRECTORY_SEPARATOR
|
||||||
. '..' . DIRECTORY_SEPARATOR
|
. '..' . DIRECTORY_SEPARATOR
|
||||||
|
@ -33,10 +33,14 @@ require __DIR__ . DIRECTORY_SEPARATOR
|
||||||
$router = new router;
|
$router = new router;
|
||||||
|
|
||||||
// Запись маршрутов
|
// Запись маршрутов
|
||||||
$router->write('/', 'index', 'index');
|
$router->write('/', 'index', 'index', 'GET');
|
||||||
|
$router->write('/', 'index', 'index', 'POST');
|
||||||
|
$router->write('/manifest', 'index', 'manifest', 'GET');
|
||||||
|
$router->write('/offline', 'index', 'offline', 'GET');
|
||||||
|
$router->write('/cache.js', 'index', 'cache', 'GET');
|
||||||
$router->write('/server/read/$server', 'server', 'read', 'POST');
|
$router->write('/server/read/$server', 'server', 'read', 'POST');
|
||||||
|
$router->write('/server/write/$server', 'server', 'write', 'POST');
|
||||||
$router->write('/servers', 'index', 'servers', 'POST');
|
$router->write('/servers', 'index', 'servers', 'POST');
|
||||||
$router->write('/servers/connect/$domain', 'server', 'write', 'POST');
|
|
||||||
|
|
||||||
// Инициализация ядра
|
// Инициализация ядра
|
||||||
$core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false));
|
$core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false));
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const VERSION = "0.1.0";
|
||||||
|
const EXPIRED = 86400000;
|
||||||
|
|
||||||
|
self.addEventListener("install", (event) => {
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener("fetch", (event) => {
|
||||||
|
event
|
||||||
|
.respondWith(
|
||||||
|
caches
|
||||||
|
.match(event.request)
|
||||||
|
.then((response) => {
|
||||||
|
if (response) {
|
||||||
|
// Found file in cache
|
||||||
|
|
||||||
|
if (
|
||||||
|
Date.now() - new Date(response.headers.get("last-modified")) >
|
||||||
|
EXPIRED
|
||||||
|
) {
|
||||||
|
// Expired period of storage response
|
||||||
|
|
||||||
|
return fetch(event.request.clone())
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
// Downloaded new version
|
||||||
|
|
||||||
|
return caches
|
||||||
|
.open(VERSION)
|
||||||
|
.then((cache) => {
|
||||||
|
// Writing new version to cache
|
||||||
|
cache.put(event.request, response.clone());
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
} else throw "Failed to download new version";
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Exit (success) (return old version)
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
} else return response;
|
||||||
|
} else {
|
||||||
|
// Not found file in cache
|
||||||
|
|
||||||
|
return fetch(event.request.clone())
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
// Downloaded
|
||||||
|
|
||||||
|
return caches
|
||||||
|
.open(VERSION)
|
||||||
|
.then((cache) => {
|
||||||
|
// Writing to cache
|
||||||
|
cache.put(event.request, response.clone());
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
} else throw "Failed to download";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return caches.match("/offline");
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* self.addEventListener("activate", (event) => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.keys().then((keyList) => {
|
||||||
|
return Promise.all(
|
||||||
|
keyList.map((key) => {
|
||||||
|
// Deleting old versions of cache
|
||||||
|
if (VERSION.indexOf(key) === -1) return caches.delete(key);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}); */
|
|
@ -10,27 +10,161 @@ if (typeof window.chats !== "function") {
|
||||||
*/
|
*/
|
||||||
static server = {
|
static server = {
|
||||||
/**
|
/**
|
||||||
* Select server
|
* Select a server (dampered)
|
||||||
*
|
*
|
||||||
* @param {string} server Domain or IP-address of the server (from cache by default)
|
* @param {string} server Domain or IP-address:port of the server (from cache by default)
|
||||||
|
* @param {bool} force Force execution
|
||||||
*
|
*
|
||||||
* @return {void} Into the document will be generated and injected an HTML-element
|
* @return {void} Into the document will be generated and injected an HTML-element
|
||||||
*/
|
*/
|
||||||
select(server = localStorage.server_ip ?? localStorage.server_domain) {
|
select(
|
||||||
if (typeof server === "string" && server.length > 0) {
|
server = localStorage.server_ip && localStorage.server_port
|
||||||
if (core.servers instanceof HTMLElement) {
|
? localStorage.server_ip + ":" + localStorage.server_port
|
||||||
core.request(`/server/read/${server}`).then((json) => {
|
: localStorage.server_domain,
|
||||||
|
force = false,
|
||||||
|
) {
|
||||||
|
// Writing status: "connecting"
|
||||||
|
core.menu.setAttribute("data-menu-status", "connecting");
|
||||||
|
|
||||||
|
// Deinitializing animation of opening
|
||||||
|
core.menu.getElementsByTagName("output")[0].classList.remove(
|
||||||
|
"slide-down",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initializing animation of closing
|
||||||
|
core.menu.getElementsByTagName("output")[0].classList.add(
|
||||||
|
"slide-down-revert",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Disabled for animation
|
||||||
|
/* core.menu.querySelector('figcaption[data-server="domain"]')
|
||||||
|
.innerText =
|
||||||
|
core.menu.querySelector('pre[data-server="description"]')
|
||||||
|
.innerText =
|
||||||
|
""; */
|
||||||
|
|
||||||
|
this._select(server, force);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a server
|
||||||
|
*
|
||||||
|
* @param {string} server Domain or IP-address:port of the server (from cache by default)
|
||||||
|
* @param {bool} force Force execution
|
||||||
|
*
|
||||||
|
* @return {void} Into the document will be generated and injected an HTML-element
|
||||||
|
*/
|
||||||
|
_select: damper(
|
||||||
|
(
|
||||||
|
server = localStorage.server_ip && localStorage.server_port
|
||||||
|
? localStorage.server_ip + ":" + localStorage.server_port
|
||||||
|
: localStorage.server_domain,
|
||||||
|
force = false,
|
||||||
|
) => {
|
||||||
|
if (server.length > 512) {
|
||||||
|
notifications.write(text.read("CHATS_SERVER_ERROR_LENGTH_MAX"));
|
||||||
|
} else if (typeof server === "string" && server.length > 0) {
|
||||||
|
if (
|
||||||
|
core.menu instanceof HTMLElement &&
|
||||||
|
core.menu.getAttribute("data-menu") === "chats"
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
|
||||||
|
// Initializing the unlock function
|
||||||
|
function unblock() {
|
||||||
|
// Writing status: "empty"
|
||||||
|
if (
|
||||||
|
core.menu.querySelector('figcaption[data-server="domain"]')
|
||||||
|
.innerText.length === 0 &&
|
||||||
|
core.menu.querySelector('pre[data-server="description"]')
|
||||||
|
.innerText.length === 0
|
||||||
|
) {
|
||||||
|
core.menu.getElementsByTagName("search")[0]
|
||||||
|
.getElementsByTagName("label")[0].classList.add(
|
||||||
|
"empty",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writing status: "disconnected"
|
||||||
|
core.menu.setAttribute("data-menu-status", "disconnected");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initiating a unlock delay in case the server does not respond
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
// this.errors(["Server does not respond"]);
|
||||||
|
unblock();
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
core.request(
|
||||||
|
`/server/read/${encodeURIComponent(server)}`,
|
||||||
|
`language=${localStorage.language ?? "english"}`,
|
||||||
|
).then((json) => {
|
||||||
|
// Deinitializing of unlock delay
|
||||||
|
clearTimeout(timeout);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
json.errors !== null && typeof json.errors === "object" &&
|
json.errors !== null && typeof json.errors === "object" &&
|
||||||
json.errors.length > 0
|
json.errors.length > 0
|
||||||
) {} else {
|
) {
|
||||||
document.querySelector('figcaption[data-server="ip"]').innerText = `${json.ip}:${json.port}`;
|
// Generating notifications with errors
|
||||||
document.querySelector('figcaption[data-server="description"]').innerText = `${json.description}`;
|
// for (const error of json.errors) notifications.write(error);
|
||||||
|
|
||||||
|
// Writing status: "disconnected"
|
||||||
|
core.menu.setAttribute("data-menu-status", "disconnected");
|
||||||
|
|
||||||
|
// Writind domain of the server
|
||||||
|
core.menu.querySelector('figcaption[data-server="domain"]')
|
||||||
|
.innerText = "";
|
||||||
|
|
||||||
|
// Writing description of the server
|
||||||
|
core.menu.querySelector('pre[data-server="description"]')
|
||||||
|
.innerText = "";
|
||||||
|
|
||||||
|
// Writing status: "empty" (for opening the description window)
|
||||||
|
core.menu.getElementsByTagName("search")[0]
|
||||||
|
.getElementsByTagName("label")[0].classList.add(
|
||||||
|
"empty",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Writing status: "connected"
|
||||||
|
core.menu.setAttribute("data-menu-status", "connected");
|
||||||
|
|
||||||
|
// Writind domain of the server
|
||||||
|
core.menu.querySelector('figcaption[data-server="domain"]')
|
||||||
|
.innerText = `${json.server.domain}`;
|
||||||
|
|
||||||
|
// Writing description of the server
|
||||||
|
core.menu.querySelector('pre[data-server="description"]')
|
||||||
|
.innerText = `${json.server.description}`;
|
||||||
|
|
||||||
|
// Deleting status: "empty" (for opening the description window) (it is implied that the response from the server cannot be empty)
|
||||||
|
core.menu.getElementsByTagName("search")[0]
|
||||||
|
.getElementsByTagName("label")[0].classList.remove(
|
||||||
|
"empty",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Deinitializing animation of closing
|
||||||
|
core.menu.getElementsByTagName("output")[0].classList.remove(
|
||||||
|
"slide-down-revert",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initializing animation of opening
|
||||||
|
core.menu.getElementsByTagName("output")[0].classList.add(
|
||||||
|
"slide-down",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Writing data of the server to local storage (browser)
|
||||||
|
localStorage.server_domain = json.server.domain;
|
||||||
|
localStorage.server_ip = json.server.ip;
|
||||||
|
localStorage.server_port = json.server.port;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
800,
|
||||||
|
1,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,26 +178,41 @@ if (typeof window.chats !== "function") {
|
||||||
*
|
*
|
||||||
* @return {void} Into the document will be generated and injected an HTML-element
|
* @return {void} Into the document will be generated and injected an HTML-element
|
||||||
*/
|
*/
|
||||||
servers(server = localStorage.server_ip ?? localStorage.server_domain) {
|
servers(
|
||||||
|
server = `${localStorage.server_ip}:${localStorage.server_port}`,
|
||||||
|
) {
|
||||||
core.request(
|
core.request(
|
||||||
"/servers",
|
"/servers",
|
||||||
typeof server === "string" && server.length > 0
|
typeof server === "string" && server.length > 0
|
||||||
? `server=${server}}`
|
? `server=${server}`
|
||||||
: "",
|
: "",
|
||||||
).then((json) => {
|
).then((json) => {
|
||||||
if (core.servers instanceof HTMLElement) core.servers.remove();
|
if (core.servers instanceof HTMLElement) core.servers.remove();
|
||||||
if (
|
if (
|
||||||
json.errors !== null && typeof json.errors === "object" &&
|
json.errors !== null && typeof json.errors === "object" &&
|
||||||
json.errors.length > 0
|
json.errors.length > 0
|
||||||
) {} else {
|
) {
|
||||||
const element = document.createElement("div");
|
// Generating notifications with errors
|
||||||
core.header.after(element);
|
for (const error of json.errors) notifications.write(error);
|
||||||
|
} else {
|
||||||
|
if (core.menu instanceof HTMLElement) {
|
||||||
|
// Writing status of connection (hack with replaying animations)
|
||||||
|
core.menu.setAttribute('data-menu-status', 'disconnected');
|
||||||
|
setTimeout(() => core.menu.setAttribute('data-menu-status', json.status ?? 'disconnected'), 100);
|
||||||
|
|
||||||
|
const element = document.createElement("search");
|
||||||
|
|
||||||
|
const search = core.menu.getElementsByTagName("search")[0];
|
||||||
|
if (search instanceof HTMLElement) search.remove();
|
||||||
|
|
||||||
|
core.menu.prepend(element);
|
||||||
element.outerHTML = json.html;
|
element.outerHTML = json.html;
|
||||||
|
|
||||||
core.servers = document.body.querySelector(
|
core.menu = document.body.querySelector(
|
||||||
"section[data-section='servers']",
|
"section[data-section='menu']",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -77,7 +226,11 @@ if (typeof window.chats !== "function") {
|
||||||
if (
|
if (
|
||||||
json.errors !== null && typeof json.errors === "object" &&
|
json.errors !== null && typeof json.errors === "object" &&
|
||||||
json.errors.length > 0
|
json.errors.length > 0
|
||||||
) {} else {
|
) {
|
||||||
|
// Generating notifications with errors
|
||||||
|
for (const error of json.errors) notifications.write(error);
|
||||||
|
} else {
|
||||||
|
// сосать бебру
|
||||||
const element = document.createElement("div");
|
const element = document.createElement("div");
|
||||||
const position = core.main.children.length;
|
const position = core.main.children.length;
|
||||||
element.style.setProperty("--position", position);
|
element.style.setProperty("--position", position);
|
||||||
|
|
|
@ -5,8 +5,11 @@ if (typeof window.core !== "function") {
|
||||||
|
|
||||||
// Initialize of the class in global namespace
|
// Initialize of the class in global namespace
|
||||||
window.core = class core {
|
window.core = class core {
|
||||||
// Label for the <main> element
|
// Domain
|
||||||
static main = document.body.getElementsByTagName('main')[0];
|
static domain = window.location.hostname;
|
||||||
|
|
||||||
|
// Animations are enabled?
|
||||||
|
static animations = getComputedStyle(document.body).getPropertyValue('--animations') === '1';
|
||||||
|
|
||||||
// Label for the <header> element
|
// Label for the <header> element
|
||||||
static header = document.body.getElementsByTagName('header')[0];
|
static header = document.body.getElementsByTagName('header')[0];
|
||||||
|
@ -14,11 +17,11 @@ if (typeof window.core !== "function") {
|
||||||
// Label for the <aside> element
|
// Label for the <aside> element
|
||||||
static aside = document.body.getElementsByTagName('aside')[0];
|
static aside = document.body.getElementsByTagName('aside')[0];
|
||||||
|
|
||||||
// Label for the "servers" element
|
// Label for the "menu" element
|
||||||
static servers = document.body.querySelector("section[data-section='servers']");
|
static menu = document.body.querySelector("section[data-section='menu']");
|
||||||
|
|
||||||
// Label for the "chats" element
|
// Label for the <main> element
|
||||||
static chats = document.body.querySelector("section[data-section='chats']");
|
static main = document.body.getElementsByTagName('main')[0];
|
||||||
|
|
||||||
// Label for the <footer> element
|
// Label for the <footer> element
|
||||||
static footer = document.body.getElementsByTagName('footer')[0];
|
static footer = document.body.getElementsByTagName('footer')[0];
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Damper
|
||||||
|
*
|
||||||
|
* @param {function} function Function to execute after damping
|
||||||
|
* @param {number} timeout Timer in milliseconds (ms)
|
||||||
|
* @param {number} force Argument number storing the enforcement status of execution (see @example)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* $a = damper(
|
||||||
|
* async (
|
||||||
|
* a, // 0
|
||||||
|
* b, // 1
|
||||||
|
* c, // 2
|
||||||
|
* force = false, // 3
|
||||||
|
* d // 4
|
||||||
|
* ) => {
|
||||||
|
* // Body of function
|
||||||
|
* },
|
||||||
|
* 500,
|
||||||
|
* 3, // 3 -> "force" argument
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* $a('for a', 'for b', 'for c', true, 'for d'); // Force execute is enabled
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
function damper(func, timeout = 300, force) {
|
||||||
|
// Initializing of the timer
|
||||||
|
let timer;
|
||||||
|
|
||||||
|
return (...args) => {
|
||||||
|
// Deinitializing of the timer
|
||||||
|
clearTimeout(timer);
|
||||||
|
|
||||||
|
if (typeof force === "number" && args[force]) {
|
||||||
|
// Force execution (ignoring the timer)
|
||||||
|
|
||||||
|
func.apply(this, args);
|
||||||
|
} else {
|
||||||
|
// Normal execution
|
||||||
|
|
||||||
|
// Execute the handled function (entry into recursion)
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
func.apply(this, args);
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch event: "initialized"
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent("damper.initialized", {
|
||||||
|
detail: { damper },
|
||||||
|
}),
|
||||||
|
);
|
|
@ -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,19 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
if (typeof window.journal !== "function") {
|
||||||
|
// Not initialized
|
||||||
|
|
||||||
|
// Initialize of the class in global namespace
|
||||||
|
window.journal = class cache {
|
||||||
|
static write(text) {
|
||||||
|
console.log(`[${core.domain ?? "notchat"}] ${text}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch event: "initialized"
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent("journal.initialized", {
|
||||||
|
detail: { journal: window.journal },
|
||||||
|
}),
|
||||||
|
);
|
|
@ -0,0 +1,3 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
try {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register("cache.js")
|
||||||
|
.then((cache) => {
|
||||||
|
// Registered
|
||||||
|
journal.write("ServiceWorker registered: " + cache.scope);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
// Not registered
|
||||||
|
journal.write("ServiceWorker not registered: " + error);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`pizda ${error}`);
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
if (typeof window.text !== "function") {
|
||||||
|
// Not initialized
|
||||||
|
|
||||||
|
// Initialize of the class in global namespace
|
||||||
|
window.text = class text {
|
||||||
|
/**
|
||||||
|
* Language
|
||||||
|
*/
|
||||||
|
static language = {
|
||||||
|
/**
|
||||||
|
* Select a language
|
||||||
|
*
|
||||||
|
* @param {string} language Name of language
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
select(language = 'english') {
|
||||||
|
// Write to the local storage in browser
|
||||||
|
localStorage.language = language;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch event: "initialized"
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent("text.initialized", {
|
||||||
|
detail: { text: window.text },
|
||||||
|
}),
|
||||||
|
);
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"CHATS_SERVER_ERROR_LENGTH_MAX": "Server address longer than 512 characters"
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"CHATS_SERVER_ERROR_LENGTH_MAX": "Адрес сервера длиннее 512-ти символов"
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
CACHE MANIFEST
|
||||||
|
|
||||||
|
CACHE:
|
||||||
|
/themes/default/css/main.css
|
||||||
|
/themes/default/css/fonts.css
|
||||||
|
/themes/default/css/fonts/dejavu.css
|
||||||
|
/themes/default/css/fonts/fira.css
|
||||||
|
/themes/default/css/fonts/hack.css
|
||||||
|
/themes/default/css/animations.css
|
||||||
|
/themes/default/css/chats.css
|
||||||
|
/themes/default/images/favicon.ico
|
||||||
|
/themes/default/fonts/dejavu/DejaVuLGCSans-ExtraLight.ttf
|
||||||
|
/themes/default/fonts/dejavu/DejaVuLGCSans.ttf
|
||||||
|
/themes/default/fonts/dejavu/DejaVuLGCSans-Oblique.ttf
|
||||||
|
/themes/default/fonts/dejavu/DejaVuLGCSans-Bold.ttf
|
||||||
|
/themes/default/fonts/dejavu/DejaVuLGCSans-BoldOblique.ttf
|
||||||
|
/themes/default/fonts/fira/FiraSans-Hair.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-HairItalic.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-UltraLight.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-UltraLightItalic.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-Light.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-LightItalic.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-Regular.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-Italic.woff2
|
||||||
|
/themes/default/fonts/fira/FiraMono-Medium.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-MediumItalic.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-SemiBold.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-SemiBoldItalic.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-Bold.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-BoldItalic.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-ExtraBold.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-ExtraBoldItalic.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-Heavy.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-HeavyItalic.woff2
|
||||||
|
/themes/default/fonts/fira/FiraMono-Regular.woff2
|
||||||
|
/themes/default/fonts/fira/FiraMono-Bold.woff2
|
||||||
|
/themes/default/fonts/fira/FiraSans-Hair.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-HairItalic.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-UltraLight.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-UltraLightItalic.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-Light.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-LightItalic.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-Regular.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-Italic.woff
|
||||||
|
/themes/default/fonts/fira/FiraMono-Medium.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-MediumItalic.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-SemiBold.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-SemiBoldItalic.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-Bold.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-BoldItalic.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-ExtraBold.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-ExtraBoldItalic.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-Heavy.woff
|
||||||
|
/themes/default/fonts/fira/FiraSans-HeavyItalic.woff
|
||||||
|
/themes/default/fonts/fira/FiraMono-Regular.woff
|
||||||
|
/themes/default/fonts/fira/FiraMono-Bold.woff
|
||||||
|
/themes/default/fonts/hack/hack-regular.woff2?sha=3114f1256
|
||||||
|
/themes/default/fonts/hack/hack-bold.woff2?sha=3114f1256
|
||||||
|
/themes/default/fonts/hack/hack-italic.woff2?sha=3114f1256
|
||||||
|
/themes/default/fonts/hack/hack-bolditalic.woff2?sha=3114f1256
|
||||||
|
/themes/default/fonts/hack/hack-regular.woff?sha=3114f1256
|
||||||
|
/themes/default/fonts/hack/hack-bold.woff?sha=3114f1256
|
||||||
|
/themes/default/fonts/hack/hack-italic.woff?sha=3114f1256
|
||||||
|
/themes/default/fonts/hack/hack-bolditalic.woff?sha=3114f1256
|
||||||
|
/themes/default/icons/data.css
|
||||||
|
/themes/default/images/ban.jpg
|
||||||
|
/themes/default/images/offline.jpg
|
||||||
|
/js/asdasd/libraries/noble-hashes.js
|
||||||
|
/js/asdasd/asdasd.js
|
||||||
|
/js/pages/chat.js
|
||||||
|
/js/adaoter.js
|
||||||
|
/js/cache.js
|
||||||
|
/js/chats.js
|
||||||
|
/js/core.js
|
||||||
|
/js/damper.js
|
||||||
|
/js/notchat.js
|
||||||
|
/js/notifications.js
|
||||||
|
/js/text.js
|
||||||
|
/languages/english.json
|
||||||
|
/languages/russian.json
|
||||||
|
/
|
||||||
|
|
||||||
|
NETWORK:
|
||||||
|
*
|
||||||
|
|
||||||
|
FALLBACK:
|
||||||
|
/ /offline
|
|
@ -1,5 +1,9 @@
|
||||||
@charset "UTF-8";
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--animations: 1;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes uprise {
|
@keyframes uprise {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
@ -13,16 +17,58 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.animation.uprise {
|
.animation.uprise {
|
||||||
animation-duration: 0.1s;
|
animation-duration: var(--animation-duration, 0.1s);
|
||||||
animation-name: uprise;
|
animation-name: uprise;
|
||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
||||||
animation-timing-function: ease-in;
|
animation-timing-function: ease-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes slide-down {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, -100%);
|
||||||
|
clip-path: polygon(0% 100%, 100% 100%, 100% 200%, 0% 200%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translate(0, 0%);
|
||||||
|
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation.slide-down {
|
||||||
|
animation-duration: var(--animation-duration, 0.2s);
|
||||||
|
animation-name: slide-down;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-timing-function: cubic-bezier(0, 1, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-down-revert {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0%);
|
||||||
|
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translate(0, -100%);
|
||||||
|
clip-path: polygon(0% 100%, 100% 100%, 100% 200%, 0% 200%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation.slide-down-revert {
|
||||||
|
animation-duration: var(--animation-duration, 0.2s);
|
||||||
|
animation-name: slide-down-revert;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-timing-function: cubic-bezier(1, 0, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes marquee {
|
@keyframes marquee {
|
||||||
0% { left: 0; }
|
0% {
|
||||||
100% { left: -100%; }
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
left: -100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.animation.marquee {
|
.animation.marquee {
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"] {
|
||||||
|
position: relative;
|
||||||
|
padding: unset;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"]>search {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"]>search>label {
|
||||||
|
z-index: 200;
|
||||||
|
position: relative;
|
||||||
|
height: 1.9rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow: clip;
|
||||||
|
color: var(--input-servers-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"]>search>label>input {
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: 0.3s cubic-bezier(1, 0, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"]>search>label>input:placeholder-shown {
|
||||||
|
/* blyaaaa */
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"]:not([data-menu-status="connecting"])>search>label:has(+ output>figure>figcaption:not(:empty) + pre:not(:empty))>input {
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
transition: 0.3s cubic-bezier(0, 1, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"]>search>label>i:first-child:first-of-type {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"]>search>label>i:first-child:first-of-type+input:first-of-type {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--input-servers-text);
|
||||||
|
background-color: var(--input-servers-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"]>search>output {
|
||||||
|
--max-height: 120px;
|
||||||
|
z-index: 100;
|
||||||
|
max-height: var(--max-height, 120px);
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 0 0 5px 5px;
|
||||||
|
background-color: var(--important);
|
||||||
|
transition: .1s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"]>search>output>figure {
|
||||||
|
margin: 13px 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"]>search>label+output>figure:has(> figcaption:empty + pre:empty) {
|
||||||
|
margin: 0 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"]>search>output>figure>pre[data-server="description"] {
|
||||||
|
margin: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* section[data-section="menu"][data-menu="chats"]>div#chats:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
section[data-section="menu"][data-menu="chats"]>search>img[data-server="image"] {
|
||||||
|
z-index: 50;
|
||||||
|
position: absolute;
|
||||||
|
max-width: 200px;
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* section[data-section="menu"][data-menu="chats"]:not(:is([data-menu-status="connected"], [data-menu-status="disconnected"]))>search>img[data-server="image"] {
|
||||||
|
opacity: 0;
|
||||||
|
} */
|
|
@ -0,0 +1,38 @@
|
||||||
|
section.hotline {
|
||||||
|
display: inline-flex;
|
||||||
|
height: calc(100% - 20px);
|
||||||
|
padding: 10px 0px;
|
||||||
|
transition: unset;
|
||||||
|
/* gap нельзя */
|
||||||
|
}
|
||||||
|
|
||||||
|
section.hotline * {
|
||||||
|
transition: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
section.hotline:last-child {
|
||||||
|
margin-bottom: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.hotline>article {
|
||||||
|
position: relative;
|
||||||
|
margin-right: 18px;
|
||||||
|
width: calc(140px - var(--padding, 0px) * 2);
|
||||||
|
height: calc(180px - var(--padding, 0px) * 2);
|
||||||
|
padding: var(--padding, 0px);
|
||||||
|
display: flex;
|
||||||
|
align-self: flex-end;
|
||||||
|
overflow: clip;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: var(--blue-dark);
|
||||||
|
/* box-shadow: 0px -6px 6px rgba(0, 0, 0, 0.3); */
|
||||||
|
}
|
||||||
|
|
||||||
|
section.hotline>article:last-child {
|
||||||
|
margin-right: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.hotline>article>* {
|
||||||
|
margin: auto;
|
||||||
|
}
|
|
@ -11,6 +11,7 @@
|
||||||
--red: #4d2d2d;
|
--red: #4d2d2d;
|
||||||
--green: #415e53;
|
--green: #415e53;
|
||||||
--blue: #243b4f;
|
--blue: #243b4f;
|
||||||
|
--blue-dark: #09262d;
|
||||||
|
|
||||||
/* --input-servers: #898c25; очень крутой зелёно говняный цвет */
|
/* --input-servers: #898c25; очень крутой зелёно говняный цвет */
|
||||||
--input-servers-text: #083932;
|
--input-servers-text: #083932;
|
||||||
|
@ -29,14 +30,13 @@
|
||||||
|
|
||||||
body {
|
body {
|
||||||
--row-aside: 200px;
|
--row-aside: 200px;
|
||||||
--row-settings: 100px;
|
|
||||||
--gap: 16px;
|
--gap: 16px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: [header] 220px [settings] 320px [main] auto [footer] 180px;
|
grid-template-columns: [header] 220px [settings] 320px [main] auto [footer] 180px;
|
||||||
grid-template-rows: [aside] var(--row-aside, 200px) [settings] var(--row-settings, 100px) [main] calc(100vh - var(--row-aside) - var(--gap) - var(--row-settings) - var(--gap)) auto;
|
grid-template-rows: [aside] var(--row-aside, 200px) [main] calc(100vh - var(--row-aside));
|
||||||
gap: var(--gap, 16px);
|
gap: var(--gap, 16px);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
|
@ -63,101 +63,33 @@ header>section[data-section="window"] {
|
||||||
|
|
||||||
header>section[data-section="main"] {
|
header>section[data-section="main"] {
|
||||||
z-index: 1200;
|
z-index: 1200;
|
||||||
grid-row: settings / -1;
|
grid-row: main;
|
||||||
display: grid;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
background-color: var(--envelope);
|
background-color: var(--envelope);
|
||||||
}
|
}
|
||||||
|
|
||||||
section[data-section="servers"] {
|
aside {
|
||||||
z-index: 250;
|
z-index: 250;
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
grid-row: aside;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"] {
|
||||||
|
z-index: 300;
|
||||||
padding: 14px 15px;
|
padding: 14px 15px;
|
||||||
grid-row: settings;
|
grid-row: main;
|
||||||
|
grid-column: settings;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background-color: var(--important);
|
background-color: var(--important);
|
||||||
}
|
}
|
||||||
|
|
||||||
section[data-section="servers"]>search {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
section[data-section="servers"]>search>label {
|
|
||||||
position: relative;
|
|
||||||
height: 1.9rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--input-servers-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
section[data-section="servers"]>search>label>i:first-child:first-of-type {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
section[data-section="servers"]>search>label>i:first-child:first-of-type+input:first-of-type {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 0 10px;
|
|
||||||
font-weight: 500;
|
|
||||||
border-radius: 3px;
|
|
||||||
color: var(--input-servers-text);
|
|
||||||
background-color: var(--input-servers-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
section[data-section="chats"] {
|
|
||||||
z-index: 200;
|
|
||||||
margin-bottom: var(--gap);
|
|
||||||
grid-row: main;
|
|
||||||
background-color: var(--section);
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside {
|
|
||||||
z-index: 300;
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
grid-row: aside;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
--sections-default: 1;
|
|
||||||
--sections-width: 520px;
|
|
||||||
z-index: 100;
|
|
||||||
margin-bottom: var(--gap);
|
|
||||||
grid-row: settings / -1;
|
|
||||||
grid-column: main;
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: [settings] var(--row-settings, 100px) [main] auto;
|
|
||||||
grid-template-columns: repeat(var(--sections, var(--sections-default, 1)), [chat] var(--sections-width, 520px));
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--gap, 16px);
|
|
||||||
transition: 0s;
|
|
||||||
}
|
|
||||||
|
|
||||||
main>section {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: var(--sections-width, 480px);
|
|
||||||
grid-column: var(--position, 1);
|
|
||||||
grid-row: main;
|
|
||||||
overflow-x: crop;
|
|
||||||
overflow-y: scroll;
|
|
||||||
background-color: var(--section);
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside {
|
|
||||||
z-index: 300;
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
grid-row: aside;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
main {
|
||||||
--sections-default: 1;
|
--sections-default: 1;
|
||||||
--sections-width: 480px;
|
--sections-width: 480px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
grid-row: settings / -1;
|
grid-row: main;
|
||||||
grid-column: main;
|
grid-column: main;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: [settings] var(--row-settings, 100px) [main] auto;
|
grid-template-rows: [settings] var(--row-settings, 100px) [main] auto;
|
||||||
|
@ -177,10 +109,6 @@ main>section {
|
||||||
background-color: var(--section);
|
background-color: var(--section);
|
||||||
}
|
}
|
||||||
|
|
||||||
main>section[data-panel-type="settings"] {
|
|
||||||
grid-row: settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
z-index: 500;
|
z-index: 500;
|
||||||
grid-column: footer;
|
grid-column: footer;
|
||||||
|
@ -196,11 +124,16 @@ footer>section[data-section="window"] {
|
||||||
|
|
||||||
footer>section[data-section="main"] {
|
footer>section[data-section="main"] {
|
||||||
z-index: 700;
|
z-index: 700;
|
||||||
grid-row: settings / -1;
|
grid-row: main;
|
||||||
display: grid;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
background-color: var(--envelope);
|
background-color: var(--envelope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
footer>section[data-section="main"]>#language {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
:is(div, section).window {
|
:is(div, section).window {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-right: 1px solid;
|
border-right: 1px solid;
|
||||||
|
@ -217,3 +150,7 @@ footer>section[data-section="main"] {
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.untouchable {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
section.hotline > article.trash {
|
||||||
|
--padding: 12px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
cursor: zoom-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.hotline > article.trash > h1 {
|
||||||
|
z-index: 10;
|
||||||
|
margin-top: 10px;
|
||||||
|
flex-grow: 4;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.hotline > article.trash :is(p, b) {
|
||||||
|
z-index: 10;
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.hotline > article.trash small {
|
||||||
|
z-index: 10;
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.hotline > article.trash > img {
|
||||||
|
z-index: 5;
|
||||||
|
position: absolute;
|
||||||
|
left: -5%;
|
||||||
|
top: -5%;
|
||||||
|
width: 110%;
|
||||||
|
height: 110%;
|
||||||
|
object-position: center;
|
||||||
|
object-fit: cover;
|
||||||
|
filter: blur(2px) saturate(30%) brightness(40%);
|
||||||
|
transition: 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.hotline > article.trash:is(:hover, :active) > img {
|
||||||
|
left: -20%;
|
||||||
|
top: -20%;
|
||||||
|
width: 140%;
|
||||||
|
height: 140%;
|
||||||
|
filter: blur(3px) saturate(0) brightness(60%) contrast(150%);
|
||||||
|
transition: 0.1s ease-out;
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 149 KiB |
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
|
@ -0,0 +1 @@
|
||||||
|
*.log
|
|
@ -50,14 +50,14 @@ final class templater extends controller implements ArrayAccess
|
||||||
* Render a HTML-document
|
* Render a HTML-document
|
||||||
*
|
*
|
||||||
* @param string $file Related path to a HTML-document
|
* @param string $file Related path to a HTML-document
|
||||||
* @param ?array $variables Registry of variables to push into registry of global variables
|
* @param array $variables Registry of variables to push into registry of global variables
|
||||||
*
|
*
|
||||||
* @return ?string HTML-document
|
* @return ?string HTML-document
|
||||||
*/
|
*/
|
||||||
public function render(string $file, ?array $variables = null): ?string
|
public function render(string $file, array $variables = []): ?string
|
||||||
{
|
{
|
||||||
// Generation and exit (success)
|
// Generation and exit (success)
|
||||||
return $this->twig->render('themes' . DIRECTORY_SEPARATOR . $this->twig->getGlobals()['theme'] . DIRECTORY_SEPARATOR . $file, $this->twig->mergeGlobals($variables ?? []));
|
return $this->twig->render('themes' . DIRECTORY_SEPARATOR . $this->twig->getGlobals()['theme'] . DIRECTORY_SEPARATOR . $file, $variables + $this->variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,10 +2,13 @@
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
|
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/chats.css" />
|
||||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/data.css" />
|
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/data.css" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block menu %}
|
||||||
|
<section data-section="menu" data-menu="chats">
|
||||||
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|
||||||
<html lang="ru">
|
<html lang="ru" manifest="/manifest">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
{% use '/themes/default/head.html' with title as head_title, meta as head_meta, css as head_css %}
|
{% use '/themes/default/head.html' with title as head_title, meta as head_meta, css as head_css %}
|
||||||
|
|
|
@ -7,7 +7,14 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<section data-section="main">
|
<section data-section="main">
|
||||||
|
<select id="language" autocomplete="language" onselect="text.language.select(this.value)">
|
||||||
|
{% for language in languages %}
|
||||||
|
<option value="{{ language }}">{{ language }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<script>
|
||||||
|
document.getElementById('language').value = localStorage.language ?? 'english';
|
||||||
|
</script>
|
||||||
</section>
|
</section>
|
||||||
</footer>
|
</footer>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
{% block css %}
|
||||||
|
<link type="text/css" rel="stylesheet" href="/themes/default/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'}} {% for attribute, value in element.attributes %}{{ attribute }}="{{ value }}"{% endfor %}>{{ element.html|raw }}</{{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,15 @@
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
{{ main|raw }}
|
||||||
|
{% if image is not null %}<img src="{{ image.src }}" alt="{{ image.alt }}">{% endif %}
|
||||||
|
<script>
|
||||||
|
// Initialization of the element
|
||||||
|
const element = document.getElementById('{{ id }}');
|
||||||
|
|
||||||
|
if (element instanceof HTMLElement) {
|
||||||
|
// Found the element
|
||||||
|
|
||||||
|
element.addEventListener('mouseenter', () => {
|
||||||
|
console.log('{{ id }}');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -2,22 +2,33 @@
|
||||||
|
|
||||||
{% use "/themes/default/header.html" with css as header_css, body as header, js as header_js %}
|
{% use "/themes/default/header.html" with css as header_css, body as header, js as header_js %}
|
||||||
{% use "/themes/default/footer.html" with css as footer_css, body as footer, js as footer_js %}
|
{% use "/themes/default/footer.html" with css as footer_css, body as footer, js as footer_js %}
|
||||||
|
{% use '/themes/default/hotline/index.html' with css as hotline_css, body as hotline_body, js as hotline_js, js_init as
|
||||||
|
hotline_js_init %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ block('header_css') }}
|
{{ block('header_css') }}
|
||||||
{{ block('footer_css') }}
|
{{ block('footer_css') }}
|
||||||
|
{{ block('hotline_css') }}
|
||||||
|
<link type="text/css" rel="stylesheet" href="/themes/default/css/trash.css">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{{ block('header') }}
|
{{ block('header') }}
|
||||||
<section data-section="chats">
|
|
||||||
</section>
|
|
||||||
<aside>
|
<aside>
|
||||||
{% block aside %}
|
{% block aside %}
|
||||||
|
{{ block('hotline_body') }}
|
||||||
{{ aside|raw }}
|
{{ aside|raw }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</aside>
|
</aside>
|
||||||
|
{% block menu %}
|
||||||
|
{% if menu %}
|
||||||
|
{{ menu|raw }}
|
||||||
|
{% else %}
|
||||||
|
<section data-section="menu" data-menu-type="chats">
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
<main style="--sections: {{ sections ?? 1 }}">
|
<main style="--sections: {{ sections ?? 1 }}">
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{{ main|raw }}
|
{{ main|raw }}
|
||||||
|
@ -30,4 +41,6 @@
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ block('footer_js') }}
|
{{ block('footer_js') }}
|
||||||
{{ block('header_js') }}
|
{{ block('header_js') }}
|
||||||
|
{{ block('hotline_js') }}
|
||||||
|
{{ block('hotline_js_init') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script src="/js/core.js" defer></script>
|
<script src="/js/core.js" defer></script>
|
||||||
|
<script src="/js/journal.js" defer></script>
|
||||||
|
<script src="/js/damper.js" defer></script>
|
||||||
|
<script src="/js/text.js" defer></script>
|
||||||
|
<script src="/js/notifications.js" defer></script>
|
||||||
|
<script src="/js/pages/index.js" defer></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<img
|
||||||
|
style="z-index: 99999; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; pointer-events: none; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;"
|
||||||
|
src="/themes/{{ theme }}/images/ban.jpg">
|
|
@ -0,0 +1,49 @@
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
z-index: 99999;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
z-index: 100000;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: max(8rem, 5vw);
|
||||||
|
font-family: sans;
|
||||||
|
color: #fff;
|
||||||
|
<!-- backdrop-filter: contrast(500%) hue-rotate(var(--rotate)); -->
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unselectable {
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<img class="unselectable" src="/themes/{{ theme }}/images/offline.jpg">
|
||||||
|
<h1 class="unselectable">{{ title ?? '🪦'}}</h1>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const title = document.getElementsByTagName('h1')[0];
|
||||||
|
|
||||||
|
let rotate = 0;
|
||||||
|
|
||||||
|
setInterval(() => title.style.setProperty('--rotate', (rotate += 5) + 'deg'), 10)
|
||||||
|
</script>
|
|
@ -1,23 +1,93 @@
|
||||||
<section data-section="servers" class="animation uprise">
|
<search class="animation uprise">
|
||||||
<search>
|
<label class="{% if current %}active{% else %}empty{% endif %}">
|
||||||
<label>
|
|
||||||
<i class="icon data"></i>
|
<i class="icon data"></i>
|
||||||
<input class="" name="server" type="text" placeholder="Сервер" list="servers" {% if current and current.domain %}
|
<input
|
||||||
value="{{ current.domain }}" {% endif %}onkeypress="localStorage.server_domain = this.value"
|
class=""
|
||||||
onselect="localStorage.server_domain = this.value" onchange="chats.server.select(this.value)" autofocus="true">
|
name="server"
|
||||||
<datalist id="servers">
|
type="text"
|
||||||
|
placeholder="Сервер"
|
||||||
|
list="servers"
|
||||||
|
maxlength="120"
|
||||||
|
spellcheck="false"
|
||||||
|
{% if current and current.ip and current.port %} value="{{ current.ip }}:{{ current.port }}" {% endif %}
|
||||||
|
onkeyup="chats.server.select(this.value)"
|
||||||
|
onchange="chats.server.select(this.value, true)"
|
||||||
|
{% if not current %} autofocus="true" {% endif %}
|
||||||
|
>
|
||||||
|
<!-- <datalist id="servers">
|
||||||
{% for server in servers %}
|
{% for server in servers %}
|
||||||
<option value="{{ server.domain }}">{{ server.domain }}{% if server.description %} {{
|
<option value="{{ server.ip }}:{{ server.port }}">{{ server.domain }}: {{ server.ip }}:{{ server.port }}</option>
|
||||||
server.description
|
|
||||||
}}{% endif %}</option>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</datalist>
|
</datalist> -->
|
||||||
</label>
|
</label>
|
||||||
<output>
|
<output class="animation slide-down" style="--animation-height: 64px">
|
||||||
<figure>
|
<figure>
|
||||||
<figcaption data-server="ip">{% if current %}{{ current.ip }}:{{ current.port }}{% endif %}</figcaption>
|
<figcaption data-server="domain">{% if current %}{{ current.domain }}{% endif %}</figcaption>
|
||||||
<span data-server="description" class="animation marquee">{{ current.description }}</span>
|
<pre data-server="description">{{ current.description }}</pre>
|
||||||
</figure>
|
</figure>
|
||||||
</output>
|
</output>
|
||||||
</search>
|
{% for image in current.images|slice(0,10)%}
|
||||||
</section>
|
<style>
|
||||||
|
section[data-section="menu"][data-menu="chats"]>search>img[data-server="image"]:nth-child({{ loop.index }}) {
|
||||||
|
z-index: 5{{ loop.index }};
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"][data-menu-status="connected"]>search>img[data-server="image"]:nth-of-type({{ loop.index }}):not(.animation) {
|
||||||
|
{{ image.connected.css }}
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"]:is([data-menu-status="disconnected"], [data-menu-status="connecting"])>search>img[data-server="image"]:nth-of-type({{ loop.index }}):not(.animation) {
|
||||||
|
{{ image.disconnected.css }}
|
||||||
|
}
|
||||||
|
|
||||||
|
section[data-section="menu"][data-menu="chats"]:not([data-menu-status="disconnected"], [data-menu-status="connected"])>search>img[data-server="image"]:nth-of-type({{ loop.index }}) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
{% if image.animations %}
|
||||||
|
{% if image.animations.connected %}
|
||||||
|
@keyframes server-image-{{ loop.index }}-connected {
|
||||||
|
{{ image.animations.connected.keyframes }} /* нужен фильтр */
|
||||||
|
}
|
||||||
|
section[data-section="menu"][data-menu="chats"][data-menu-status="connected"]>search>img[data-server="image"]:nth-of-type({{ loop.index }}).animation {
|
||||||
|
{{ image.animations.connected.css }}
|
||||||
|
animation-name: server-image-{{ loop.index }}-connected;
|
||||||
|
{% if image.animations.connected.duration %}animation-duration: {{ image.animations.connected.duration }}s;{% endif %}
|
||||||
|
{% if image.animations.connected.fill.mode %}animation-fill-mode: {{ image.animations.connected.fill.mode }};{% endif %}
|
||||||
|
{% if image.animations.connected.timing.function %}animation-timing-function: {{ image.animations.connected.timing.function }};{% endif %}
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if image.animations.disconnected %}
|
||||||
|
@keyframes server-image-{{ loop.index }}-disconnected {
|
||||||
|
{{ image.animations.disconnected.keyframes }} /* нужен фильтр */
|
||||||
|
}
|
||||||
|
section[data-section="menu"][data-menu="chats"]:is([data-menu-status="disconnected"], [data-menu-status="connecting"])>search>img[data-server="image"]:nth-of-type({{ loop.index }}).animation {
|
||||||
|
{{ image.animations.disconnected.css }}
|
||||||
|
animation-name: server-image-{{ loop.index }}-disconnected;
|
||||||
|
{% if image.animations.disconnected.duration %}animation-duration: {{ image.animations.disconnected.duration }}s;{% endif %}
|
||||||
|
{% if image.animations.disconnected.fill.mode %}animation-fill-mode: {{ image.animations.disconnected.fill.mode }};{% endif %}
|
||||||
|
{% if image.animations.disconnected.timing.function %}animation-timing-function: {{ image.animations.disconnected.timing.function }};{% endif %}
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</style>
|
||||||
|
{% if image.url %}
|
||||||
|
<img
|
||||||
|
data-server="image"
|
||||||
|
class="unselectable untouchable animation"
|
||||||
|
style="{% if image.style %} {{ image.style }}{% endif %}"
|
||||||
|
src="{{ image.url }}"
|
||||||
|
alt="{{ image.description }}"
|
||||||
|
loading="eager"
|
||||||
|
decoding="async"
|
||||||
|
importance="high"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
data-nosnippet="true"
|
||||||
|
{% if image.animations.connected.duration %}data-server-image-animations-connected-duration="{{ image.animations.connected.duration }}"{% endif %}
|
||||||
|
{% if image.animations.disconnected.duration %}data-server-image-animations-disconnected-duration="{{ image.animations.disconnected.duration }}"{% endif %}
|
||||||
|
ondragstart="return false"
|
||||||
|
/>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</search>
|
||||||
|
|
Loading…
Reference in New Issue