Compare commits

..

No commits in common. "ac7694f71661099e83e70db0c5110cd35bc9f0ca" and "5509b971483fbdcc7140de1769da6a3d6b63feab" have entirely different histories.

54 changed files with 1313 additions and 2250 deletions

View File

@ -1,62 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\controllers;
// Files of the project
use mirzaev\arming_bot\controllers\core,
mirzaev\arming_bot\models\session,
mirzaev\arming_bot\models\account as model;
// Framework for ArangoDB
use mirzaev\arangodb\document;
/**
* Controller of account
*
* @package mirzaev\arming_bot\controllers
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class account extends core
{
/**
* Registry of errors
*/
protected array $errors = [
'session' => [],
'account' => []
];
/**
* Write to the buffer
*
* @param array $parameters Parameters of the request (POST + GET)
*
* @return void
*/
public function write(array $parameters = []): void
{
if (!empty($parameters) && $this->account instanceof model) {
// Found data of the program and active account
foreach ($parameters as $name => $value) {
// Iterate over parameters
// Validation of the parameter
if (mb_strlen($value) > 4096) continue;
// Convert name to multidimensional array
foreach (array_reverse(explode('_', $name)) as $key) $parameter = [$key => $parameter ?? json_validate($value) ? json_decode($value, true, 10) : $value];
// Write data of to the buffer parameter in the implement object of account document from ArangoDB
$this->account->buffer = $parameter + $this->account->buffer ?? [];
}
// Write from implement object to account document from ArangoDB
document::update($this->account->__document(), $this->errors['account']);
}
}
}

View File

@ -6,21 +6,14 @@ namespace mirzaev\arming_bot\controllers;
// Files of the project // Files of the project
use mirzaev\arming_bot\controllers\core, use mirzaev\arming_bot\controllers\core,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\entry, mirzaev\arming_bot\models\entry,
mirzaev\arming_bot\models\category, mirzaev\arming_bot\models\category,
mirzaev\arming_bot\models\product, mirzaev\arming_bot\models\product;
mirzaev\arming_bot\models\menu;
// Library for ArangoDB
use ArangoDBClient\Document as _document;
/** /**
* Controller of catalog * Controller of catalog
* *
* @package mirzaev\arming_bot\controllers * @package mirzaev\arming_bot\controllers
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class catalog extends core final class catalog extends core
@ -31,7 +24,6 @@ final class catalog extends core
protected array $errors = [ protected array $errors = [
'session' => [], 'session' => [],
'account' => [], 'account' => [],
'menu' => [],
'catalog' => [] 'catalog' => []
]; ];
@ -42,348 +34,74 @@ final class catalog extends core
*/ */
public function index(array $parameters = []): ?string public function index(array $parameters = []): ?string
{ {
// Validating // Initializing identifier of a category
if (!empty($parameters['category']) && preg_match('/[\d]+/', $parameters['category'], $matches)) $category = (int) $matches[0]; preg_match('/[\d]+/', $parameters['identifier'] ?? '', $matches);
$identifier = $matches[0] ?? null;
if (isset($category)) { // Initializint the buffer of respnse
// Received and validated identifier of the category $html = [];
// Initialize of category if (!empty($parameters['identifier'])) {
$category = category::_read('d.identifier == @identifier', parameters: ['identifier' => $category], errors: $this->errors['catalog']); // Передана категория (идентификатор)
// Инициализация актуальной категории
$category = category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $identifier], errors: $this->errors['catalog']);
if ($category instanceof category) { if ($category instanceof category) {
// Found the category // Found the category
// Write to the response buffer // Поиск категорий или товаров входящих в актуальную категорию
$response['category'] = ['name' => $category->name ?? null];
// Search for categories that are descendants of $category
$entries = entry::search( $entries = entry::search(
document: $category, document: $category,
amount: 50, amount: 30,
errors: $this->errors['catalog'] errors: $this->errors['catalog']
); );
/* var_dump($entries); die; */
// Initialize buffers of entries (in singular, by parameter from ArangoDB) // Объявление буферов категорий и товаров (важно - в единственном числе, по параметру из базы данных)
$category = $product = []; $category = $product = [];
foreach ($entries as $entry) { foreach ($entries as $entry) {
// Iterate over entries (descendands) // Перебор вхождений
// Write entry to the buffer of entries (sort by $category and $product) // Запись массивов категорий и товаров ($category и $product) в буфер глобальной переменной шаблонизатора
${$entry->_type}[] = $entry; ${$entry->_type}[] = $entry;
} }
// Write to the buffer of global variables of view templater // Запись категорий из буфера в глобальную переменную шаблонизатора
$this->view->categories = $category ?? null; $this->view->categories = $category;
// Write to the buffer of global variables of view templater // Запись товаров из буфера в глобальную переменную шаблонизатора
$this->view->products = $product ?? null; $this->view->products = $product;
/* var_dump($this->view->products); die; */ // Generating filters
$this->view->filters = [
'brands' => product::collect(
'd.brand.' . $this->language->name,
errors: $this->errors['catalog']
)
];
// Delete buffers // Generating HTML and writing to the buffer of response
unset($category, $product); $html = [
'filters' => count($this->view->products) > 0 ? $this->view->render('catalog/elements/filters.html') : null,
'products' => $this->view->render('catalog/elements/products/2columns.html')
];
} }
} else if (!isset($parameters['category'])) {
// Not received identifie of the category
// Search for root ascendants categories
$this->view->categories = entry::ascendants(descendant: new category, errors: $this->errors['catalog']) ?? null;
}
// Validating
if (!empty($parameters['product']) && preg_match('/[\d]+/', $parameters['product'], $matches)) $product = (int) $matches[0];
if (isset($product)) {
// Received and validated identifier of the product
// Search for the product data and write to the buffer of global variables of view templater
$this->view->product = product::read(
filter: "d.identifier == @identifier && d.deleted != true && d.hidden != true",
sort: 'd.created DESC',
amount: 1,
return: '{name: d.name.@language, description: d.description.@language, cost: d.cost, weight: d.weight, dimensions: d.dimensions, brand: d.brand.@language, compatibility: d.compatibility.@language, images: d.images[*].storage}',
parameters: ['identifier' => $product],
language: $this->language,
errors: $this->errors['catalog']
)[0]?->getAll() ?? null;
}
// Intializing buffer of query parameters
$_parameters = [];
// Initializing buffer of filters query (AQL)
$_filters = 'd.deleted != true && d.hidden != true';
// Search among products in the $category
if (isset($this->view->products) && count($this->view->products) > 0) {
// Amount of rendered products is more than 0
// Write to the buffer of filters query (AQL)
$_filters .= ' && POSITION(["' . implode('", "', array_map(fn(_document $document): string => $document->getId(), $this->view->products)) . '"], d._id)';
}
// Validating
if (!empty($parameters['brand']) && preg_match('/[\w\s]+/u', urldecode($parameters['brand']), $matches)) $brand = $matches[0];
if (isset($brand)) {
// Received and validated filter by brand
// Writing to the account buffer
$this->account?->write(
[
'catalog' => [
'filters' => [
'brand' => $brand
]
]
]
);
// Writing to the session buffer
$this->session?->write(
[
'catalog' => [
'filters' => [
'brand' => $brand
]
]
]
);
// Writing to the buffer of filters query (AQL)
$_filters .= ' && d.brand.@language == @brand';
// Writing to the buffer of query parameters
$_parameters['brand'] = $brand;
} else { } else {
// Not received or not validated filter by brand // Не передана категория
// Writing to the account buffer // Поиск категорий: "categories" (самый верхний уровень)
$this->account?->write( $this->view->categories = entry::ascendants(descendant: new category, errors: $this->errors['catalog']);
[
'catalog' => [
'filters' => [
'brand' => null
]
]
]
);
// Writing to the session buffer
$this->session?->write(
[
'catalog' => [
'filters' => [
'brand' => null
]
]
]
);
}
// Initialize buffer of filters query (AQL)
$_sort = 'd.position ASC, d.name ASC, d.created DESC';
// Validating
if (!empty($parameters['sort']) && preg_match('/[\w\s]+/u', $parameters['sort'], $matches)) $sort = $matches[0];
if (isset($sort)) {
// Received and validated sort
// Writing to the account buffer
$this->account?->write(
[
'catalog' => [
'sort' => $sort
]
]
);
// Writing to the session buffer
$this->session?->write(
[
'catalog' => [
'sort' => $sort
]
]
);
// Write to the buffer of sort query (AQL)
$_sort = "d.@sort DESC, $_sort";
// Write to the buffer of query parameters
$_parameters['sort'] = $sort;
} else {
// Not received or not validated filter by brand
// Writing to the account buffer
$this->account?->write(
[
'catalog' => [
'sort' => null
]
]
);
// Writing to the session buffer
$this->session?->write(
[
'catalog' => [
'sort' => null
]
]
);
}
// Validating @todo add throwing errors
if (!empty($parameters['text']) && preg_match('/[\w\s]+/u', urldecode($parameters['text']), $matches) && mb_strlen($matches[0]) > 2) $text = $matches[0];
if (isset($text)) {
// Received and validated text
// Writing to the account buffer
$this->account?->write(
[
'catalog' => [
'search' => [
'text' => $text
]
]
]
);
// Writing to the session buffer
$this->session?->write(
[
'catalog' => [
'search' => [
'text' => $text
]
]
]
);
// Search for products and write to the buffer of global variables of view templater
$this->view->products = product::read(
search: $text ?? null,
filter: $_filters,
sort: $_sort,
amount: 50, // @todo pagination
language: $this->language,
parameters: $_parameters,
return: 'DISTINCT MERGE(d, {name: d.name.@language, description: d.description.@language, compatibility: d.compatibility.@language})',
errors: $this->errors['catalog']
);
} else {
// Not received or not validated filter by brand
// Writing to the account buffer
$this->account?->write(
[
'catalog' => [
'search' => [
'text' => null
]
]
]
);
// Writing to the session buffer
$this->session?->write(
[
'catalog' => [
'search' => [
'text' => null
]
]
]
);
}
if (isset($this->view->products) && count($this->view->products) > 0) {
// Amount of rendered products is more than 0
// Search for filters and write to the buffer of global variables of view templater
$this->view->filters = [
'brands' => product::collect(
return: 'd.brand.@language',
products: array_map(fn(_document $document): string => $document->getId(), $this->view->products),
language: $this->language,
errors: $this->errors['catalog']
)
];
}
if (isset($menu)) {
//
} else {
// Not received ... menu
// Search for filters and write to the buffer of global variables of view templater
$this->view->menu = menu::_read(
return: 'MERGE(d, { name: d.name.@language })',
sort: 'd.position ASC, d.created DESC, d._key DESC',
amount: 4,
parameters: ['language' => $this->language->name],
errors: $this->errors['menu']
);
} }
if ($_SERVER['REQUEST_METHOD'] === 'GET') { if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// GET request // GET request
if (!empty($this->view->product)) {
// Initialized the product data
// Writing javascript code that has been executed after core and damper has been loaded
$this->view->javascript = [<<<javascript
if (typeof _window === 'undefined') {
_window = setTimeout(() => core.catalog.product_system('$product'), 500);
}
javascript] + ($this->view->javascript ?? []);
}
// Exit (success) // Exit (success)
return $this->view->render('catalog/page.html'); return $this->view->render('catalog/page.html');
} else if ($_SERVER['REQUEST_METHOD'] === 'POST') { } else if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// POST request // POST request
// Initializing the buffer of response
$response = [
'title' => $this->language === language::ru ? 'Каталог' : 'Catalog' // @see https://git.mirzaev.sexy/mirzaev/huesos/issues/1
];
if (isset($this->view->categories)) {
// Initialized categories
// Render HTML-code of categories and write to the response buffer
$response['categories'] = $this->view->render('catalog/elements/categories.html');
}
if (isset($this->view->product)) {
// Initialized product
// Writing data of the product to the response buffer @todo GENERATE THIS ON THE SERVER
$response['product'] = $this->view->product;
}
if (isset($this->view->products)) {
// Initialized products
// Render HTML-code of products and write to the response buffer
$response['products'] = $this->view->render('catalog/elements/products.html');
}
if (isset($this->view->filters)) {
// Initialized filters
// Render HTML-code of filters and write to the response buffer
$response['filters'] = $this->view->render('catalog/elements/filters.html');
}
// Initializing a response headers // Initializing a response headers
header('Content-Type: application/json'); header('Content-Type: application/json');
header('Content-Encoding: none'); header('Content-Encoding: none');
@ -394,7 +112,11 @@ final class catalog extends core
// Generating the reponse // Generating the reponse
echo json_encode( echo json_encode(
$response + [ [
'title' => $title ?? '',
'html' => $html + [
'categories' => $this->view->render('catalog/elements/categories.html'),
],
'errors' => $this->errors 'errors' => $this->errors
] ]
); );
@ -413,4 +135,122 @@ final class catalog extends core
// Exit (fail) // Exit (fail)
return null; return null;
} }
/**
* Search
*
* @param array $parameters Parameters of the request (POST + GET)
*/
public function search(array $parameters = []): ?string
{
// Initializing of text fore search
preg_match('/[\w\s]+/u', $parameters['text'] ?? '', $matches);
$text = $matches[0] ?? null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// POST request
// Search for products
$this->view->products = isset($text) ? product::read(
search: $text,
filter: 'd.deleted != true && d.hidden != true',
sort: 'd.position ASC, d.name ASC, d.created DESC',
amount: 30,
language: $this->language,
errors: $this->errors['catalog']
) : [];
// Initializing a response headers
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Initializing of the output buffer
ob_start();
// Generating the reponse
echo json_encode(
[
'title' => $title ?? '',
'html' => [
'products' => $this->view->render('catalog/elements/products/2columns.html')
],
'errors' => $this->errors
]
);
// Initializing a response headers
header('Content-Length: ' . ob_get_length());
// Sending and deinitializing of the output buffer
ob_end_flush();
flush();
// Exit (success)
return null;
}
// Exit (fail)
return null;
}
/**
* Product
*
* @param array $parameters Parameters of the request (POST + GET)
*/
public function product(array $parameters = []): ?string
{
// Initializing identifier of a product
preg_match('/[\d]+/', $parameters['identifier'] ?? '', $matches);
$identifier = $matches[0] ?? null;
if (!empty($identifier)) {
// Received identifier of the product
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// POST request
// Search for products
$product = product::read(
filter: "d.identifier == @identifier && d.deleted != true && d.hidden != true",
sort: 'd.created DESC',
amount: 1,
return: '{identifier: d.identifier, name: d.name.@language, description: d.description.@language, cost: d.cost, weight: d.weight, dimensions: d.dimensions, brand: d.brand.@language, compatibility: d.compatibility.@language, images: d.images[*].storage}',
parameters: ['identifier' => (int) $identifier],
language: $this->language,
errors: $this->errors['catalog']
)[0]?->getAll();
// Initializing a response headers
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Initializing of the output buffer
ob_start();
// Generating the reponse
echo json_encode(
[
'product' => $product,
'errors' => $this->errors
]
);
// Initializing a response headers
header('Content-Length: ' . ob_get_length());
// Sending and deinitializing of the output buffer
ob_end_flush();
flush();
// Exit (success)
return null;
}
}
// Exit (fail)
return null;
}
} }

