menu + catelog REBUILD + new icons
This commit is contained in:
parent
5509b97148
commit
16be453b07
|
@ -0,0 +1,60 @@
|
||||||
|
<?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
|
||||||
|
* @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']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,11 @@ namespace mirzaev\arming_bot\controllers;
|
||||||
use mirzaev\arming_bot\controllers\core,
|
use mirzaev\arming_bot\controllers\core,
|
||||||
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
|
||||||
|
@ -24,6 +28,7 @@ final class catalog extends core
|
||||||
protected array $errors = [
|
protected array $errors = [
|
||||||
'session' => [],
|
'session' => [],
|
||||||
'account' => [],
|
'account' => [],
|
||||||
|
'menu' => [],
|
||||||
'catalog' => []
|
'catalog' => []
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -34,66 +39,162 @@ final class catalog extends core
|
||||||
*/
|
*/
|
||||||
public function index(array $parameters = []): ?string
|
public function index(array $parameters = []): ?string
|
||||||
{
|
{
|
||||||
// Initializing identifier of a category
|
// Validating
|
||||||
preg_match('/[\d]+/', $parameters['identifier'] ?? '', $matches);
|
if (!empty($parameters['category']) && preg_match('/[\d]+/', $parameters['category'], $matches)) $category = (int) $matches[0];
|
||||||
$identifier = $matches[0] ?? null;
|
|
||||||
|
|
||||||
// Initializint the buffer of respnse
|
if (isset($category)) {
|
||||||
$html = [];
|
// Received and validated identifier of the category
|
||||||
|
|
||||||
if (!empty($parameters['identifier'])) {
|
// Initialize of category
|
||||||
// Передана категория (идентификатор)
|
$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: 30,
|
amount: 30,
|
||||||
errors: $this->errors['catalog']
|
errors: $this->errors['catalog']
|
||||||
);
|
);
|
||||||
|
|
||||||
// Объявление буферов категорий и товаров (важно - в единственном числе, по параметру из базы данных)
|
// 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)
|
||||||
|
|
||||||
// Запись массивов категорий и товаров ($category и $product) в буфер глобальной переменной шаблонизатора
|
// Write entry to the buffer of entries (sort by $category and $product)
|
||||||
${$entry->_type}[] = $entry;
|
${$entry->_type}[] = $entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Запись категорий из буфера в глобальную переменную шаблонизатора
|
// Write to the buffer of global variables of view templater
|
||||||
$this->view->categories = $category;
|
$this->view->categories = $category;
|
||||||
|
|
||||||
// Запись товаров из буфера в глобальную переменную шаблонизатора
|
// Write to the buffer of global variables of view templater
|
||||||
$this->view->products = $product;
|
$this->view->products = $product;
|
||||||
|
|
||||||
// Generating filters
|
// Delete buffers
|
||||||
$this->view->filters = [
|
unset($category, $product);
|
||||||
'brands' => product::collect(
|
|
||||||
'd.brand.' . $this->language->name,
|
|
||||||
errors: $this->errors['catalog']
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
// Generating HTML and writing to the buffer of response
|
|
||||||
$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 {
|
} else if (!isset($parameters['category'])) {
|
||||||
// Не передана категория
|
// Not received identifie of the category
|
||||||
|
|
||||||
// Поиск категорий: "categories" (самый верхний уровень)
|
// Search for root ascendants categories
|
||||||
$this->view->categories = entry::ascendants(descendant: new category, errors: $this->errors['catalog']);
|
$this->view->categories = entry::ascendants(descendant: new category, errors: $this->errors['catalog']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 product 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validating
|
||||||
|
if (!empty($parameters['text']) && preg_match('/[\w\s]+/u', $parameters['text'], $matches)) $text = $matches[0];
|
||||||
|
|
||||||
|
if (isset($text)) {
|
||||||
|
// Received and validated text for search
|
||||||
|
|
||||||
|
// Intialize buffer of query parameters
|
||||||
|
$_parameters = [];
|
||||||
|
|
||||||
|
// Initialize buffer of filters query (AQL)
|
||||||
|
$_filters = 'd.deleted != true && d.hidden != true';
|
||||||
|
|
||||||
|
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]+/', $parameters['brand'], $matches)) $brand = $matches[0];
|
||||||
|
|
||||||
|
if (isset($brand)) {
|
||||||
|
// Received and validated filter by brand
|
||||||
|
|
||||||
|
// Write to the buffer of filters query (AQL)
|
||||||
|
$_filters .= ' && d.brand.@language == @brand';
|
||||||
|
|
||||||
|
// Write to the buffer of query parameters
|
||||||
|
$_parameters['brand'] = $brand;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize buffer of filters query (AQL)
|
||||||
|
$_sort = 'd.position ASC, d.name ASC, d.created DESC';
|
||||||
|
|
||||||
|
// Validating
|
||||||
|
if (!empty($parameters['sort']) && preg_match('/[\w]+/', $parameters['sort'], $matches)) $sort = $matches[0];
|
||||||
|
|
||||||
|
if (isset($sort)) {
|
||||||
|
// Received and validated sort
|
||||||
|
|
||||||
|
// Write to the buffer of sort query (AQL)
|
||||||
|
$_sort = "d.@sort DESC, $_sort";
|
||||||
|
|
||||||
|
// Write to the buffer of query parameters
|
||||||
|
$_parameters['sort'] = $sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for products and write to the buffer of global variables of view templater
|
||||||
|
$this->view->products = product::read(
|
||||||
|
search: $text,
|
||||||
|
filter: $_filters,
|
||||||
|
sort: $_sort,
|
||||||
|
amount: 30,
|
||||||
|
language: $this->language,
|
||||||
|
parameters: $_parameters,
|
||||||
|
errors: $this->errors['catalog']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
@ -102,152 +203,64 @@ final class catalog extends core
|
||||||
} else if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
} else if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
// POST request
|
// POST request
|
||||||
|
|
||||||
// Initializing a response headers
|
// Initializing the buffer of response
|
||||||
header('Content-Type: application/json');
|
$response = [
|
||||||
header('Content-Encoding: none');
|
'title' => $title ?? null
|
||||||
header('X-Accel-Buffering: no');
|
];
|
||||||
|
|
||||||
// Initializing of the output buffer
|
if (isset($this->view->categories)) {
|
||||||
ob_start();
|
// Initialized categories
|
||||||
|
|
||||||
// Generating the reponse
|
// Render HTML-code of categories and write to the response buffer
|
||||||
echo json_encode(
|
$response['html'] ??= [];
|
||||||
[
|
$response['html']['categories'] = $this->view->render('catalog/elements/categories.html');
|
||||||
'title' => $title ?? '',
|
|
||||||
'html' => $html + [
|
|
||||||
'categories' => $this->view->render('catalog/elements/categories.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($this->view->product)) {
|
||||||
|
// Initialized product
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->view->products)) {
|
||||||
|
// Initialized products
|
||||||
|
|
||||||
|
// Render HTML-code of products and write to the response buffer
|
||||||
|
$response['html'] ??= [];
|
||||||
|
$response['html']['products'] = $this->view->render('catalog/elements/products/2columns.html');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->view->filters)) {
|
||||||
|
// Initialized filters
|
||||||
|
|
||||||
|
// Render HTML-code of filters and write to the response buffer
|
||||||
|
$response['html'] ??= [];
|
||||||
|
$response['html']['filters'] = $this->view->render('catalog/elements/filters.html');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
$response + [
|
||||||
|
'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)
|
// Exit (fail)
|
||||||
|
|
|
@ -153,14 +153,14 @@ final class session extends core
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect session to the telegram account
|
* Write to the buffer
|
||||||
*
|
*
|
||||||
* @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 = []): ?string
|
public function write(array $parameters = []): void
|
||||||
{
|
{
|
||||||
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
|
||||||
|
@ -171,18 +171,15 @@ 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;
|
||||||
|
|
||||||
// Write data of the program to the implement object of session document from ArangoDB
|
// Convert name to multidimensional array
|
||||||
$this->session->{$name} = json_decode($value, true, 10);
|
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 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?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';
|
||||||
|
}
|
|
@ -214,12 +214,16 @@ 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 {
|
||||||
|
@ -230,13 +234,16 @@ 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
|
||||||
|
|
|
@ -39,12 +39,14 @@ $router = new router;
|
||||||
// Initialize routes
|
// Initialize routes
|
||||||
$router
|
$router
|
||||||
->write('/', 'catalog', 'index', 'GET')
|
->write('/', 'catalog', 'index', 'GET')
|
||||||
->write('/search', 'catalog', 'search', 'POST')
|
->write('/', 'catalog', 'index', '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') */
|
||||||
|
;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
|
|
@ -72,10 +72,11 @@ import("/js/core.js").then(() =>
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
if (core.telegram.api.initData.length > 0) {
|
if (core.telegram.api.initData.length > 0) {
|
||||||
core.request(
|
core
|
||||||
"/session/connect/telegram",
|
.request(
|
||||||
core.telegram.api.initData,
|
"/session/connect/telegram",
|
||||||
)
|
core.telegram.api.initData
|
||||||
|
)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
if (
|
if (
|
||||||
json.errors !== null &&
|
json.errors !== null &&
|
||||||
|
@ -93,9 +94,8 @@ import("/js/core.js").then(() =>
|
||||||
const a =
|
const a =
|
||||||
core.status_account.getElementsByTagName("a")[0];
|
core.status_account.getElementsByTagName("a")[0];
|
||||||
a.setAttribute("onclick", "core.account.profile()");
|
a.setAttribute("onclick", "core.account.profile()");
|
||||||
a.innerText = json.domain.length > 0
|
a.innerText =
|
||||||
? "@" + json.domain
|
json.domain.length > 0 ? "@" + json.domain : "ERROR";
|
||||||
: "ERROR";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -109,6 +109,34 @@ 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,20 @@ 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();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -29,226 +29,19 @@ import("/js/core.js").then(() =>
|
||||||
// Write to the core
|
// Write to the core
|
||||||
core.catalog = class catalog {
|
core.catalog = class catalog {
|
||||||
/**
|
/**
|
||||||
* Registry of filters (instead of cookies)
|
* Parameters of search
|
||||||
|
*
|
||||||
|
* Will be converted to the body of the GET request ("?name=value&name2=value2")
|
||||||
*/
|
*/
|
||||||
static filters = new Map([
|
static parameters = new Map([
|
||||||
|
["text", null],
|
||||||
|
["category", null],
|
||||||
|
["sort", null],
|
||||||
["brand", null],
|
["brand", null],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select a category (interface)
|
* Search (interface)
|
||||||
*
|
|
||||||
* @param {HTMLElement} button Button of a category <a>
|
|
||||||
* @param {bool} clean Clear search bar?
|
|
||||||
* @param {bool} force Ignore the damper?
|
|
||||||
*
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
static category(button, clean = true, force = false) {
|
|
||||||
// Initializing identifier of the category
|
|
||||||
const identifier = button.getAttribute(
|
|
||||||
"data-category-identifier",
|
|
||||||
);
|
|
||||||
|
|
||||||
this._category(identifier, clean, force);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select a category (damper)
|
|
||||||
*
|
|
||||||
* @param {HTMLElement} button Button of category <a>
|
|
||||||
* @param {bool} clean Clear search bar?
|
|
||||||
* @param {bool} force Ignore the damper?
|
|
||||||
*
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
static _category = core.damper(
|
|
||||||
(...variables) => this.__category(...variables),
|
|
||||||
400,
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select a category (system)
|
|
||||||
*
|
|
||||||
* @param {string} identifier Identifier of the category
|
|
||||||
* @param {bool} clean Clear search bar?
|
|
||||||
*
|
|
||||||
* @return {Promise} Request to the server
|
|
||||||
*/
|
|
||||||
static __category(identifier, clean = true) {
|
|
||||||
if (typeof identifier === "string") {
|
|
||||||
// Received required parameters
|
|
||||||
|
|
||||||
return core.request(
|
|
||||||
"/category" +
|
|
||||||
("/" + identifier).replace(/^\/*/, "/").trim().replace(
|
|
||||||
/\/*$/,
|
|
||||||
"",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.then((json) => {
|
|
||||||
if (
|
|
||||||
json.errors !== null &&
|
|
||||||
typeof json.errors === "object" &&
|
|
||||||
json.errors.length > 0
|
|
||||||
) {
|
|
||||||
// Errors received
|
|
||||||
} else {
|
|
||||||
// Errors not received
|
|
||||||
|
|
||||||
if (clean) {
|
|
||||||
// Clearing the search bar
|
|
||||||
const search = core.main.querySelector(
|
|
||||||
'search[data-section="search"]>input',
|
|
||||||
);
|
|
||||||
if (search instanceof HTMLElement) search.value = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof json.title === "string" &&
|
|
||||||
json.title.length > 0
|
|
||||||
) {
|
|
||||||
// Received the page title
|
|
||||||
|
|
||||||
// Initialize a link to the categories list
|
|
||||||
const title = core.main.getElementsByTagName("h2")[0];
|
|
||||||
|
|
||||||
// Write the title
|
|
||||||
title.innerText = json.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof json.html.categories === "string" &&
|
|
||||||
json.html.categories.length > 0
|
|
||||||
) {
|
|
||||||
// Received categories (reinitialization of the categories)
|
|
||||||
|
|
||||||
const categories = core.main.querySelector(
|
|
||||||
'section[data-catalog-type="categories"]',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (categories instanceof HTMLElement) {
|
|
||||||
// Found list of categories
|
|
||||||
|
|
||||||
categories.outerHTML = json.html.categories;
|
|
||||||
} else {
|
|
||||||
// Not found list of categories
|
|
||||||
|
|
||||||
const element = document.createElement("section");
|
|
||||||
|
|
||||||
const search = core.main.querySelector(
|
|
||||||
'search[data-section="search"]',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (search instanceof HTMLElement) {
|
|
||||||
core.main.insertBefore(
|
|
||||||
element,
|
|
||||||
search.nextSibling,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
core.main.append(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
element.outerHTML = json.html.categories;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Not received categories (deinitialization of the categories)
|
|
||||||
|
|
||||||
const categories = core.main.querySelector(
|
|
||||||
'section[data-catalog-type="categories"',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (categories instanceof HTMLElement) {
|
|
||||||
categories.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof json.html.filters === "string" &&
|
|
||||||
json.html.filters.length > 0
|
|
||||||
) {
|
|
||||||
// Received filters (reinitialization of the filters)
|
|
||||||
|
|
||||||
const filters = core.main.querySelector(
|
|
||||||
'section[data-section="filters"]',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (filters instanceof HTMLElement) {
|
|
||||||
// Found list of filters
|
|
||||||
|
|
||||||
filters.outerHTML = json.html.filters;
|
|
||||||
} else {
|
|
||||||
// Not found list of categories
|
|
||||||
|
|
||||||
const element = document.createElement("section");
|
|
||||||
|
|
||||||
const categories = core.main.querySelector(
|
|
||||||
'section[data-catalog-type="categories"]',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (categories instanceof HTMLElement) {
|
|
||||||
core.main.insertBefore(
|
|
||||||
element,
|
|
||||||
categories.nextSibling,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
core.main.append(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
element.outerHTML = json.html.filters;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Not received categories (deinitialization of the categories)
|
|
||||||
|
|
||||||
const filters = core.main.querySelector(
|
|
||||||
'section[data-section="filters"',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (filters instanceof HTMLElement) {
|
|
||||||
filters.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof json.html.products === "string" &&
|
|
||||||
json.html.products.length > 0
|
|
||||||
) {
|
|
||||||
// Received products (reinitialization of the products)
|
|
||||||
|
|
||||||
const products = core.main.querySelector(
|
|
||||||
'section[data-catalog-type="products"]',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (products instanceof HTMLElement) {
|
|
||||||
// Found list of products
|
|
||||||
|
|
||||||
products.outerHTML = json.html.products;
|
|
||||||
} else {
|
|
||||||
// Not found list of products
|
|
||||||
|
|
||||||
const element = document.createElement("section");
|
|
||||||
core.main.append(element);
|
|
||||||
element.outerHTML = json.html.products;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Not received products (deinitialization of the products)
|
|
||||||
|
|
||||||
const products = core.main.querySelector(
|
|
||||||
'section[data-catalog-type="products"',
|
|
||||||
);
|
|
||||||
if (products instanceof HTMLElement) {
|
|
||||||
products
|
|
||||||
.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select a category (interface)
|
|
||||||
*
|
*
|
||||||
* @param {Event} event Event (keyup)
|
* @param {Event} event Event (keyup)
|
||||||
* @param {HTMLElement} element Search bar <input>
|
* @param {HTMLElement} element Search bar <input>
|
||||||
|
@ -257,28 +50,37 @@ import("/js/core.js").then(() =>
|
||||||
* @return {void}
|
* @return {void}
|
||||||
*/
|
*/
|
||||||
static search(event, element, force = false) {
|
static search(event, element, force = false) {
|
||||||
|
if (typeof element === "undefined") {
|
||||||
|
element = document.getElementById("search");
|
||||||
|
}
|
||||||
|
|
||||||
element.classList.remove("error");
|
element.classList.remove("error");
|
||||||
|
|
||||||
if (element.value.length === 1) {
|
if (element.value.length === 1) {
|
||||||
return;
|
return;
|
||||||
} else if (event.keyCode === 13) {
|
|
||||||
// Button: "enter"
|
|
||||||
|
|
||||||
element.setAttribute("disabled", true);
|
|
||||||
|
|
||||||
this._search(element, force);
|
|
||||||
} else {
|
} else {
|
||||||
// Button: any
|
if (typeof event === "undefined") {
|
||||||
|
//
|
||||||
|
|
||||||
this._search(element, force);
|
this._search(element, true);
|
||||||
|
} else if (event.keyCode === 13) {
|
||||||
|
// Button: "enter"
|
||||||
|
|
||||||
|
element.setAttribute("disabled", true);
|
||||||
|
|
||||||
|
this._search(element, force);
|
||||||
|
} else {
|
||||||
|
// Button: any
|
||||||
|
|
||||||
|
this._search(element, force);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search in the catalog (damper)
|
* Search (damper)
|
||||||
*
|
*
|
||||||
* @param {HTMLElement} button Button of category <a>
|
* @param {HTMLElement} element Search bar <input>
|
||||||
* @param {bool} clean Clear search bar?
|
|
||||||
* @param {bool} force Ignore the damper?
|
* @param {bool} force Ignore the damper?
|
||||||
*
|
*
|
||||||
* @return {void}
|
* @return {void}
|
||||||
|
@ -290,7 +92,7 @@ import("/js/core.js").then(() =>
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search in the catalog (system)
|
* Search (system)
|
||||||
*
|
*
|
||||||
* @param {HTMLElement} element Search bar <input>
|
* @param {HTMLElement} element Search bar <input>
|
||||||
*
|
*
|
||||||
|
@ -299,101 +101,170 @@ import("/js/core.js").then(() =>
|
||||||
* @todo add animations of errors
|
* @todo add animations of errors
|
||||||
*/
|
*/
|
||||||
static __search(element) {
|
static __search(element) {
|
||||||
return this.__category("/", false)
|
if (typeof element === "undefined") {
|
||||||
.then(function () {
|
element = document.getElementById("search");
|
||||||
core.request("/search", `text=${element.value}`)
|
}
|
||||||
.then((json) => {
|
|
||||||
element.removeAttribute("disabled");
|
|
||||||
element.focus();
|
|
||||||
|
|
||||||
if (
|
const urn = Array.from(this.parameters)
|
||||||
json.errors !== null &&
|
.filter(([k, v]) =>
|
||||||
typeof json.errors === "object" &&
|
typeof v === "string" || typeof v === "number"
|
||||||
json.errors.length > 0
|
)
|
||||||
) {
|
.map(([k, v]) => `${k}=${v}`)
|
||||||
// Errors received
|
.join("&");
|
||||||
|
|
||||||
element.classList.add("error");
|
return core.request(
|
||||||
|
"/",
|
||||||
|
urn,
|
||||||
|
)
|
||||||
|
.then((json) => {
|
||||||
|
element.removeAttribute("disabled");
|
||||||
|
element.focus();
|
||||||
|
|
||||||
|
if (
|
||||||
|
json.errors !== null &&
|
||||||
|
typeof json.errors === "object" &&
|
||||||
|
json.errors.length > 0
|
||||||
|
) {
|
||||||
|
// Errors received
|
||||||
|
|
||||||
|
element.classList.add("error");
|
||||||
|
} else {
|
||||||
|
// Errors not received
|
||||||
|
|
||||||
|
history.pushState(
|
||||||
|
{},
|
||||||
|
urn,
|
||||||
|
urn,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof json.title === "string" &&
|
||||||
|
json.title.length > 0
|
||||||
|
) {
|
||||||
|
// Received the page title
|
||||||
|
|
||||||
|
// Initialize a link to the categories list
|
||||||
|
const title = core.main.getElementsByTagName("h2")[0];
|
||||||
|
|
||||||
|
// Write the title
|
||||||
|
title.innerText = json.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deinitialization of the categories
|
||||||
|
const categories = core.main.querySelector(
|
||||||
|
'section[data-catalog-type="categories"]',
|
||||||
|
);
|
||||||
|
// if (categories instanceof HTMLElement) categories.remove();
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof json.html.categories === "string" &&
|
||||||
|
json.html.categories.length > 0
|
||||||
|
) {
|
||||||
|
// Received categories (reinitialization of the categories)
|
||||||
|
|
||||||
|
const categories = core.main.querySelector(
|
||||||
|
'section[data-catalog-type="categories"]',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (categories instanceof HTMLElement) {
|
||||||
|
// Found list of categories
|
||||||
|
|
||||||
|
categories.outerHTML = json.html.categories;
|
||||||
} else {
|
} else {
|
||||||
// Errors not received
|
// Not found list of categories
|
||||||
|
|
||||||
if (
|
const element = document.createElement("section");
|
||||||
typeof json.title === "string" &&
|
|
||||||
json.title.length > 0
|
|
||||||
) {
|
|
||||||
// Received the page title
|
|
||||||
|
|
||||||
// Initialize a link to the categories list
|
const search = core.main.querySelector(
|
||||||
const title =
|
'search[data-section="search"]',
|
||||||
core.main.getElementsByTagName("h2")[0];
|
);
|
||||||
|
|
||||||
// Write the title
|
if (search instanceof HTMLElement) {
|
||||||
title.innerText = json.title;
|
core.main.insertBefore(
|
||||||
|
element,
|
||||||
|
search.nextSibling,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
core.main.append(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deinitialization of the categories
|
element.outerHTML = json.html.categories;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not received categories (deinitialization of the categories)
|
||||||
|
|
||||||
|
const categories = core.main.querySelector(
|
||||||
|
'section[data-catalog-type="categories"',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (categories instanceof HTMLElement) {
|
||||||
|
categories.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof json.html.products === "string" &&
|
||||||
|
json.html.products.length > 0
|
||||||
|
) {
|
||||||
|
// Received products (reinitialization of the products)
|
||||||
|
|
||||||
|
const products = core.main.querySelector(
|
||||||
|
'section[data-catalog-type="products"]',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (products instanceof HTMLElement) {
|
||||||
|
// Found list of products
|
||||||
|
|
||||||
|
products.outerHTML = json.html.products;
|
||||||
|
} else {
|
||||||
|
// Not found list of products
|
||||||
|
|
||||||
|
const element = document.createElement("section");
|
||||||
|
|
||||||
const categories = core.main.querySelector(
|
const categories = core.main.querySelector(
|
||||||
'section[data-catalog-type="categories"]',
|
'section[data-catalog-type="categories"',
|
||||||
);
|
);
|
||||||
// if (categories instanceof HTMLElement) categories.remove();
|
|
||||||
|
|
||||||
if (
|
if (categories instanceof HTMLElement) {
|
||||||
typeof json.html.products === "string" &&
|
//
|
||||||
json.html.products.length > 0
|
|
||||||
) {
|
|
||||||
// Received products (reinitialization of the products)
|
|
||||||
|
|
||||||
const products = core.main.querySelector(
|
//
|
||||||
'section[data-catalog-type="products"]',
|
core.main.insertBefore(
|
||||||
|
element,
|
||||||
|
categories.nextSibling,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (products instanceof HTMLElement) {
|
element.outerHTML = json.html.products;
|
||||||
// Found list of products
|
} else {
|
||||||
|
//
|
||||||
|
|
||||||
products.outerHTML = json.html.products;
|
//
|
||||||
} else {
|
const search = core.main.querySelector(
|
||||||
// Not found list of products
|
'search[data-section="search"]',
|
||||||
|
);
|
||||||
|
|
||||||
const element = document.createElement("section");
|
if (search instanceof HTMLElement) {
|
||||||
|
core.main.insertBefore(
|
||||||
const categories = core.main.querySelector(
|
element,
|
||||||
'section[data-catalog-type="categories"',
|
search.nextSibling,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (categories instanceof HTMLElement) {
|
element.outerHTML = json.html.products;
|
||||||
core.main.insertBefore(
|
|
||||||
element,
|
|
||||||
categories.nextSibling,
|
|
||||||
);
|
|
||||||
|
|
||||||
element.outerHTML = json.html.products;
|
|
||||||
} else {
|
|
||||||
const search = core.main.querySelector(
|
|
||||||
'search[data-section="search"]',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (search instanceof HTMLElement) {
|
|
||||||
core.main.insertBefore(
|
|
||||||
element,
|
|
||||||
search.nextSibling,
|
|
||||||
);
|
|
||||||
|
|
||||||
element.outerHTML = json.html.products;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Not received products (deinitialization of the products)
|
|
||||||
|
|
||||||
const products = core.main.querySelector(
|
|
||||||
'section[data-catalog-type="products"]',
|
|
||||||
);
|
|
||||||
if (products instanceof HTMLElement) {
|
|
||||||
products.remove();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
|
// Not received products (deinitialization of the products)
|
||||||
|
|
||||||
|
const products = core.main.querySelector(
|
||||||
|
'section[data-catalog-type="products"]',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (products instanceof HTMLElement) {
|
||||||
|
products.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,7 +310,10 @@ import("/js/core.js").then(() =>
|
||||||
if (typeof identifier === "string") {
|
if (typeof identifier === "string") {
|
||||||
//
|
//
|
||||||
|
|
||||||
return core.request(`/product/${identifier}`)
|
//
|
||||||
|
const urn = `?product=${identifier}`;
|
||||||
|
|
||||||
|
return core.request(urn)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
if (
|
if (
|
||||||
json.errors !== null &&
|
json.errors !== null &&
|
||||||
|
@ -471,7 +345,7 @@ import("/js/core.js").then(() =>
|
||||||
|
|
||||||
const name = document.createElement("span");
|
const name = document.createElement("span");
|
||||||
name.classList.add("name");
|
name.classList.add("name");
|
||||||
name.setAttribute("title", json.product.identifier);
|
name.setAttribute("title", identifier);
|
||||||
name.innerText = json.product.name;
|
name.innerText = json.product.name;
|
||||||
|
|
||||||
const exit = document.createElement("a");
|
const exit = document.createElement("a");
|
||||||
|
@ -671,8 +545,9 @@ import("/js/core.js").then(() =>
|
||||||
);
|
);
|
||||||
|
|
||||||
history.pushState(
|
history.pushState(
|
||||||
{ product_card: json.product.identifier },
|
{ identifier },
|
||||||
json.product.name,
|
json.product.name,
|
||||||
|
urn,
|
||||||
);
|
);
|
||||||
|
|
||||||
// блокировка закрытия карточки
|
// блокировка закрытия карточки
|
||||||
|
@ -681,7 +556,12 @@ import("/js/core.js").then(() =>
|
||||||
wrap.addEventListener("mousedown", _from);
|
wrap.addEventListener("mousedown", _from);
|
||||||
wrap.addEventListener("touchstart", _from);
|
wrap.addEventListener("touchstart", _from);
|
||||||
|
|
||||||
const remove = () => {
|
const remove = (event) => {
|
||||||
|
if (
|
||||||
|
typeof event === "undefined" ||
|
||||||
|
event.type !== "popstate"
|
||||||
|
) history.back();
|
||||||
|
|
||||||
wrap.remove();
|
wrap.remove();
|
||||||
images.removeEventListener(
|
images.removeEventListener(
|
||||||
"mousedown",
|
"mousedown",
|
||||||
|
|
|
@ -46,13 +46,43 @@ const core = class core {
|
||||||
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(address), { method, headers, body })
|
return await fetch(encodeURI(address), { method, headers, body })
|
||||||
.then((response) => response[type]());
|
.then((response) => type === null || 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Сгенерировать окно выбора действия
|
* Сгенерировать окно выбора действия
|
||||||
*
|
*
|
||||||
|
|
|
@ -27,19 +27,32 @@ import("/js/core.js").then(() =>
|
||||||
// Write to the core
|
// Write to the core
|
||||||
core.session = class session {
|
core.session = class session {
|
||||||
/**
|
/**
|
||||||
* Send data of Telegram program settings to the session
|
* Buffer
|
||||||
*
|
|
||||||
* @return {void}
|
|
||||||
*/
|
*/
|
||||||
static telegram() {
|
static buffer = class buffer {
|
||||||
//
|
/**
|
||||||
const { initData, initDataUnsafe, ...data } = core.telegram.api;
|
* Write to the session 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") {
|
||||||
|
//
|
||||||
|
|
||||||
core.request(
|
// Send request to the server
|
||||||
"/session/write",
|
core.request(
|
||||||
"telegram=" + JSON.stringify(data),
|
"/session/write",
|
||||||
);
|
`${name}=${value}`,
|
||||||
}
|
"POST",
|
||||||
|
{},
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
@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;
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ 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:
|
||||||
|
|
|
@ -25,6 +25,9 @@ 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;
|
||||||
|
@ -39,10 +42,20 @@ 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 {
|
||||||
--offset-x: 2%;
|
container-type: inline-size;
|
||||||
|
container-name: main;
|
||||||
padding: 0 var(--offset-x);
|
padding: 0 var(--offset-x);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -56,8 +69,6 @@ main>section:last-child {
|
||||||
}
|
}
|
||||||
|
|
||||||
main>*[data-section] {
|
main>*[data-section] {
|
||||||
--gap: 16px;
|
|
||||||
--width: calc(100% - var(--gap) * 2);
|
|
||||||
width: var(--width);
|
width: var(--width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +161,7 @@ a[type="button"] {
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2 {
|
h2 {
|
||||||
margin: 28px 0 0;
|
margin: 1rem 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {}
|
footer {}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,19 +2,15 @@
|
||||||
<section class="unselectable" data-section="catalog" data-catalog-type="categories"
|
<section class="unselectable" data-section="catalog" data-catalog-type="categories"
|
||||||
data-catalog-level="{{ level ?? 0 }}">
|
data-catalog-level="{{ level ?? 0 }}">
|
||||||
{% for category in categories %}
|
{% for category in categories %}
|
||||||
{% if category.images %}
|
<a id="{{ category.getId() }}" class="category" type="button"
|
||||||
<a id="{{ category.getId() }}" class="category" type="button" onclick="core.catalog.category(this)" onkeydown="event.keyCode === 13 && core.catalog.category(this)"
|
onclick="core.catalog.parameters.set('category', {{ category.identifier }}); core.catalog.search()"
|
||||||
data-category-identifier="{{ category.identifier }}" tabindex="3">
|
onkeydown="event.keyCode === 13 && (core.catalog.parameters.set('category', {{ category.identifier }}), core.catalog.search())"
|
||||||
<img src="{{ category.images.0.storage }}"
|
tabindex="3">
|
||||||
alt="{{ category.name[language] }}" ondrugstart="return false;">
|
{% if category.images %}
|
||||||
|
<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 %}
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
{% if filters is not empty %}
|
{% if filters is not empty %}
|
||||||
<section class="unselectble" data-section="filters">
|
<section class="unselectble" data-section="filters">
|
||||||
<section class="unselectable" data-type="select" data-filter="brand" tabindex="4">
|
<section class="unselectable" data-type="select" data-filter="brand" tabindex="4">
|
||||||
<input name="brand" type="radio" id="brand_title"{% if session.filters.brand is empty %} checked{% endif %}>
|
{% set cheked_brand = account.buffer.filters.brand ?? session.buffer.filters.brand %}
|
||||||
|
<input name="brand" type="radio" id="brand_title" onclick="core.catalog.parameters.set('brand', null); core.catalog.search()" {% if checked_brand
|
||||||
|
is empty %} checked{% endif %}>
|
||||||
<label for="brand_title" type="button">{{ language == 'ru' ? 'Бренд' : 'Brand' }}</label>
|
<label for="brand_title" type="button">{{ language == 'ru' ? 'Бренд' : 'Brand' }}</label>
|
||||||
<input name="brand" type="radio" id="brand_all">
|
<input name="brand" type="radio" id="brand_all" onclick="core.catalog.parameters.set('brand', null); core.catalog.search()">
|
||||||
<label for="brand_all" type="button">{{ language == 'ru' ? 'Все бренды' : 'All brands' }}</label>
|
<label for="brand_all" type="button">{{ 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 session.filters.brand == brand %} checked{% endif %}>
|
<input name="brand" type="radio" id="brand_{{ loop.index }}" onclick="core.catalog.parameters.set('brand', '{{ brand }}'); core.catalog.search()" {%
|
||||||
|
if brand==checked_brand %} checked{% endif %}>
|
||||||
<label for="brand_{{ loop.index }}" type="button">{{ brand }}</label>
|
<label for="brand_{{ loop.index }}" type="button">{{ brand }}</label>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<search data-section="search">
|
<search data-section="search">
|
||||||
<label><i class="icon search"></i></label>
|
<label><i class="icon search"></i></label>
|
||||||
<input id="search" placeholder="Поиск по каталогу" type="search" onkeyup="event.keyCode !== 9 && core.catalog.search(event, this)" tabindex="1"/>
|
{% set search = account.buffer.search ?? session.buffer.search %}
|
||||||
|
<input id="search" placeholder="Поиск по каталогу" type="search" tabindex="1"
|
||||||
|
onkeyup="event.keyCode === 9 || core.catalog.parameters.set('text', this.value); core.catalog.search(event, this)"{% if search is not empty %} value="{{ search }}"{% endif %} />
|
||||||
</search>
|
</search>
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
|
{% 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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
{% 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 type="text/javascript" src="/js/menu.js"></script>
|
||||||
|
{% endblock %}
|
|
@ -1,14 +0,0 @@
|
||||||
{% 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 %}
|
|
Loading…
Reference in New Issue