View File

@ -20,8 +20,6 @@ use mirzaev\minimal\controller;
* Core of controllers * Core of controllers
* *
* @package mirzaev\arming_bot\controllers * @package mirzaev\arming_bot\controllers
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
class core extends controller class core extends controller

View File

@ -12,8 +12,6 @@ use mirzaev\arming_bot\controllers\core,
* Index controller * Index controller
* *
* @package mirzaev\arming_bot\controllers * @package mirzaev\arming_bot\controllers
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class index extends core final class index extends core

View File

@ -16,8 +16,6 @@ use mirzaev\arangodb\document;
* Controller of session * Controller of session
* *
* @package mirzaev\arming_bot\controllers * @package mirzaev\arming_bot\controllers
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class session extends core final class session extends core
@ -155,14 +153,14 @@ final class session extends core
return null; return null;
} }
/** /**
* Write to the buffer * Connect session to the telegram account
* *
* @param array $parameters Parameters of the request (POST + GET) * @param array $parameters Parameters of the request (POST + GET)
*
* @return void
*/ */
public function write(array $parameters = []): void public function write(array $parameters = []): ?string
{ {
if (!empty($parameters) && $this->session instanceof model) { if (!empty($parameters) && $this->session instanceof model) {
// Found data of the program and active session // Found data of the program and active session
@ -173,15 +171,18 @@ final class session extends core
// Validation of the parameter // Validation of the parameter
if (mb_strlen($value) > 4096) continue; if (mb_strlen($value) > 4096) continue;
// Convert name to multidimensional array // Write data of the program to the implement object of session document from ArangoDB
foreach (array_reverse(explode('_', $name)) as $key) $parameter = [$key => $parameter ?? json_validate($value) ? json_decode($value, true, 10) : $value]; $this->session->{$name} = json_decode($value, true, 10);
// Write data of to the buffer parameter in the implement object of session document from ArangoDB
$this->session->buffer = $parameter + ($this->session->buffer ?? []);
} }
// Write from implement object to session document from ArangoDB // Write from implement object to session document from ArangoDB
document::update($this->session->__document(), $this->errors['session']); document::update($this->session->__document(), $this->errors['session']);
// Exit (success)
return null;
} }
// Exit (fail)
return null;
} }
} }

View File

@ -8,7 +8,6 @@ namespace mirzaev\arming_bot\models;
use mirzaev\arming_bot\models\core, use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\status, mirzaev\arming_bot\models\traits\status,
mirzaev\arming_bot\models\traits\document as arangodb_document_trait, mirzaev\arming_bot\models\traits\document as arangodb_document_trait,
mirzaev\arming_bot\models\traits\buffer,
mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface, mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface,
mirzaev\arming_bot\models\enumerations\language; mirzaev\arming_bot\models\enumerations\language;
@ -35,9 +34,7 @@ use exception;
*/ */
final class account extends core implements arangodb_document_interface final class account extends core implements arangodb_document_interface
{ {
use status, arangodb_document_trait, buffer { use status, arangodb_document_trait;
buffer::write as write;
}
/** /**
* Name of the collection in ArangoDB * Name of the collection in ArangoDB

View File

@ -4,18 +4,16 @@ declare(strict_types=1);
namespace mirzaev\arming_bot\models; namespace mirzaev\arming_bot\models;
// Files of the project
use mirzaev\arming_bot\models\interfaces\collection as collection_interface;
// Framework for PHP // Framework for PHP
use mirzaev\minimal\model; use mirzaev\minimal\model;
// Library for ArangoDB
use ArangoDBClient\Document as _document;
// Framework for ArangoDB // Framework for ArangoDB
use mirzaev\arangodb\connection as arangodb, use mirzaev\arangodb\connection as arangodb,
mirzaev\arangodb\collection; mirzaev\arangodb\collection,
mirzaev\arangodb\enumerations\collection\type;
// Library for ArangoDB
use ArangoDBClient\Document as _document;
// Built-in libraries // Built-in libraries
use exception; use exception;
@ -28,7 +26,7 @@ use exception;
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
class core extends model implements collection_interface class core extends model
{ {
/** /**
* Postfix for name of models files * Postfix for name of models files
@ -47,6 +45,16 @@ class core extends model implements collection_interface
*/ */
protected static arangodb $arangodb; protected static arangodb $arangodb;
/**
* Name of the collection in ArangoDB
*/
public const string COLLECTION = 'THIS_COLLECTION_SHOULD_NOT_EXIST';
/**
* Type of the collection in ArangoDB
*/
public const type TYPE = type::document;
/** /**
* Constructor of an instance * Constructor of an instance
* *
@ -150,7 +158,6 @@ class core extends model implements collection_interface
return null; return null;
} }
/** /**
* Write * Write
* *

View File

@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\models\interfaces;
// Framework for ArangoDB
use mirzaev\arangodb\enumerations\collection\type;
/**
* Interface for implementing a collection from ArangoDB
*
* @package mirzaev\arming_bot\models\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
interface collection
{
/**
* Name of the collection in ArangoDB
*/
public const string COLLECTION = 'THIS_COLLECTION_SHOULD_NOT_EXIST';
/**
* Type of the collection in ArangoDB
*/
public const type TYPE = type::document;
}

View File

@ -13,8 +13,6 @@ use ArangoDBClient\Document as _document;
* @param _document $document An instance of the ArangoDB document from ArangoDB (protected readonly) * @param _document $document An instance of the ArangoDB document from ArangoDB (protected readonly)
* *
* @package mirzaev\arming_bot\models\traits * @package mirzaev\arming_bot\models\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
interface document interface document

View File

@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\document as arangodb_document_trait,
mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface;
/**
* Model of menu
*
* @package mirzaev\arming_bot\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class menu extends core implements arangodb_document_interface
{
use arangodb_document_trait;
/**
* Name of the collection in ArangoDB
*/
final public const string COLLECTION = 'menu';
}

View File

@ -122,7 +122,7 @@ final class product extends core
* @param array $parameters Binded parameters for placeholders ['placeholder' => parameter] * @param array $parameters Binded parameters for placeholders ['placeholder' => parameter]
* @param array &$errors Registry of errors * @param array &$errors Registry of errors
* *
* @return array Found products (can be empty) * @return array Массив с найденными товарами (может быть пустым)
*/ */
public static function read( public static function read(
?string $search = null, ?string $search = null,
@ -130,7 +130,7 @@ final class product extends core
?string $sort = 'd.position ASC, d.created DESC', ?string $sort = 'd.position ASC, d.created DESC',
int $page = 1, int $page = 1,
int $amount = 100, int $amount = 100,
?string $return = 'DISTINCT d', ?string $return = 'd',
language $language = language::en, language $language = language::en,
array $parameters = [], array $parameters = [],
array &$errors = [] array &$errors = []
@ -163,22 +163,22 @@ final class product extends core
1, 1,
false false
) OR ) OR
LEVENSHTEIN_MATCH( levenshtein_match(
d.description.@language, d.description.@language,
TOKENS(@search, @analyzer)[0], tokens(@search, @analyzer)[0],
1, 1,
false false
) OR ) OR
LEVENSHTEIN_MATCH( levenshtein_match(
d.compatibility.@language, d.compatibility.@language,
TOKENS(@search, @analyzer)[0], tokens(@search, @analyzer)[0],
1, 1,
false false
) )
AQL, AQL,
empty($filter) ? '' : "FILTER $filter", empty($filter) ? '' : "FILTER $filter",
empty($search) ? (empty($sort) ? '' : "SORT $sort") : (empty($sort) ? "SORT BM25(d) DESC" : "SORT BM25(d) DESC, $sort"), empty($search) ? (empty($sort) ? '' : "SORT $sort") : (empty($sort) ? "SORT BM25(d) DESC" : "SORT BM25(d) DESC, $sort"),
empty($return) ? 'DISTINCT d' : $return empty($return) ? 'd' : $return
), ),
[ [
'@collection' => empty($search) ? static::COLLECTION : static::COLLECTION . 's_search', '@collection' => empty($search) ? static::COLLECTION : static::COLLECTION . 's_search',
@ -214,16 +214,12 @@ final class product extends core
* Collect parameter from all products * Collect parameter from all products
* *
* @param string $return Return (AQL path) * @param string $return Return (AQL path)
* @param array $products Array with products system identifiers ["_id", "_id", "_id"...]
* @param array &$errors Registry of errors * @param array &$errors Registry of errors
* *
* @return array Array with found unique parameter values from all products (can be empty) * @return array Array with found unique parameter values from all products (can be empty)
*/ */
public static function collect( public static function collect(
string $return = 'd._key', string $return = 'd._key',
array $products = [],
language $language = language::en,
array $parameters = [],
array &$errors = [] array &$errors = []
): array { ): array {
try { try {
@ -234,16 +230,13 @@ final class product extends core
sprintf( sprintf(
<<<'AQL' <<<'AQL'
FOR d IN @@collection FOR d IN @@collection
%s
RETURN DISTINCT %s RETURN DISTINCT %s
AQL, AQL,
empty($products) ? '' : 'FILTER POSITION(["' . implode('", "', $products) . '"], d._id)',
empty($return) ? 'd._key' : $return empty($return) ? 'd._key' : $return
), ),
[ [
'@collection' => static::COLLECTION, '@collection' => static::COLLECTION,
'language' => $language->name, ],
] + $parameters,
errors: $errors errors: $errors
)) { )) {
// Found parameters // Found parameters

View File

@ -10,7 +10,6 @@ use mirzaev\arming_bot\models\account,
mirzaev\arming_bot\models\enumerations\session as verification, mirzaev\arming_bot\models\enumerations\session as verification,
mirzaev\arming_bot\models\traits\status, mirzaev\arming_bot\models\traits\status,
mirzaev\arming_bot\models\traits\document as arangodb_document_trait, mirzaev\arming_bot\models\traits\document as arangodb_document_trait,
mirzaev\arming_bot\models\traits\buffer,
mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface, mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface,
mirzaev\arming_bot\models\enumerations\language; mirzaev\arming_bot\models\enumerations\language;
@ -34,9 +33,7 @@ use exception;
*/ */
final class session extends core implements arangodb_document_interface final class session extends core implements arangodb_document_interface
{ {
use status, arangodb_document_trait, buffer { use status, arangodb_document_trait;
buffer::write as write;
}
/** /**
* Name of the collection in ArangoDB * Name of the collection in ArangoDB
@ -46,7 +43,7 @@ final class session extends core implements arangodb_document_interface
/** /**
* Type of sessions verification * Type of sessions verification
*/ */
final public const verification VERIFICATION = verification::hash_else_address; public const verification VERIFICATION = verification::hash_else_address;
/** /**
* Constructor of instance * Constructor of instance
@ -328,4 +325,44 @@ final class session extends core implements arangodb_document_interface
// Exit (fail) // Exit (fail)
return null; return null;
} }
/**
* Write to buffer of the session
*
* @param array $data Data for merging
* @param array &$errors Registry of errors
*
* @return bool Is data has written into the session document from ArangoDB?
*/
public function write(array $data, array &$errors = []): bool
{
try {
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
// Initialized the collection
// The instance of the session document from ArangoDB is initialized?
isset($this->document) || throw new exception('The instance of the sessoin document from ArangoDB is not initialized');
// Writing data into buffer of the instance of the session document from ArangoDB
$this->document->buffer = array_replace_recursive(
$this->document->buffer ?? [],
[$_SERVER['INTERFACE'] => array_replace_recursive($this->document->buffer[$_SERVER['INTERFACE']] ?? [], $data)]
);
// Writing to ArangoDB and exit (success)
return document::update($this->document, errors: $errors);
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Exit (fail)
return false;
}
} }

View File

@ -1,71 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\models\traits;
// Files of the project
use mirzaev\arming_bot\models\interfaces\collection as collection_interface;
// Library for ArangoDB
use ArangoDBClient\Document as _document;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
mirzaev\arangodb\document;
// Built-in libraries
use exception;
/**
* Trait for buffer
*
* @uses collection_interface
*
* @param static COLLECTION Name of the collection in ArangoDB
* @param static TYPE Type of the collection in ArangoDB
*
* @package mirzaev\arming_bot\models\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
trait buffer
{
/**
* Write to buffer of the session
*
* @param array $data Data for writing (merge)
* @param array &$errors Registry of errors
*
* @return bool Is data has written into the session document from ArangoDB?
*/
public function write(array $data, array &$errors = []): bool
{
try {
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
// Initialized the collection
// The instance of the session document from ArangoDB is initialized?
isset($this->document) || throw new exception('The instance of the sessoin document from ArangoDB is not initialized');
// Writing data into buffer of the instance of the session document from ArangoDB
$this->document->buffer = array_replace_recursive($this->document->buffer ?? [], $data);
// Writing to ArangoDB and exit (success)
return document::update($this->document, errors: $errors);
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Exit (fail)
return false;
}
}

View File

@ -22,8 +22,6 @@ use exception;
* @var protected readonly _document|null $document An instance of the ArangoDB document * @var protected readonly _document|null $document An instance of the ArangoDB document
* *
* @package mirzaev\arming_bot\models\traits * @package mirzaev\arming_bot\models\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
trait document trait document

View File

@ -11,8 +11,6 @@ use exception;
* Trait for initialization of files handlers * Trait for initialization of files handlers
* *
* @package mirzaev\arming_bot\models\traits * @package mirzaev\arming_bot\models\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
trait files trait files

View File

@ -11,8 +11,6 @@ use exception;
* Trait for initialization of a status * Trait for initialization of a status
* *
* @package mirzaev\arming_bot\models\traits * @package mirzaev\arming_bot\models\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
trait status trait status

View File

@ -11,8 +11,6 @@ use exception;
* Trait for "Yandex Disk" * Trait for "Yandex Disk"
* *
* @package mirzaev\arming_bot\models\traits\yandex * @package mirzaev\arming_bot\models\traits\yandex
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
trait disk trait disk

View File

@ -39,14 +39,12 @@ $router = new router;
// Initialize routes // Initialize routes
$router $router
->write('/', 'catalog', 'index', 'GET') ->write('/', 'catalog', 'index', 'GET')
->write('/', 'catalog', 'index', 'POST') ->write('/search', 'catalog', 'search', 'POST')
->write('/account/write', 'account', 'write', 'POST')
->write('/session/write', 'session', 'write', 'POST') ->write('/session/write', 'session', 'write', 'POST')
->write('/session/connect/telegram', 'session', 'telegram', 'POST') ->write('/session/connect/telegram', 'session', 'telegram', 'POST')
/* ->write('/category/$identifier', 'catalog', 'index', 'POST') */ ->write('/category/$identifier', 'catalog', 'index', 'POST')
/* ->write('/category', 'catalog', 'index', 'POST') */ ->write('/category', 'catalog', 'index', 'POST')
/* ->write('/product/$identifier', 'catalog', 'product', 'POST') */ ->write('/product/$identifier', 'catalog', 'product', 'POST');
;
/* /*

View File

@ -24,17 +24,7 @@ import("/js/core.js").then(() =>
if (typeof core.account === "undefined") { if (typeof core.account === "undefined") {
// Not initialized // Not initialized
/** // Write to the core
* @name Account
*
* @description
* Implements actions with accounts
*
* @memberof core
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
core.account = class account { core.account = class account {
// Wrap of indicator of the account // Wrap of indicator of the account
static wrap = document.getElementById("account"); static wrap = document.getElementById("account");
@ -82,11 +72,10 @@ import("/js/core.js").then(() =>
}, 3000); }, 3000);
if (core.telegram.api.initData.length > 0) { if (core.telegram.api.initData.length > 0) {
core core.request(
.request( "/session/connect/telegram",
"/session/connect/telegram", core.telegram.api.initData,
core.telegram.api.initData, )
)
.then((json) => { .then((json) => {
if ( if (
json.errors !== null && json.errors !== null &&
@ -120,34 +109,6 @@ import("/js/core.js").then(() =>
}); });
} }
} }
/**
* Buffer
*/
static buffer = class buffer {
/**
* Write to the account buffer
*
* @param {string} name Name of the parameter
* @param {string} value Value of the parameter (it can be JSON)
*
* @return {void}
*/
static write(name, value) {
if (typeof name === "string" && typeof value === "string") {
//
// Send request to the server
core.request(
"/account/write",
`${name}=${value}`,
"POST",
{},
null,
);
}
}
};
}; };
} }
} }

View File

@ -23,20 +23,10 @@ import("/js/core.js").then(() =>
}, 5000); }, 5000);
function initialization() { function initialization() {
// core.session.telegram();
const { initData, initDataUnsafe, ...data } = core.telegram.api;
//
core.session.buffer.write("telegram_program", JSON.stringify(data));
if (core.telegram.api.initData.length > 0) { if (core.telegram.api.initData.length > 0) {
//
//
core.account.authentication(); core.account.authentication();
} }
//
core.telegram.api.ready(); core.telegram.api.ready();
} }
}) })

View File

@ -22,17 +22,7 @@ import("/js/core.js").then(() =>
if (typeof core.cart === "undefined") { if (typeof core.cart === "undefined") {
// Not initialized // Not initialized
/** // Write to the core
* @name Cart
*
* @description
* Implements actions with cart
*
* @memberof core
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
core.cart = class cart { core.cart = class cart {
/** /**
* Products in cart ["product/148181", "product/148181", "product/148181"...] * Products in cart ["product/148181", "product/148181", "product/148181"...]

File diff suppressed because it is too large Load Diff

View File

@ -22,17 +22,7 @@ import("/js/core.js").then(() =>
if (typeof core.connection === "undefined") { if (typeof core.connection === "undefined") {
// Not initialized // Not initialized
/** // Write to the core
* @name Connection
*
* @description
* Implements actions with websocket connection
*
* @memberof core
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
core.connection = class connection { core.connection = class connection {
// Wrap of indicator of the connection // Wrap of indicator of the connection
static wrap = document.getElementById("connection"); static wrap = document.getElementById("connection");
@ -215,6 +205,7 @@ import("/js/core.js").then(() =>
}; };
} }
core.connection.connect( core.connection.connect(
3000, 3000,
() => () =>

View File

@ -1,14 +1,6 @@
"use strict"; "use strict";
/** // Initialize of the class in global namespace
* @name Core
*
* @description
* Core of the project
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
const core = class core { const core = class core {
// Domain // Domain
static domain = window.location.hostname; static domain = window.location.hostname;
@ -16,34 +8,31 @@ const core = class core {
// Language // Language
static language = "ru"; static language = "ru";
// Window // Label for the "loading" element
static window;
// The "loading" element
static status_loading = document.getElementById("loading"); static status_loading = document.getElementById("loading");
// The "account" element // Label for the "account" element
static status_account = document.getElementById("account"); static status_account = document.getElementById("account");
// The <header> element // Label for the <header> element
static header = document.body.getElementsByTagName("header")[0]; static header = document.body.getElementsByTagName("header")[0];
// The <aside> element // Label for the <aside> element
static aside = document.body.getElementsByTagName("aside")[0]; static aside = document.body.getElementsByTagName("aside")[0];
// The <menu> element // Label for the "menu" element
static menu = document.body.getElementsByTagName("menu")[0]; static menu = document.body.querySelector("section[data-section='menu']");
// The <main> element // Label for the <main> element
static main = document.body.getElementsByTagName("main")[0]; static main = document.body.getElementsByTagName("main")[0];
// The <footer> element // Label for the <footer> element
static footer = document.body.getElementsByTagName("footer")[0]; static footer = document.body.getElementsByTagName("footer")[0];
/** /**
* Request * Request
* *
* @param {string} uri * @param {string} address
* @param {string} body * @param {string} body
* @param {string} method POST, GET... * @param {string} method POST, GET...
* @param {object} headers * @param {object} headers
@ -52,48 +41,18 @@ const core = class core {
* @return {Promise} * @return {Promise}
*/ */
static async request( static async request(
uri = "/", address = "/",
body, body,
method = "POST", method = "POST",
headers = { headers = {
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
}, },
type = "json", type = "json",
) { ) {
return await fetch(encodeURI(uri), { method, headers, body }) return await fetch(encodeURI(address), { method, headers, body })
.then((response) => type === null || response[type]()); .then((response) => response[type]());
} }
/**
* Buffer
*/
static buffer = class buffer {
/**
* Write to buffers
*
* @param {string} name Name of the parameter
* @param {string} value Value of the parameter (it can be JSON)
*
* @return {void}
*/
static write(name, value) {
if (typeof this.session === "function") {
// Initialized the session implement object
// Write to the session buffer
core.session.buffer.write(name, value);
}
if (typeof this.account === "function") {
// Initialized the account implement object
// Write to the account buffer
core.account.buffer.write(name, value);
}
}
};
/** /**
* Сгенерировать окно выбора действия * Сгенерировать окно выбора действия
* *

View File

@ -19,19 +19,14 @@ import("/js/core.js").then(() => {
// Not initialized // Not initialized
/** /**
* @name Damper * Damper
*
* @description
* Execute multiple "function" calls in a "timeout" amount of time just once
* *
* @param {function} function Function to execute after damping * @param {function} function Function to execute after damping
* @param {number} timeout Timer in milliseconds (ms) * @param {number} timeout Timer in milliseconds (ms)
* @param {number} force Argument number storing the status of enforcement execution (see @example) * @param {number} force Argument number storing the status of enforcement execution (see @example)
* *
* @memberof core
*
* @example * @example
* a = damper( * $a = damper(
* async ( * async (
* a, // 0 * a, // 0
* b, // 1 * b, // 1
@ -45,10 +40,9 @@ import("/js/core.js").then(() => {
* 3, // 3 -> "force" argument * 3, // 3 -> "force" argument
* ); * );
* *
* a('for a', 'for b', 'for c', true, 'for d'); // Force execute is enabled * $a('for a', 'for b', 'for c', true, 'for d'); // Force execute is enabled
* *
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @return {void}
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
core.damper = (func, timeout = 300, force) => { core.damper = (func, timeout = 300, force) => {
// Initializing of the timer // Initializing of the timer
@ -66,7 +60,9 @@ import("/js/core.js").then(() => {
// Normal execution // Normal execution
// Execute the handled function (entry into recursion) // Execute the handled function (entry into recursion)
timer = setTimeout(() => func.apply(this, args), timeout); timer = setTimeout(() => {
func.apply(this, args);
}, timeout);
} }
}; };
}; };

View File

@ -19,7 +19,7 @@ import("/js/core.js").then(() => {
// Not initialized // Not initialized
/** /**
* @name Бегущая строка * Бегущая строка
* *
* @description * @description
* Простой, но мощный класс для создания бегущих строк. Поддерживает * Простой, но мощный класс для создания бегущих строк. Поддерживает
@ -31,8 +31,6 @@ import("/js/core.js").then(() => {
* события при выбранных действиях для того, чтобы пользователь имел возможность * события при выбранных действиях для того, чтобы пользователь имел возможность
* дорабатывать функционал без изучения и изменения моего кода * дорабатывать функционал без изучения и изменения моего кода
* *
* @memberof core
*
* @example * @example
* сonst hotline = new hotline(); * сonst hotline = new hotline();
* hotline.step = '-5'; * hotline.step = '-5';
@ -43,7 +41,7 @@ import("/js/core.js").then(() => {
* Сейчас при БЫСТРОМ прокручивании можно заметит как элементы "появляются" в начале и конце строки. * Сейчас при БЫСТРОМ прокручивании можно заметит как элементы "появляются" в начале и конце строки.
* 2. "gap" and "padding" in wrap should be removed! or added here to the calculations * 2. "gap" and "padding" in wrap should be removed! or added here to the calculations
* *
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @copyright WTFPL
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
core.hotline = class hotline { core.hotline = class hotline {

View File

@ -1,265 +0,0 @@
"use strict";
// Import dependencies
import("/js/core.js").then(() =>
import("/js/damper.js").then(() => {
const dependencies = setInterval(() => {
if (typeof core === "function" && typeof core.damper === "function") {
clearInterval(dependencies);
clearTimeout(timeout);
initialization();
}
}, 10);
const timeout = setTimeout(() => {
clearInterval(dependencies);
initialization();
}, 5000);
function initialization() {
if (typeof core.loader === "undefined") {
// Not initialized
/**
* @name Loader
*
* @description
* Implements actions with loading and rendering content
*
* @memberof core
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
core.loader = class loader {
/**
* Load
*
* @param {string} uri
* @param {string} body
*
* @return {Promise}
*/
static async load(uri = "/", body) {
if (typeof uri === "string") {
// Received and validated uri
return await core
.request(
uri,
body,
"POST",
{
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
"json"
)
.then((json) => {
if (
json.errors !== null &&
typeof json.errors === "object" &&
json.errors.length > 0
) {
// Fail (received errors)
} else {
// Success (not received errors)
// Writing to the browser history
history.pushState({}, json.title ?? uri, uri);
/**
* The <title>
*
* The title of the page
*/
if (
typeof json.title === "string" &&
json.title.length > 0
) {
// Received text for the <title> of the page
// Search for the <title> element (document.title)
const title = document.getElementsByTagName("title")[0];
if (title instanceof HTMLElement) {
// Found the <title> element
// Writing into the <title> element
title.innerText = json.title;
} else {
// Not found the <title> element
// Initialize the <title> element
const title = document.createElement("title");
// Inititalize the <head> element (document.head)
const head = document.getElementsByTagName("head")[0];
if (head instanceof HTMLElement) {
// Found the <head> element
// Writing the <title> element into the <head> element
head.appendChild(title);
}
// Writing title into the <title> element
title.innerText = json.title;
}
}
/**
* The <header> element
*/
if (
typeof json.header === "string" &&
json.header.length > 0
) {
// Received and validated the header HTML-code
if (core.header instanceof HTMLElement) {
// Found the <header> element
// Writing into the <header> element
core.header.outerHTML = json.header;
// Reinitializing the parameter with the <header> element
core.header =
document.getElementsByTagName("header")[0];
} else {
// Not found the <header> element
// Initialize the <header> element
core.header = document.createElement("header");
// Inititalize the <body> element (document.body)
const body = document.getElementsByTagName("body")[0];
if (body instanceof HTMLElement) {
// Found the <body> element
if (core.main instanceof HTMLElement) {
// Found the <main> element
// Writing the <header> element before the <main> element
body.insertBefore(core.header, core.main);
} else if (core.footer instanceof HTMLElement) {
// Fount the <footer> element
// Writing the <header> element before the <footer> element
body.insertBefore(core.header, core.footer);
} else {
// Not found the <main> element and the <footer> element
// Search for the last <section> element inside the <body> element
const section = document.body.querySelector(
"body > section:last-of-type"
);
if (section instanceof HTMLElement) {
// Found the last <section> element inside the <body> element
// Writing the <header> element after the last <section> element inside the <body> element
body.insertBefore(
core.header,
section.nextSibling
);
} else {
// Not found section elements <section> inside the <body> element
// Writing the <header> element into the <body> element
body.appendChild(core.header);
}
}
// Writing into the <header> element
core.header.outerHTML = json.header;
// Reinitializing the parameter with the <header> element
core.header =
document.getElementsByTagName("header")[0];
}
}
}
/**
* The <main> element
*
* The main content of the page
*/
if (typeof json.main === "string" && json.main.length > 0) {
// Received and validated the <main> HTML-code
if (core.main instanceof HTMLElement) {
// Found the <main> element
// Writing into the <main> element
core.main.outerHTML = json.main;
// Reinitializing the parameter with the <main> element
core.main = document.getElementsByTagName("main")[0];
} else {
// Not found the <main> element
// Initialize the <main> element
core.main = document.createElement("main");
// Inititalize the <body> element (document.body)
const body = document.getElementsByTagName("body")[0];
if (body instanceof HTMLElement) {
// Found the <body> element
if (core.header instanceof HTMLElement) {
// Found the <header> element
// Writing the <main> element after the <header> element
body.insertBefore(
core.main,
core.header.nextSibling
);
} else if (core.footer instanceof HTMLElement) {
// Fount the <footer> element
// Writing the <main> element before the <footer> element
body.insertBefore(core.main, core.footer);
} else {
// Not found the <header> element and the <footer> element
// Search for the last <section> element inside the <body> element
const section = document.body.querySelector(
"body > section:last-of-type"
);
if (section instanceof HTMLElement) {
// Found the last <section> element inside the <body> element
// Writing the <main> element after the last <section> element inside the <body> element
body.insertBefore(core.main, section.nextSibling);
} else {
// Not found section elements <section> inside the <body> element
// Writing the <main> element into the <body> element
body.appendChild(core.main);
}
}
// Writing into the <main> element
core.main.outerHTML = json.main;
// Reinitializing the parameter with the <main> element
core.main = document.getElementsByTagName("main")[0];
}
}
}
// Exit (success)
return json;
}
});
}
}
};
}
}
})
);

View File

@ -24,45 +24,22 @@ import("/js/core.js").then(() =>
if (typeof core.session === "undefined") { if (typeof core.session === "undefined") {
// Not initialized // Not initialized
/** // Write to the core
* @name Session
*
* @description
* Implements actions with sessions
*
* @memberof core
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
core.session = class session { core.session = class session {
/** /**
* Buffer * Send data of Telegram program settings to the session
*
* @return {void}
*/ */
static buffer = class buffer { static telegram() {
/** //
* Write to the session buffer const { initData, initDataUnsafe, ...data } = core.telegram.api;
*
* @param {string} name Name of the parameter
* @param {string} value Value of the parameter (it can be JSON)
*
* @return {void}
*/
static write(name, value) {
if (typeof name === "string" && typeof value === "string") {
//
// Send request to the server core.request(
core.request( "/session/write",
"/session/write", "telegram=" + JSON.stringify(data),
`${name}=${value}`, );
"POST", }
{},
null,
);
}
}
};
}; };
} }
} }

View File

@ -22,20 +22,10 @@ import("/js/core.js").then(() =>
if (typeof core.telegram === "undefined") { if (typeof core.telegram === "undefined") {
// Not initialized // Not initialized
/** // Write to the core
* @name Telegram
*
* @description
* Implements actions with data of the telegram account
*
* @memberof core
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
core.telegram = class telegram { core.telegram = class telegram {
/** /**
* Telegram "WebApp" API * Telegram WebApp API
* *
* @see {@link https://core.telegram.org/bots/webapps#initializing-mini-apps} * @see {@link https://core.telegram.org/bots/webapps#initializing-mini-apps}
*/ */

View File

@ -1,149 +0,0 @@
@charset "UTF-8";
main>section:is(#products, #categories) {
width: var(--width);
display: flex;
flex-flow: row wrap;
gap: var(--gap, 5px);
}
main>section#categories>a.category[type="button"] {
--padding: 0.7rem;
position: relative;
height: 23px;
padding: var(--padding);
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
overflow: hidden;
border-radius: 0.75rem;
color: var(--tg-theme-button-text-color);
background-color: var(--tg-theme-button-color);
}
main>section#categories:last-child {
/* margin-bottom: unset; */
}
main>section#categories>a.category[type="button"]:has(>img) {
min-width: calc(50% - var(--padding) * 2);
height: 180px;
padding: unset;
}
main>section#categories>a.category[type="button"]>img {
position: absolute;
left: -5%;
top: -5%;
width: 110%;
height: 110%;
object-fit: cover;
/* filter: blur(1px); */
filter: brightness(60%);
}
main>section#categories>a.category[type="button"]:is(:hover, :focus)>img {
filter: unset;
}
main>section#categories>a.category[type="button"]:has(>img)>p {
position: absolute;
left: var(--padding);
bottom: var(--padding);
right: var(--padding);
margin: unset;
width: min-content;
border-radius: 0.75rem;
background: var(--tg-theme-secondary-bg-color);
}
main>section#categories>a.category[type="button"]>p {
z-index: 100;
padding: 8px 16px;
}
main>section#filters {
width: var(--width);
max-height: 2.5rem;
display: flex;
align-items: start;
}
main>section#products {
--column: calc((100% - var(--gap)) / 2);
width: var(--width);
display: grid;
grid-gap: var(--gap);
grid-template-columns: repeat(2, var(--column));
grid-auto-flow: row dense;
}
main>section#products>div.column {
width: 100%;
display: flex;
flex-direction: column;
gap: var(--gap);
}
main>section#products>div.column>article.product {
position: relative;
width: 100%;
display: flex;
flex-grow: 0;
flex-direction: column;
border-radius: 0.75rem;
overflow: clip;
backdrop-filter: brightness(0.7);
cursor: pointer;
}
main>section#products>div.column>article.product:is(:hover, :focus) {
/* flex-grow: 0.1; */
/* background-color: var(--tg-theme-secondary-bg-color); */
}
main>section#products>div.column>article.product:is(:hover, :focus)>* {
transition: 0s;
}
main>section#products>div.column>article.product:not(:is(:hover, :focus))>* {
transition: 0.2s ease-out;
}
main>section#products>div.column>article.product>a {
display: contents;
}
main>section#products>div.column>article.product>a>img:first-of-type {
width: 100%;
height: 100%;
}
main>section#products>div.column>article.product>a>img:first-of-type+* {
margin-top: auto;
}
main>section#products>div.column>article.product>a>p.title {
z-index: 50;
margin: unset;
padding: 4px 8px;
font-size: 0.9rem;
font-weight: bold;
overflow-wrap: anywhere;
hyphens: auto;
color: var(--tg-theme-text-color);
background-color: var(--tg-theme-secondary-bg-color);
}
main>section#products>div.column>article.product>button:last-of-type {
z-index: 100;
height: 33px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: var(--tg-theme-button-text-color);
background-color: var(--tg-theme-button-color);
}

View File

@ -0,0 +1,160 @@
@charset "UTF-8";
main>section[data-section="catalog"] {
width: var(--width);
height: 100%;
display: flex;
flex-flow: row wrap;
gap: var(--gap, 5px);
/* justify-content: space-between; */
/* justify-content: space-evenly; */
/* background-color: var(--tg-theme-secondary-bg-color); */
}
main>section[data-section="catalog"]:last-child {
margin-bottom: unset;
}
main>section[data-section="catalog"]>a.category[type="button"] {
height: 23px;
padding: 8px 16px;
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
border-radius: 0.75rem;
color: var(--tg-theme-button-text-color);
background-color: var(--tg-theme-button-color);
}
main>section[data-section="catalog"]>article.product {
/* --product-height: 200px; */
--product-height: 220px;
--title-font-size: 0.9rem;
--title-height: 1.5rem;
--button-height: 33px;
position: relative;
/* width: calc((100% - var(--gap) * 2) / 3); */
width: calc((100% - var(--gap)) / 2);
height: var(--product-height);
display: flex;
flex-grow: 0;
flex-direction: column;
white-space: nowrap;
border-radius: 0.75rem;
overflow: clip;
backdrop-filter: brightness(0.7);
cursor: pointer;
}
main>section[data-section="catalog"]>article.product:hover {
/* flex-grow: 0.1; */
/* background-color: var(--tg-theme-secondary-bg-color); */
}
main>section[data-section="catalog"]>article.product:hover>* {
transition: 0s;
}
main>section[data-section="catalog"]>article.product:not(:hover)>* {
transition: 0.2s ease-out;
}
main>section[data-section="catalog"]>article.product>img:first-of-type {
z-index: -50;
position: absolute;
bottom: var(--button-height);
/* bottom: calc(var(--button-height) + var(--title-height)); */
width: 100%;
/* height: 100%; */
height: calc(var(--product-height) - var(--button-height));
object-fit: cover;
}
main>section[data-section="catalog"]>article.product>img:first-of-type+* {
margin-top: auto;
}
main>section[data-section="catalog"]>article.product>a {
padding: 4px 8px 4px 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: bold;
backdrop-filter: brightness(0.4) contrast(1.2);
color: var(--text-light, var(--tg-theme-text-color));
}
main>section[data-section="catalog"]>article.product>a.title {
padding: 0 8px 0 8px;
height: var(--title-height);
min-height: var(--title-height);
line-height: var(--title-height);
font-size: var(--title-font-size);
}
main>section[data-section="catalog"]>article.product:hover>a.title {
backdrop-filter: brightness(0.35) contrast(1.2);
}
main>section[data-section="catalog"]>article.product>a+ :is(a, small) {
padding: 0px 8px 0 8px;
}
main>section[data-section="catalog"]>article.product>small {
padding: 3px 8px 0 8px;
white-space: normal;
word-break: break-all;
font-size: 0.7rem;
backdrop-filter: brightness(0.4) contrast(1.2);
color: var(--text-light, var(--tg-theme-text-color));
}
main>section[data-section="catalog"]>article.product>small.description {
overflow: hidden;
}
main>section[data-section="catalog"]>article.product:hover>small.description {
height: calc(var(--product-height) - var(--title-height) - var(--button-height) - 6px);
}
main>section[data-section="catalog"]>article.product:not(:hover)>small.description {
height: 0;
padding: 0 8px 0px 8px !important;
}
main>section[data-section="catalog"]>article.product>*:has(+ button:last-of-type) {
--offset-before-button: 9px;
padding: 4px 8px 13px 8px;
}
main>section[data-section="catalog"]>article.product>button:last-of-type {
height: var(--button-height);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: var(--tg-theme-button-text-color);
background-color: var(--tg-theme-button-color);
}
main>section[data-section="cart"] {
--diameter: 4rem;
z-index: 999;
right: 5vw;
bottom: 5vw;
position: fixed;
width: var(--diameter);
height: var(--diameter);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
border-radius: 100%;
background-color: var(--tg-theme-button-color);
}
main>section[data-section="cart"]>i.icon.shopping.cart {
top: -1px;
color: var(--tg-theme-button-text-color);
}

View File

@ -0,0 +1,169 @@
@charset "UTF-8";
main>section[data-section="catalog"] {
width: var(--width);
display: flex;
flex-flow: row wrap;
gap: var(--gap, 5px);
}
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"] {
--padding: 0.7rem;
position: relative;
height: 23px;
padding: var(--padding);
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
overflow: hidden;
border-radius: 0.75rem;
color: var(--tg-theme-button-text-color);
background-color: var(--tg-theme-button-color);
}
main>section[data-section="catalog"][data-catalog-type="categories"]:last-child {
/* margin-bottom: unset; */
}
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:has(>img) {
min-width: calc(50% - var(--padding) * 2);
height: 180px;
padding: unset;
}
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]>img {
position: absolute;
left: -5%;
top: -5%;
width: 110%;
height: 110%;
object-fit: cover;
/* filter: blur(1px); */
filter: brightness(60%);
}
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:is(:hover, :focus)>img {
filter: unset;
}
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:has(>img)>p {
position: absolute;
left: var(--padding);
bottom: var(--padding);
right: var(--padding);
margin: unset;
width: min-content;
border-radius: 0.75rem;
background: var(--tg-theme-secondary-bg-color);
}
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]>p {
z-index: 100;
padding: 8px 16px;
}
main>section[data-section="catalog"][data-catalog-type="products"] {
--column: calc((100% - var(--gap)) / 2);
width: var(--width);
display: grid;
grid-gap: var(--gap);
grid-template-columns: repeat(2, var(--column));
grid-auto-flow: row dense;
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column {
width: 100%;
display: flex;
flex-direction: column;
gap: var(--gap);
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product {
position: relative;
width: 100%;
display: flex;
flex-grow: 0;
flex-direction: column;
border-radius: 0.75rem;
overflow: clip;
backdrop-filter: brightness(0.7);
cursor: pointer;
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product:is(:hover, :focus) {
/* flex-grow: 0.1; */
/* background-color: var(--tg-theme-secondary-bg-color); */
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product:is(:hover, :focus)>* {
transition: 0s;
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product:not(:is(:hover, :focus))>* {
transition: 0.2s ease-out;
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product>a {
display: contents;
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product>a>img:first-of-type {
width: 100%;
height: 100%;
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product>a>img:first-of-type+* {
margin-top: auto;
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product>a>p.title {
z-index: 50;
margin: unset;
padding: 4px 8px;
font-size: 0.9rem;
font-weight: bold;
overflow-wrap: anywhere;
hyphens: auto;
color: var(--tg-theme-text-color);
background-color: var(--tg-theme-secondary-bg-color);
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product>button:last-of-type {
z-index: 100;
height: 33px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: var(--tg-theme-button-text-color);
background-color: var(--tg-theme-button-color);
}
main>section[data-section="cart"] {
--diameter: 4rem;
z-index: 999;
right: 5vw;
bottom: 5vw;
position: fixed;
width: var(--diameter);
height: var(--diameter);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
border-radius: 100%;
background-color: var(--tg-theme-button-color);
}
main>section[data-section="cart"]>i.icon.shopping.cart {
top: -1px;
color: var(--tg-theme-button-text-color);
}
main>section[data-section="filters"] {
max-height: 2.5rem;
display: flex;
align-items: start;
}

View File

@ -0,0 +1,117 @@
@charset "UTF-8";
main>section[data-section="catalog"] {
width: var(--width);
display: flex;
flex-flow: row wrap;
gap: var(--gap, 5px);
}
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"] {
height: 23px;
padding: 8px 16px;
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
border-radius: 0.75rem;
color: var(--tg-theme-button-text-color);
background-color: var(--tg-theme-button-color);
}
main>section[data-section="catalog"][data-catalog-type="categories"]:last-child {
/* margin-bottom: unset; */
}
main>section[data-section="catalog"][data-catalog-type="products"] {
--column: calc((100% - var(--gap) * 2) / 3);
width: var(--width);
display: grid;
grid-gap: var(--gap);
grid-template-columns: repeat(3, var(--column));
grid-auto-flow: row dense;
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column {
width: 100%;
display: flex;
flex-direction: column;
gap: var(--gap);
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product {
position: relative;
width: 100%;
display: flex;
flex-grow: 0;
flex-direction: column;
border-radius: 0.75rem;
overflow: clip;
backdrop-filter: brightness(0.7);
cursor: pointer;
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product:hover {
/* flex-grow: 0.1; */
/* background-color: var(--tg-theme-secondary-bg-color); */
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product:hover>* {
transition: 0s;
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product:not(:hover)>* {
transition: 0.2s ease-out;
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product>img:first-of-type {
z-index: -50;
width: 100%;
max-height: 120px;
object-fit: cover;
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product>img:first-of-type+* {
margin-top: auto;
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product>a.title {
padding: 4px 8px;
font-size: 0.9rem;
font-weight: bold;
word-break: break-word;
hyphens: auto;
color: var(--tg-theme-text-color);
background-color: var(--tg-theme-secondary-bg-color);
}
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product>button:last-of-type {
height: 33px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: var(--tg-theme-button-text-color);
background-color: var(--tg-theme-button-color);
}
main>section[data-section="cart"] {
--diameter: 4rem;
z-index: 999;
right: 5vw;
bottom: 5vw;
position: fixed;
width: var(--diameter);
height: var(--diameter);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
border-radius: 100%;
background-color: var(--tg-theme-button-color);
}
main>section[data-section="cart"]>i.icon.shopping.cart {
top: -1px;
color: var(--tg-theme-button-text-color);
}

View File

@ -1,37 +0,0 @@
@charset "UTF-8";
i.icon.arrow.top.left {
box-sizing: border-box;
position: relative;
display: block;
width: 22px;
height: 22px;
border: 2px solid;
border-radius: 20px;
}
i.icon.arrow.top.left::after,
i.icon.arrow.top.left::before {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
}
i.icon.arrow.top.left::after {
width: 10px;
height: 2px;
background: currentColor;
transform: rotate(45deg);
bottom: 8px;
right: 4px;
}
i.icon.arrow.top.left::before {
width: 6px;
height: 6px;
left: 4px;
top: 4px;
border-top: 2px solid;
border-left: 2px solid;
}

View File

@ -4,6 +4,7 @@ i.icon.shopping.cart {
display: block; display: block;
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
transform: scale(var(--ggs, 1));
width: 20px; width: 20px;
height: 21px; height: 21px;
background: background:

View File

@ -25,9 +25,6 @@ a {
} }
body { body {
--gap: 16px;
--width: calc(100% - var(--gap) * 2);
--offset-x: 2%;
width: 100%; width: 100%;
height: 100%; height: 100%;
margin: 0; margin: 0;
@ -42,20 +39,10 @@ body {
aside {} aside {}
header { header {}
container-type: inline-size;
container-name: header;
margin: 2rem 0 1rem;
padding: 0 var(--offset-x);
display: flex;
flex-direction: column;
align-items: center;
gap: 26px;
}
main { main {
container-type: inline-size; --offset-x: 2%;
container-name: main;
padding: 0 var(--offset-x); padding: 0 var(--offset-x);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -69,6 +56,8 @@ main>section:last-child {
} }
main>*[data-section] { main>*[data-section] {
--gap: 16px;
--width: calc(100% - var(--gap) * 2);
width: var(--width); width: var(--width);
} }
@ -161,7 +150,7 @@ a[type="button"] {
h1, h1,
h2 { h2 {
margin: 1rem 0 0; margin: 28px 0 0;
} }
footer {} footer {}

View File

@ -1,53 +0,0 @@
@charset "UTF-8";
header>nav#menu {
container-type: inline-size;
container-name: menu;
width: var(--width);
min-height: 3rem;
display: flex;
flex-flow: row wrap;
gap: 1rem;
border-radius: 1.375rem;
overflow: hidden;
}
header>nav#menu>a[type="button"] {
height: 3rem;
padding: unset;
border-radius: 1.375rem;
color: var(--unsafe-color, var(--tg-theme-button-text-color));
background-color: var(--unsafe-background-color, var(--tg-theme-button-color));
}
header>nav#menu>a[type=button]>:first-child {
margin-left: 1rem;
}
header>nav#menu>a[type="button"]>* {
margin-right: 1rem;
}
@container header (max-width: 450px) {
header>nav#menu > a[type="button"]:nth-child(1)> i.icon+span {
display: none;
}
}
@container header (max-width: 350px) {
header>nav#menu > a[type="button"]:nth-child(2)> i.icon+span {
display: none;
}
}
@container header (max-width: 250px) {
header>nav#menu > a[type="button"]:nth-child(3)> i.icon+span {
display: none;
}
}
@container header (max-width: 150px) {
header>nav#menu > a[type="button"]> i.icon+span {
display: none;
}
}

View File

@ -67,6 +67,7 @@ final class templater extends controller implements ArrayAccess
if (!empty($account?->status())) $this->twig->addGlobal('account', $account); if (!empty($account?->status())) $this->twig->addGlobal('account', $account);
$this->twig->addGlobal('language', $account?->language->name ?? $settings?->language->name ?? 'en'); $this->twig->addGlobal('language', $account?->language->name ?? $settings?->language->name ?? 'en');
// Initialize function of dimensions formattinx // Initialize function of dimensions formattinx
$this->twig->addFunction( $this->twig->addFunction(
new TwigFunction( new TwigFunction(

View File

@ -19,5 +19,5 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
<script src="/js/account.js"></script> <script type="text/javascript" src="/js/account.js"></script>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,3 @@
<section data-section="cart">
<i class="icon shopping cart"></i>
</section>

View File

@ -1,15 +1,20 @@
{% if categories is not empty %} {% if categories is not empty %}
<section id="categories" class="unselectable"> <section class="unselectable" data-section="catalog" data-catalog-type="categories"
data-catalog-level="{{ level ?? 0 }}">
{% for category in categories %} {% for category in categories %}
<a id="{{ category.getId() }}" class="category" type="button" href="?category={{ category.identifier }}" {% if category.images %}
onclick="core.catalog.parameters.set('category', {{ category.identifier }}); return core.catalog.search(event);" <a id="{{ category.getId() }}" class="category" type="button" onclick="core.catalog.category(this)" onkeydown="event.keyCode === 13 && core.catalog.category(this)"
onkeydown="event.keyCode === 13 && (core.catalog.parameters.set('category', {{ category.identifier }}), core.catalog.search(event))" data-category-identifier="{{ category.identifier }}" tabindex="3">
tabindex="3"> <img src="{{ category.images.0.storage }}"
{% if category.images %} alt="{{ category.name[language] }}" ondrugstart="return false;">
<img src="{{ category.images.0.storage }}" alt="{{ category.name[language] }}" ondrugstart="return false;">
{% endif %}
<p>{{ category.name[language] }}</p> <p>{{ category.name[language] }}</p>
</a> </a>
{% else %}
<a id="{{ category.getId() }}" class="category" type="button" onclick="core.catalog.category(this)" onkeydown="event.keyCode === 13 && core.catalog.category(this)"
data-category-identifier="{{ category.identifier }}" tabindex="3">
<p>{{ category.name[language] }}</p>
</a>
{% endif %}
{% endfor %} {% endfor %}
</section> </section>
{% endif %} {% endif %}

View File

@ -1,21 +1,13 @@
{% if filters is not empty %} {% if filters is not empty %}
<section id="filters" class="unselectble"> <section class="unselectble" data-section="filters">
<section class="unselectable" data-type="select" tabindex="4"> <section class="unselectable" data-type="select" data-filter="brand" tabindex="4">
{% set buffer_brand = account.buffer.catalog.filters.brand ?? session.buffer.catalog.filters.brand %} <input name="brand" type="radio" id="brand_title"{% if session.filters.brand is empty %} checked{% endif %}>
<input name="brand" type="radio" id="brand_title" {% if buffer_brand is empty %} checked{% endif %}> <label for="brand_title" type="button">{{ language == 'ru' ? 'Бренд' : 'Brand' }}</label>
<label for="brand_title" type="button" onclick="core.catalog.parameters.set('brand', null); core.catalog.search(event)">
{{ language == 'ru' ? 'Бренд' : 'Brand' }}
</label>
<input name="brand" type="radio" id="brand_all"> <input name="brand" type="radio" id="brand_all">
<label for="brand_all" type="button" onclick="core.catalog.parameters.set('brand', null); core.catalog.search(event)"> <label for="brand_all" type="button">{{ language == 'ru' ? 'Все бренды' : 'All brands' }}</label>
{{ language == 'ru' ? 'Все бренды' : 'All brands' }}
</label>
{% for brand in filters.brands %} {% for brand in filters.brands %}
<input name="brand" type="radio" id="brand_{{ loop.index }}" {% if brand == buffer_brand %} checked{% endif %}> <input name="brand" type="radio" id="brand_{{ loop.index }}"{% if session.filters.brand == brand %} checked{% endif %}>
<label for="brand_{{ loop.index }}" type="button" <label for="brand_{{ loop.index }}" type="button">{{ brand }}</label>
onclick="core.catalog.parameters.set('brand', '{{ brand }}'); core.catalog.search(event)">
{{ brand }}
</label>
{% endfor %} {% endfor %}
</section> </section>
</section> </section>

View File

@ -0,0 +1,13 @@
{% if products is not empty %}
<section class="unselectable" data-section="catalog" data-catalog-type="products" data-catalog-level="{{ level ?? 0 }}">
{% for product in products %}
<article id="{{ product.getId() }}" class="product unselectable">
<img src="/images/{{ product.getKey() }}/1.jpg" alt="{{ product.title.ru }}" ondrugstart="return false;" onclick="return core.catalog.product({{ product.getKey() }});">
<a class="title" title="{{ product.title.ru }}" onclick="return core.catalog.product({{ product.getKey() }});">{{ product.title.ru }}</a>
<small class="description" title="{{ product.description.ru }}" onclick="return core.catalog.product({{ product.getKey() }});">{{ product.description.ru | length > 160 ? product.description.ru | slice(0, 160) ~
'...' : product.description.ru }}</small>
<button title="Добавить в корзину" onclick="return core.catalog.cart.add({{ product.getKey() }})">{{ product.cost }}р</button>
</article>
{% endfor %}
</section>
{% endif %}

View File

@ -1,7 +1,7 @@
{% macro card(product) %} {% macro card(product) %}
{% set title = product.name[language] ~ ' ' ~ product.brand[language] ~ format_dimensions(product.dimensions.x, product.dimensions.y, product.dimensions.z, ' ') ~ ' ' ~ product.weight ~ 'г' %} {% set title = product.name[language] ~ ' ' ~ product.brand[language] ~ format_dimensions(product.dimensions.x, product.dimensions.y, product.dimensions.z, ' ') ~ ' ' ~ product.weight ~ 'г' %}
<article id="{{ product.getId() }}" class="product unselectable"> <article id="{{ product.getId() }}" class="product unselectable">
<a data-product-identifier="{{ product.identifier }}" href="?product={{ product.identifier }}" onclick="return core.catalog.product(this);" onkeydown="event.keyCode === 13 && core.catalog.product(this)" tabindex="10"> <a data-product-identifier="{{ product.identifier }}" onclick="core.catalog.product(this)" onkeydown="event.keyCode === 13 && core.catalog.product(this)" tabindex="10">
<img src="{{ product.images.0.storage }}" alt="{{ product.name[language] }}" ondrugstart="return false;"> <img src="{{ product.images.0.storage }}" alt="{{ product.name[language] }}" ondrugstart="return false;">
<p class="title" title="{{ product.name[language] }}"> <p class="title" title="{{ product.name[language] }}">
{{ title | length > 45 ? title | slice(0, 45) ~ '...' : title }} {{ title | length > 45 ? title | slice(0, 45) ~ '...' : title }}
@ -13,14 +13,7 @@
</article> </article>
{% endmacro %} {% endmacro %}
{% if products is not empty %} {% if products is not empty %}
<section id="products" class="unselectable"> <section class="unselectable" data-section="catalog" data-catalog-type="products" data-catalog-level="{{ level ?? 0 }}">
<div class="column">
{% for product in products %}
{% if loop.index % 2 == 1 %}
{{ _self.card(product) }}
{% endif %}
{% endfor %}
</div>
<div class="column"> <div class="column">
{% for product in products %} {% for product in products %}
{% if loop.index % 2 == 0 %} {% if loop.index % 2 == 0 %}
@ -28,5 +21,12 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
<div class="column">
{% for product in products %}
{% if loop.index % 2 == 1 %}
{{ _self.card(product) }}
{% endif %}
{% endfor %}
</div>
</section> </section>
{% endif %} {% endif %}

View File

@ -0,0 +1,36 @@
{% macro card(product) %}
<article id="{{ product.getId() }}" class="product unselectable">
<img src="/images/{{ product.getKey() }}/1.jpg" alt="{{ product.title.ru }}" ondrugstart="return false;"
onclick="return core.catalog.product({{ product.getKey() }});">
<a class="title" title="{{ product.title.ru }}" onclick="return core.catalog.product({{ product.getKey() }});">{{
product.title.ru | length > 35 ? product.title.ru | slice(0, 35) ~ '...' : product.title.ru }}</a>
<button title="Добавить в корзину" onclick="return core.catalog.cart.add({{ product.getKey() }})">{{ product.cost
}}р</button>
</article>
{% endmacro %}
{% if products is not empty %}
<section class="unselectable" data-section="catalog" data-catalog-type="products" data-catalog-level="{{ level ?? 0 }}">
<div class="column">
{% for product in products %}
{% if loop.index0 % 3 == 0 %}
{{ _self.card(product) }}
{% endif %}
{% endfor %}
</div>
<div class="column">
{% for product in products %}
{% if loop.index0 % 3 == 1 %}
{{ _self.card(product) }}
{% endif %}
{% endfor %}
</div>
<div class="column">
{% for product in products %}
{% if loop.index0 % 3 == 2 %}
{{ _self.card(product) }}
{% endif %}
{% endfor %}
</div>
</section>
{% endif %}

View File

@ -1,7 +1,4 @@
<search id="search"> <search data-section="search">
<label class="unselectable"><i class="icon search"></i></label> <label><i class="icon search"></i></label>
{% set buffer_search = account.buffer.catalog.search.text ?? session.buffer.catalog.search.text %} <input id="search" placeholder="Поиск по каталогу" type="search" onkeyup="event.keyCode !== 9 && core.catalog.search(event, this)" tabindex="1"/>
<input placeholder="Поиск по каталогу" type="search" tabindex="1"
onkeyup="event.keyCode === 9 || core.catalog.parameters.set('text', this.value); core.catalog.search(event, this)"
{% if buffer_search is not empty %} value="{{ buffer_search }}" {% endif %} />
</search> </search>

View File

@ -2,7 +2,7 @@
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/catalog.css" /> <link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/catalog/2columns.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/interface/select.css" /> <link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/interface/select.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/search.css" /> <link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/search.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/shopping_cart.css" /> <link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/shopping_cart.css" />
@ -10,11 +10,13 @@
{% endblock %} {% endblock %}
{% block main %} {% block main %}
<h2 class="unselectable">{{ h2 }}</h2> <h2 class="unselectable">Каталог</h2>
{% include "/themes/default/catalog/elements/search.html" %} {% include "/themes/default/catalog/elements/search.html" %}
{% include "/themes/default/catalog/elements/categories.html" %} {% include "/themes/default/catalog/elements/categories.html" %}
{% include "/themes/default/catalog/elements/filters.html" %} {% include "/themes/default/catalog/elements/filters.html" %}
{% include "/themes/default/catalog/elements/products.html" %} {% include "/themes/default/catalog/elements/products/2columns.html" %}
<!-- {% include "/themes/default/catalog/elements/cart.html" %} -->
{% endblock %} {% endblock %}
{% block js %} {% block js %}

View File

@ -10,5 +10,5 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
<script src="/js/connection.js"></script> <script type="text/javascript" src="/js/connection.js"></script>
{% endblock %} {% endblock %}

View File

@ -1,16 +1,17 @@
{% block title %} {% block title %}
<title>{% if head.title is not empty %}{{ head.title }}{% else %}{{ settings.project.name }}{% endif %}</title> <title>{% if head.title != empty %}{{head.title}}{% else %}ARMING{% endif %}</title>
{% endblock %} {% endblock %}
{% block meta %} {% block meta %}
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% for meta in head.metas %} {% for meta in head.metas %}
<meta {% for name, value in meta.attributes %}{{ name }}="{{ value }}" {% endfor %}> <meta {% for name, value in meta.attributes %}{{name}}="{{value}}" {% endfor %}>
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
<!-- <link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/initialization.css" /> -->
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/main.css" /> <link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/main.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/loading_spinner.css" /> <link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/loading_spinner.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/loading.css" /> <link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/loading.css" />

View File

@ -1,15 +1,10 @@
{% use "/themes/default/menu.html" with css as menu_css, body as menu_body, js as menu_js %}
{% block css %} {% block css %}
{{ block('menu_css') }}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<header> <header>
{{ block('menu_body') }}
</header> </header>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ block('menu_js') }}
{% endblock %} {% endblock %}

View File

@ -7,16 +7,16 @@
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ block('header_css') }}
{{ block('connection_css') }} {{ block('connection_css') }}
{{ block('account_css') }} {{ block('account_css') }}
{{ block('header_css') }}
{{ block('footer_css') }} {{ block('footer_css') }}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
{{ block('header') }}
{{ block('connection_body') }} {{ block('connection_body') }}
{{ block('account_body') }} {{ block('account_body') }}
{{ block('header') }}
<main> <main>
{% block main %} {% block main %}
{{ main|raw }} {{ main|raw }}
@ -27,8 +27,8 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ block('header_js') }}
{{ block('connection_js') }} {{ block('connection_js') }}
{{ block('account_js') }} {{ block('account_js') }}
{{ block('header_js') }}
{{ block('footer_js') }} {{ block('footer_js') }}
{% endblock %} {% endblock %}

View File

@ -1,38 +1,8 @@
{% block js %} {% block js %}
<script src="https://telegram.org/js/telegram-web-app.js"></script> <script src="https://telegram.org/js/telegram-web-app.js" defer></script>
<script src="/js/core.js"></script> <script src="/js/core.js" defer></script>
<script src="/js/damper.js"></script> <script src="/js/damper.js"></script>
<script src="/js/loader.js"></script>
<script src="/js/telegram.js"></script> <script src="/js/telegram.js"></script>
<script src="/js/session.js"></script> <script src="/js/session.js"></script>
<script src="/js/authentication.js"></script> <script src="/js/authentication.js"></script>
{% if javascript is not empty %}
<script>
import("/js/core.js").then(() =>
import("/js/damper.js").then(() => {
const dependencies = setInterval(() => {
if (
typeof core === "function" &&
typeof core.damper === "function"
) {
clearInterval(dependencies);
clearTimeout(timeout);
initialization();
}
}, 10);
const timeout = setTimeout(() => {
clearInterval(dependencies);
initialization();
}, 5000);
function initialization() {
let _window;
{% for code in javascript %}
{{ code|raw }}
{% endfor %}
}
})
);
</script>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -1,30 +0,0 @@
{% block css %}
<link type="text/css" rel="stylesheet" href="/themes/default/css/menu.css">
{% for button in menu %}
{% if button.icon %}
<link type="text/css" rel="stylesheet" href="/themes/default/css/icons/{{ button.icon|replace({' ': '-'}) }}.css">
{% endif %}
{% endfor %}
{% endblock %}
{% block body %}
<nav id="menu">
{% for button in menu %}
<a href='{{ button.urn }}' onclick="return core.loader.load('{{ button.urn }}');" type="button" class="unselectable"
title="{{ button.name }}"
style="order: {{ button.position }};{% for target, color in button.color %} --unsafe-{{ target }}: {{ color|e }};{% endfor %}">
{% if button.icon %}
<i class="icon {{ button.icon }}"></i>
{% endif %}
<span>{{ button.name }}</span>
{% if button.image.storage %}
<img src="{{ button.image.storage }}" alt="{{ button.name }}" ondrugstart="return false;">
{% endif %}
</a>
{% endfor %}
</nav>
{% endblock %}
{% block js %}
<script src="/js/menu.js"></script>
{% endblock %}

View File

@ -0,0 +1,14 @@
{% extends "/themes/default/index.html" %}
{% block css %}
{{ parent() }}
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/catalog.css" />
{% endblock %}
{% block main %}
{% endblock %}
{% block js %}
{{ parent() }}
<script src="/js/catalog.js" defer></script>
{% endblock %}