languages + close button + import fix + total rebuild

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2024-10-12 19:19:55 +03:00
parent 8de9a0b158
commit 5fa4abba5f
25 changed files with 581 additions and 396 deletions

View File

@ -35,11 +35,15 @@ final class catalog extends core
*/
public function index(array $parameters = []): ?string
{
if (!empty($parameters['category'])) {
// Initializing identifier of a category
preg_match('/[\d]+/', $parameters['identifier'] ?? '', $matches);
$identifier = $matches[0] ?? null;
if (!empty($parameters['identifier'])) {
// Передана категория (идентификатор)
// Инициализация актуальной категории
$category = new category(document: category::_read('d.identifier == @identifier', parameters: ['identifier' => $parameters['category']]));
$category = category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $identifier], errors: $this->errors['catalog']);
if ($category instanceof category) {
// Found the category
@ -84,7 +88,10 @@ final class catalog extends core
// Generating filters
$this->view->filters = [
'brands' => product::collect('d.brand.ru', $this->errors['catalog'])
'brands' => product::collect(
'd.brand.' . $this->language->name,
errors: $this->errors['catalog']
)
];
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
@ -110,7 +117,7 @@ final class catalog extends core
'html' => [
'categories' => $this->view->render('catalog/elements/categories.html'),
'products' => $this->view->render('catalog/elements/products/2columns.html'),
'filters' => $this->view->render('catalog/elemments/filters.html')
'filters' => $this->view->render('catalog/elements/filters.html')
],
'errors' => $this->errors
]
@ -151,6 +158,7 @@ final class catalog extends core
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']
) : [];
@ -167,7 +175,7 @@ final class catalog extends core
[
'title' => $title ?? '',
'html' => [
'products' => $this->view->render('catalog/elements/products.html')
'products' => $this->view->render('catalog/elements/products/2columns.html')
],
'errors' => $this->errors
]
@ -195,53 +203,27 @@ final class catalog extends core
*/
public function product(array $parameters = []): ?string
{
// Initializing of text fore search
preg_match('/[\d]+/', $parameters['id'] ?? '', $matches);
$_key = $matches[0] ?? null;
// Initializing identifier of a product
preg_match('/[\d]+/', $parameters['identifier'] ?? '', $matches);
$identifier = $matches[0] ?? null;
if (!empty($_key)) {
// Received id of prouct (_key)
if (!empty($identifier)) {
// Received identifier of the product
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// POST request
// Search for products
$product = product::read(
filter: "d._key == \"$_key\" && d.deleted != true && d.hidden != true",
filter: "d.identifier == @identifier && d.deleted != true && d.hidden != true",
sort: 'd.created DESC',
amount: 1,
return: '{id: d._key, title: d.title.ru, description: d.description.ru, cost: d.cost, weight: d.weight, dimensions: d.dimensions, brand: d.brand.ru, compatibility: d.compatibility.ru}',
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();
if (!empty($product)) {
// Found the product
// Initializing buffer of images
$images = [];
foreach (
glob(INDEX .
DIRECTORY_SEPARATOR .
'themes' .
DIRECTORY_SEPARATOR .
(THEME ?? 'default') .
DIRECTORY_SEPARATOR .
'images' .
DIRECTORY_SEPARATOR .
$_key .
DIRECTORY_SEPARATOR .
"*.{jpg,png,gif}", GLOB_BRACE) as $file
) {
// Iterate over images of the product
// Write to buffer of images
$images[] = "/images/$_key/" . basename($file);
}
$product = $product + ['images' => $images];
}
// Initializing a response headers
header('Content-Type: application/json');
header('Content-Encoding: none');

View File

@ -10,7 +10,8 @@ use mirzaev\arming_bot\views\templater,
mirzaev\arming_bot\models\account,
mirzaev\arming_bot\models\session,
mirzaev\arming_bot\models\settings,
mirzaev\arming_bot\models\suspension;
mirzaev\arming_bot\models\suspension,
mirzaev\arming_bot\models\enumerations\language;
// Framework for PHP
use mirzaev\minimal\controller;
@ -31,7 +32,7 @@ class core extends controller
/**
* Instance of the settings
*/
public static settings $settings;
protected readonly settings $settings;
/**
* Instance of a session
@ -43,6 +44,11 @@ class core extends controller
*/
protected readonly ?account $account;
/**
* Language
*/
protected language $language = language::en;
/**
* Registry of errors
*/
@ -57,6 +63,8 @@ class core extends controller
* @param bool $initialize Initialize a controller?
*
* @return void
*
* @todo settings account и session не имеют проверок на возврат null
*/
public function __construct(bool $initialize = true)
{
@ -70,7 +78,7 @@ class core extends controller
// Initializing is requested
// Initializing of models core (connect to ArangoDB...)
new models();
new models(true);
// Initializing of the date until which the session will be active
$expires = strtotime('+1 week');
@ -106,10 +114,18 @@ class core extends controller
$this->account = $this->session->account($this->errors['account']);
// Initializing of the settings
self::$settings = settings::active();
$this->settings = settings::active();
// Initializing of language
if ($this->account?->language) $this->language = $this->account->language ?? language::en;
else if ($this->settings?->language) $this->language = $this->settings->language ?? language::en;
// Initializing of preprocessor of views
$this->view = new templater($this->session, $this->account);
$this->view = new templater(
session: $this->session,
account: $this->account,
settings: $this->settings
);
// @todo перенести в middleware
@ -142,20 +158,20 @@ class core extends controller
suspension:
// Write title of the page to templater global variables
$this->view->title = match ($account?->language ?? self::$settings?->language) {
'ru' => 'Приостановлено',
'en' => 'Suspended',
$this->view->title = match ($this->language) {
language::en => 'Suspended',
language::ru => 'Приостановлено',
default => 'Suspended'
};
// Write description of the suspension to templater global variables
$this->view->description = $suspension->description[$account?->language ?? self::$settings?->language] ?? array_values($suspension->description)[0];
$this->view->description = $suspension->description[$this->language] ?? array_values($suspension->description)[0];
// Write message of remaining time of the suspension to templater global variables
$this->view->remain = [
'title' => match ($account?->language ?? self::$settings?->language) {
'ru' => 'Осталось времени: ',
'en' => 'Time remaining: ',
'title' => match ($this->language) {
language::en => 'Time remaining: ',
language::ru => 'Осталось времени: ',
default => 'Time remaining: '
},
'value' => $suspension?->message()

View File

@ -8,7 +8,8 @@ namespace mirzaev\arming_bot\models;
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\status,
mirzaev\arming_bot\models\traits\document as arangodb_document_trait,
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;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
@ -17,6 +18,9 @@ use mirzaev\arangodb\collection,
// Framework for Telegram
use Zanzara\Telegram\Type\User as telegram;
// Library for ArangoDB
use ArangoDBClient\Document as _document;
// Built-in libraries
use exception;
@ -52,10 +56,8 @@ final class account extends core implements arangodb_document_interface
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
// Initialized the collection
try {
// Initializing the account and exit (success)
return new static(
document: collection::execute(
// Initializing the account
$result = collection::execute(
<<<'AQL'
FOR d IN @@collection
FILTER d.identifier == @identifier
@ -66,14 +68,31 @@ final class account extends core implements arangodb_document_interface
'identifier' => $identifier
],
errors: $errors
)
);
} catch (exception $e) {
if ($registration) {
if ($result instanceof _document) {
// Initialized the account
// Initializing the object
$account = new account;
if (method_exists($account, '__document')) {
// Object can implement a document from ArangoDB
// Abstractioning of parameters
$result->language = language::{$result->language} ?? 'en';
// Writing the instance of account document from ArangoDB to the implement object
$account->__document($result);
// Exit (success)
return $account;
}
} else if ($registration) {
// Not found the account and registration is requested
// Creating account
$account = document::write(
$_id = document::write(
static::COLLECTION,
(is_array($registration)
? $registration :
@ -100,7 +119,7 @@ final class account extends core implements arangodb_document_interface
'messages' => $registration->getCanReadAllGroupMessages()
],
'premium' => $registration->isPremium(),
'language' => $registration->getLanguageCode(),
'language' => language::{$registration->getLanguageCode()}->name ?? 'en',
'queries' => [
'inline' => $registration->getSupportsInlineQueries()
]
@ -111,14 +130,13 @@ final class account extends core implements arangodb_document_interface
errors: $errors
);
if ($account) {
if ($_id) {
// Created account
// Initializing of the account (without registration request)
return static::initialize($identifier, errors: $errors);
} else throw new exception('Failed to register account');
} else throw new exception('Failed to find account');
}
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors

View File

@ -10,6 +10,7 @@ use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\category,
mirzaev\arming_bot\models\entry,
mirzaev\arming_bot\models\traits\files,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\traits\yandex\disk as yandex;
// Framework for ArangoDB
@ -52,7 +53,7 @@ final class catalog extends core
* @param int &$products_deleted Counter of deleted products
* @param int &$products_new Counter of new products
* @param int &$products_old Counter of old products
* @param string &$language Language (en, ru...)
* @param language $language Language
* @param array &$errors Registry of errors
*
* @return void
@ -75,7 +76,7 @@ final class catalog extends core
int &$products_deleted = 0,
int &$products_old = 0,
int &$products_new = 0,
string $language = 'en',
language $language = language::en,
array &$errors = []
): void {
try {
@ -112,7 +113,7 @@ final class catalog extends core
try {
if (!empty($row['identifier']) && !empty($row['name'])) {
// All required cells are filled in
// Required cells are filled in
// Incrementing the counter of loaded categories
++$categories_loaded;
@ -123,36 +124,40 @@ final class catalog extends core
// Declaring the variable with the category
$category = null;
try {
// Initializing the category
$category = new category(document: category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['identifier']], errors: $errors)[0] ?? null);
$category = category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['identifier']], errors: $errors);
if ($category instanceof category) {
// Initialized the category
// Initializing name of the category
$category->name[$language] === $row['name'] || $category->name = [[$language => $row['name']]] + $category->name ?? [];
if (empty($category->name) || empty($category->name[$language->name]) || $category->name[$language->name] !== $row['name'])
$category->name = [$language->name => $row['name']] + ($category->name ?? []);
// Initializing position of the category
$category->position === $row['position'] || $category->position = $row['position'];
} catch (exception $e) {
// Not found the category
if (empty($category->position) || $category->position === $row['position'])
$category->position = $row['position'];
} else {
// Not initialized the category
// Creating the category
$_id = category::write((int) $row['identifier'], [$language => $row['name']], $row['position'] ?? null, $errors);
$_id = $created = category::write((int) $row['identifier'], [$language->name => $row['name']], $row['position'] ?? null, $errors);
// Initializing the category
$category = new category(document: $created = category::_read('d._id == @_id', parameters: ['_id' => $_id], errors: $errors)[0] ?? null);
$category = category::_read('d._id == @_id', parameters: ['_id' => $_id], errors: $errors);
// Incrementing the counter of created categories
if ($created) ++$categories_created;
};
if ($category instanceof category) {
// Found the category
// Initialized the category
if (!empty($row['category'])) {
// Received the ascendant category
// Initializing the ascendant category
$ascendant = new category(document: category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['category']], errors: $errors)[0] ?? null);
$ascendant = category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['category']], errors: $errors);
if ($ascendant instanceof category) {
// Found the ascendant category
@ -265,7 +270,7 @@ final class catalog extends core
try {
if (!empty($row['identifier']) && !empty($row['name'])) {
// All required cells are filled in
// Required cells are filled in
// Incrementing the counter of loaded products
++$products_loaded;
@ -276,45 +281,63 @@ final class catalog extends core
// Declaring the variable with the product
$product = null;
try {
// Initializing the product
$product = new product(document: product::_read('d.identifier == %u', parameters: ['identifier' => (int) $row['identifier']], errors: $errors)[0] ?? null);
$product = product::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['identifier']], errors: $errors);
// Initializing name of the category
$product->name[$language] === $row['name'] || $product->name = [[$language => $row['name']]] + $product->name ?? [];
if ($product instanceof product) {
// Initialized the product
// Initializing name of the product
if (empty($product->name) || empty($product->name[$language->name]) || $product->name[$language->name] !== $row['name'])
$product->name = [$language->name => $row['name']] + ($product->name ?? []);
// Initializing description of the product
if (empty($product->description) || empty($product->description[$language->name]) || $product->description[$language->name] !== $row['description'])
$product->description = [$language->name => $row['description']] + ($product->description ?? []);
// Initializing brand of the product
if (empty($product->brand) || empty($product->brand[$language->name]) || $product->brand[$language->name] !== $row['brand'])
$product->brand = [$language->name => $row['brand']] + ($product->brand ?? []);
// Initializing compatibility of the product
if (empty($product->compatibility) || empty($product->brand[$language->name]) || $product->compatibility[$language->name] !== $row['compatibility'])
$product->compatibility = [$language->name => $row['compatibility']] + ($product->compatibility ?? []);
// Initializing position of the product
$product->position === $row['position'] || $product->position = $row['position'];
} catch (exception $e) {
// Not found the product
if (empty($product->position) || $product->position !== $row['position'])
$product->position = $row['position'];
} else {
// Not initialized the product
// Creating the product
$_id = product::write(
(int) $row['identifier'],
[$language => $row['name']],
[$language => $row['description']],
[$language->name => $row['name']],
[$language->name => $row['description']],
(float) $row['cost'],
(float) $row['weight'],
['x' => $row['x'], 'y' => $row['y'], 'z' => $row['z']],
[$language->name => $row['brand']],
[$language->name => $row['compatibility']],
$row['position'] ?? null,
errors: $errors
);
// Initializing the product
$product = new product(document: $created = product::_read(sprintf('d._id == "%s"', $_id), errors: $errors)[0] ?? null);
$product = $created = product::_read('d._id == @_id', parameters: ['_id' => $_id], errors: $errors);
// Incrementing the counter of created products
if ($created) ++$products_created;
}
if ($product instanceof product) {
// Found the product
// Initialized the product
if (!empty($row['category'])) {
// Received the category
// Initializing the category
$category = new category(document: category::_read(sprintf('d.identifier == %u', (int) $row['category']), errors: $errors)[0] ?? null);
$category = category::_read(sprintf('d.identifier == %u', (int) $row['category']), errors: $errors);
if ($category instanceof category) {
// Found the ascendant category
@ -410,7 +433,7 @@ final class catalog extends core
sort: 'd.updated DESC',
amount: 100000,
errors: $errors
) as $document
) ?? [] as $document
) {
// Iterating over categories
@ -444,7 +467,7 @@ final class catalog extends core
sort: 'd.updated DESC',
amount: 100000,
errors: $errors
) as $document
) ?? [] as $document
) {
// Iterating over products

View File

@ -12,7 +12,7 @@ use mirzaev\arangodb\connection as arangodb,
mirzaev\arangodb\collection,
mirzaev\arangodb\enumerations\collection\type;
// Libraries for ArangoDB
// Library for ArangoDB
use ArangoDBClient\Document as _document;
// Built-in libraries
@ -40,6 +40,8 @@ class core extends model
/**
* Instance of the session of ArangoDB
*
* @todo ПЕРЕДЕЛАТЬ В php 8.4
*/
protected static arangodb $arangodb;
@ -61,7 +63,7 @@ class core extends model
*
* @return void
*/
public function __construct(bool $initialize = true, ?arangodb $arangodb = null)
public function __construct(bool $initialize = false, ?arangodb $arangodb = null)
{
// For the extends system
parent::__construct($initialize);
@ -69,17 +71,8 @@ class core extends model
if ($initialize) {
// Initializing is requested
if (isset($arangodb)) {
// Recieved an instance of a session of ArangoDB
// Writing an instance of a session of ArangoDB to the property
$this->__set('arangodb', $arangodb);
} else {
// Not recieved an instance of a session of ArangoDB
// Initializing of an instance of a session of ArangoDB
$this->__get('arangodb');
}
self::$arangodb = $arangodb ?? new arangodb(require static::ARANGODB);
}
}
@ -104,7 +97,7 @@ class core extends model
string $return = 'd',
array $parameters = [],
array &$errors = []
): _document|array|null {
): _document|static|array|null {
try {
if (collection::initialize(static::COLLECTION, static::TYPE)) {
// Initialized the collection
@ -117,22 +110,39 @@ class core extends model
%s
%s
LIMIT @offset, @amount
RETURN @return
RETURN %s
AQL,
empty($filter) ? '' : "FILTER $filter",
empty($sort) ? '' : "SORT $sort",
empty($return) ? 'd' : $return
),
[
'@collection' => static::COLLECTION,
'offset' => --$page <= 0 ? 0 : $page * $amount,
'amount' => $amount,
'return' => $return
'amount' => $amount
] + $parameters,
errors: $errors
);
if ($result instanceof _document) {
// Received only 1 document and
// Initializing the object
$object = new static;
if (method_exists($object, '__document')) {
// Object can implement a document from ArangoDB
// Writing the instance of document from ArangoDB to the implement object
$object->__document($result);
// Exit (success)
return is_array($result) ? $result : [$result];
return $object;
}
}
// Exit (success)
return $result;
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to registry of errors
@ -160,26 +170,9 @@ class core extends model
{
match ($name) {
'arangodb' => (function () use ($value) {
if ($this->__isset('arangodb')) {
// Is alredy initialized
// Exit (fail)
throw new exception('Forbidden to reinitialize the session of ArangoDB ($this::$arangodb)', 500);
} else {
// Is not already initialized
if ($value instanceof arangodb) {
// Recieved an appropriate value
// Writing the property and exit (success)
self::$arangodb = $value;
} else {
// Recieved an inappropriate value
// Exit (fail)
throw new exception('Session of ArangoDB ($this::$arangodb) is need to be mirzaev\arangodb\connection', 500);
}
}
if (isset(static::$arangodb)) throw new exception('Forbidden to reinitialize the session of ArangoDB ($this::$arangodb)', 500);
else if ($value instanceof arangodb) self::$arangodb = $value;
else throw new exception('Session of connection to ArangoDB ($this::$arangodb) is need to be mirzaev\arangodb\connection', 500);
})(),
default => parent::__set($name, $value)
};
@ -195,22 +188,6 @@ class core extends model
public function __get(string $name): mixed
{
return match ($name) {
'arangodb' => (function () {
try {
if (!$this->__isset('arangodb')) {
// Is not initialized
// Initializing of a default value from settings
$this->__set('arangodb', new arangodb(require static::ARANGODB));
}
// Exit (success)
return self::$arangodb;
} catch (exception) {
// Exit (fail)
return null;
}
})(),
default => parent::__get($name)
};
}

View File

@ -227,7 +227,8 @@ final class entry extends core implements arangodb_document_interface
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
// Initialized collections
if ($documents = collection::execute(
// Execute and exit (success)
return is_array($result = collection::execute(
sprintf(
<<<'AQL'
FOR v IN 1..1 INBOUND @document GRAPH @graph
@ -241,18 +242,13 @@ final class entry extends core implements arangodb_document_interface
empty($sort) ? '' : "SORT $sort",
),
[
'grapth' => 'catalog',
'graph' => 'catalog',
'document' => $document->getId(),
'offset' => --$page <= 0 ? $page = 0 : $page * $amount,
'amount' => $amount
],
errors: $errors
)) {
// Fount entries
// Возврат (успех)
return is_array($documents) ? $documents : [$documents];
} else return [];
)) ? $result : [$result];
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . $document::TYPE . ' collection: ' . $document::COLLECTION);
} catch (exception $e) {

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\models\enumerations;
/**
* Types of human languages
*
* @package mirzaev\arming_bot\models\enumerations
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
enum language
{
case en;
case ru;
/**
* Translate label of language
*
* @param language|null $language Language into which to translate
*
* @return string Translated label of language
*
* @todo
* 1. More languages
* 2. Cases???
*/
public function translate(?language $language = language::en): string
{
// Exit (success)
return match ($this) {
language::en => match ($language) {
language::en => 'English',
language::ru => 'Английский'
},
language::ru => match ($language) {
language::en => 'Russian',
language::ru => 'Русский'
}
};
}
}

View File

@ -4,6 +4,14 @@ declare(strict_types=1);
namespace mirzaev\arming_bot\models\enumerations;
/**
* Types of session verification
*
* @package mirzaev\arming_bot\models\enumerations
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
enum session
{
case hash_only;

View File

@ -7,9 +7,6 @@ namespace mirzaev\arming_bot\models\interfaces;
// Library для ArangoDB
use ArangoDBClient\Document as _document;
// Framework for ArangoDB
use mirzaev\arangodb\connection as arangodb;
/**
* Interface for implementing a document instance from ArangoDB
*

View File

@ -6,6 +6,7 @@ namespace mirzaev\arming_bot\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\traits\document as arangodb_document_trait;
// Framework for ArangoDB
@ -41,6 +42,8 @@ final class product extends core
* @param float $cost Cost
* @param float $weight Weight
* @param array $dimensions Dimensions ['x' => 0.0, 'y' => 0.0, 'z' => 0.0]
* @param array|null $brand Brand [['en' => value], ['ru' => значение]]
* @param array|null $compatibility Compatibility [['en' => value], ['ru' => значение]]
* @param array $images Images (first will be thumbnail)
* @param int|null $position Position for sorting in the catalog (ASC)
* @param array $data Data
@ -58,6 +61,8 @@ final class product extends core
float $cost = 0,
float $weight = 0,
array $dimensions = ['x' => 0, 'y' => 0, 'z' => 0],
?array $brand = [['en' => 'ERROR']],
?array $compatibility = [['en' => 'ERROR']],
array $images = [],
?int $position = null,
array $data = [],
@ -81,6 +86,8 @@ final class product extends core
'y' => $dimensions['y'] ?? 0,
'z' => $dimensions['z'] ?? 0,
],
'brand' => $brand,
'compatibility' => $compatibility,
'images' => $images,
'position' => $position,
'version' => ROBOT_VERSION
@ -111,7 +118,8 @@ final class product extends core
* @param int $page Page
* @param int $amount Amount per page
* @param string|null $return Return (AQL)
* @param string $language Language code (en, ru...)
* @param language $language Language code (en, ru...)
* @param array $parameters Binded parameters for placeholders ['placeholder' => parameter]
* @param array &$errors Registry of errors
*
* @return array Массив с найденными товарами (может быть пустым)
@ -123,23 +131,31 @@ final class product extends core
int $page = 1,
int $amount = 100,
?string $return = 'd',
string $language = 'en',
language $language = language::en,
array $parameters = [],
array &$errors = []
): array {
try {
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
// Initialized the collection
// Initializing the query
$aql = <<<'AQL'
FOR d IN @@collection
AQL;
// Initializing parameters for search
if ($search) $parameters += [
'search' => $search,
'analyzer' => 'text_' . $language->name
];
if ($search) {
// Requested search
// Writing to the query
$aql .= <<<'AQL'
// Reading products
$documents = collection::execute(
sprintf(
<<<'AQL'
FOR d IN @@collection %s
%s
%s
LIMIT @offset, @amount
RETURN %s
AQL,
empty($search) ? '' : <<<'AQL'
SEARCH
LEVENSHTEIN_MATCH(
d.name.@language,
@ -159,34 +175,17 @@ final class product extends core
1,
false
)
AQL;
// Adding sorting
if ($sort) $sort = "BM25(d) DESC, $sort";
else $sort = "BM25(d) DESC";
}
// Reading products
$documents = collection::execute(
sprintf(
$aql . <<<'AQL'
%s
%s
LIMIT @offset, @amount
RETURN $s
AQL,
empty($filter) ? '' : "FILTER $filter",
empty($sort) ? '' : "SORT $sort",
empty($search) ? (empty($sort) ? '' : "SORT $sort") : (empty($sort) ? "SORT BM25(d) DESC" : "SORT BM25(d) DESC, $sort"),
empty($return) ? 'd' : $return
),
[
'@collection' => $search ? static::COLLECTION . 's_search' : static::COLLECTION,
'search' => $search,
'language' => $language,
'analyzer' => "text_$language",
'@collection' => empty($search) ? static::COLLECTION : static::COLLECTION . 's_search',
'language' => $language->name,
'offset' => --$page <= 0 ? $page = 0 : $page * $amount,
'amount' => $amount,
],
] + $parameters,
errors: $errors
);
@ -196,7 +195,7 @@ final class product extends core
// Exit (success)
return is_array($documents) ? $documents : [$documents];
} else return [];
} else throw new exception('Failed to initialize ' . static::COLLECTION . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -227,9 +226,9 @@ final class product extends core
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
// Initialized the collection
if ($values = collection::execute(
if ($result = collection::execute(
<<<'AQL'
FOR d IN @@collecton
FOR d IN @@collection
RETURN DISTINCT @parameter
AQL,
[
@ -241,7 +240,7 @@ final class product extends core
// Found parameters
// Exit (success)
return $values;
return is_array($result) ? $result : [$result];
} else return [];
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} catch (exception $e) {

View File

@ -141,7 +141,7 @@ final class session extends core implements arangodb_document_interface
// Initialized collections
// Search for connected account
$document = collection::execute(
$result = collection::execute(
<<<AQL
FOR v IN INBOUND @session GRAPH sessions
SORT v.created DESC
@ -154,17 +154,21 @@ final class session extends core implements arangodb_document_interface
errors: $errors
);
if ($document instanceof _document) {
// Found connected account
if ($result instanceof _document) {
// Found active settings
// Initializing the implement object of the instance of sesson document from ArangoDB
$account = new account;
// Initializing the object
$account = new static;
// Writing the instance of session document from ArangoDB to the implement object
$account->__document($document);
if (method_exists($account, '__document')) {
// Object can implement a document from ArangoDB
// Writing the instance of account document from ArangoDB to the implement object
$account->__document($result);
// Exit (success)
return $account;
}
} else return null;
} else throw new exception('Failed to initialize ' . account::TYPE . ' collection: ' . account::COLLECTION);
} else throw new exception('Failed to initialize ' . connect::TYPE . ' collection: ' . connect::COLLECTION);

View File

@ -7,7 +7,8 @@ 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;
mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface,
mirzaev\arming_bot\models\enumerations\language;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
@ -51,7 +52,7 @@ final class settings extends core implements arangodb_document_interface
// Initialized the collection
// Search for active settings
$document = collection::execute(
$result = collection::execute(
<<<'AQL'
FOR d IN @@collection
FILTER d.status == 'active'
@ -65,17 +66,24 @@ final class settings extends core implements arangodb_document_interface
errors: $errors
);
if ($document instanceof _document) {
if ($result instanceof _document) {
// Found active settings
// Initializing the implement object of the instance of settings document from ArangoDB
// Initializing the object
$settings = new static;
if (method_exists($settings, '__document')) {
// Object can implement a document from ArangoDB
// Abstractioning of parameters
$result->language = language::{$result->language} ?? 'en';
// Writing the instance of settings document from ArangoDB to the implement object
$settings->__document($document);
$settings->__document($result);
// Exit (success)
return $settings;
}
} else if ($create) {
// Not found active settings and requested their creating

View File

@ -6,8 +6,7 @@ namespace mirzaev\arming_bot\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\controllers\core as controller,
mirzaev\arming_bot\models\settings,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\traits\document as arangodb_document_trait,
mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface;
@ -53,7 +52,7 @@ final class suspension extends core implements arangodb_document_interface
// Initialized the collection
// Search for active suspension
$document = collection::execute(
$result = collection::execute(
<<<'AQL'
FOR d IN @@collection
FILTER d.end > @time
@ -68,17 +67,21 @@ final class suspension extends core implements arangodb_document_interface
errors: $errors
);
if ($document instanceof _document) {
// Found active suspension
if ($result instanceof _document) {
// Found active settings
// Initializing the implement object of the instance of suspension document from ArangoDB
// Initializing the object
$suspension = new static;
if (method_exists($suspension, '__document')) {
// Object can implement a document from ArangoDB
// Writing the instance of suspension document from ArangoDB to the implement object
$suspension->__document($document);
$suspension->__document($result);
// Exit (success)
return $suspension;
}
} else return null;
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
@ -98,17 +101,14 @@ final class suspension extends core implements arangodb_document_interface
/**
* Generate message about remaining time
*
* @param string|null $language Language of the generated text (otherwise used from settings.language)
* @param language|null $language Language of the generated text (otherwise used from settings.language)
* @param array &$errors Registry of errors
*
* @return string|null Text: "? days, ? hours and ? minutes"
*/
public function message(?string $language = null, array &$errors = []): ?string
public function message(?language $language = language::en, array &$errors = []): ?string
{
try {
// Initializing default value
$language ??= controller::$settings?->language ?? 'en';
// Initializing the time until the suspension ends
$difference = date_diff(new datetime('@' . $this->document->end), new datetime());
@ -118,54 +118,54 @@ final class suspension extends core implements arangodb_document_interface
$difference->d,
match ($difference->d > 20 ? $difference->d % 10 : $difference->d % 100) {
1 => match ($language) {
'ru' => 'день',
'en' => 'day',
language::en => 'day',
language::ru => 'день',
default => 'day'
},
2, 3, 4 => match ($language) {
'ru' => 'дня',
'en' => 'days',
language::en => 'days',
language::ru => 'дня',
default => 'days'
},
default => match ($language) {
'ru' => 'дней',
'en' => 'days',
language::en => 'days',
language::ru => 'дней',
default => 'days'
}
},
$difference->h,
match ($difference->h > 20 ? $difference->h % 10 : $difference->h % 100) {
1 => match ($language) {
'ru' => 'час',
'en' => 'hours',
language::en => 'hours',
language::ru => 'час',
default => 'hour'
},
2, 3, 4 => match ($language) {
'ru' => 'часа',
'en' => 'hours',
language::en => 'hours',
language::ru => 'часа',
default => 'hours'
},
default => match ($language) {
'ru' => 'часов',
'en' => 'hours',
language::en => 'hours',
language::ru => 'часов',
default => 'hours'
}
},
$difference->i,
match ($difference->i > 20 ? $difference->i % 10 : $difference->i % 100) {
1 => match ($language) {
'ru' => 'минута',
'en' => 'minute',
language::en => 'minute',
language::ru => 'минута',
default => 'minute'
},
2, 3, 4 => match ($language) {
'ru' => 'минуты',
'en' => 'minutes',
language::en => 'minutes',
language::ru => 'минуты',
default => 'minutes'
},
default => match ($language) {
'ru' => 'минут',
'en' => 'minutes',
language::en => 'minutes',
language::ru => 'минут',
default => 'minutes'
}
}

View File

@ -410,7 +410,7 @@ final class telegram extends core
$products_deleted,
$products_old,
$products_new,
language: 'ru'
language: $account->language ?? settings::active()?->language ?? 'en'
);
// Отправка сообщения
@ -489,7 +489,7 @@ final class telegram extends core
$telegram = $ctx->getEffectiveUser();
// Инициализация аккаунта
$account = account::initialization($telegram->getId(), $telegram);
$account = account::initialize($telegram->getId(), $telegram);
if ($account) {
// Инициализирован аккаунт
@ -561,11 +561,11 @@ final class telegram extends core
}
// Инициализация сообщения
$message = "⚠️ *Работа приостановлена*\n*Оставшееся время\:* " . $suspension->message($account->language ?? controller::$settings?->language);
$message = "⚠️ *Работа приостановлена*\n*Оставшееся время\:* " . $suspension->message($account->language ?? settings::active()?->language ?? 'en');
// Добавление описания причины приостановки, если найдена
if (!empty($suspension->description))
$message .= "\n\n" . $suspension->description[$account->language ?? controller::$settings?->language] ?? array_values($suspension->description)[0];
$message .= "\n\n" . $suspension->description[$account->language ?? settings::active()?->language ?? 'en'] ?? array_values($suspension->description)[0];
// Отправка сообщения
$ctx->sendMessage($message)

View File

@ -49,7 +49,7 @@ trait document
parent::__construct($initialize, $arangodb);
// Writing to the property
if ($document instanceof _document) $this->document = $document;
if ($document instanceof _document) $this->__document($document);
else if ($document === null) throw new exception('Failed to initialize an instance of the document from ArangoDB');
}
@ -98,7 +98,6 @@ trait document
{
// Read a property from an instance of the ArangoDB document and exit (success)
return match ($name) {
'arangodb' => core::$arangodb,
default => $this->document->{$name}
};
}

View File

@ -41,8 +41,9 @@ $router
->write('/', 'catalog', 'index', 'GET')
->write('/search', 'catalog', 'search', 'POST')
->write('/session/connect/telegram', 'session', 'telegram', 'POST')
->write('/product/$id', 'catalog', 'product', 'POST')
->write('/$categories...', 'catalog', 'index', 'POST');
->write('/category/$identifier', 'catalog', 'index', 'POST')
->write('/category', 'catalog', 'index', 'POST')
->write('/product/$identifier', 'catalog', 'product', 'POST');
/*

View File

@ -34,16 +34,11 @@ import("/js/core.js").then(() =>
// Write to the core
core.catalog = class catalog {
/**
* Current position in hierarchy of the categories
*/
static categories = [];
/**
* Registry of filters (instead of cookies)
*/
static filters = new Map([
['brand', null]
["brand", null],
]);
/**
@ -56,10 +51,12 @@ import("/js/core.js").then(() =>
* @return {void}
*/
static category(button, clean = true, force = false) {
// Initialize of the new category name
const category = button.getAttribute("data-category-name");
// Initializing identifier of the category
const identifier = button.getAttribute(
"data-category-identifier",
);
this._category(category, clean, force);
this._category(identifier, clean, force);
}
/**
@ -80,22 +77,22 @@ import("/js/core.js").then(() =>
/**
* Select a category (system)
*
* @param {HTMLElement} button Button of category <a>
* @param {string} identifier Identifier of the category
* @param {bool} clean Clear search bar?
*
* @return {Promise} Request to the server
*/
static __category(category = "", clean = true) {
static __category(category, clean = true) {
if (typeof category === "string") {
//
// Received required parameters
let urn;
if (category === "/" || category === "") urn = "/";
else {urn = this.categories.length > 0
? `/${this.categories.join("/")}/${category}`
: `/${category}`;}
return core.request(urn)
return core.request(
"/category" +
("/" + category).replace(/^\/*/, "/").trim().replace(
/\/*$/,
"",
),
)
.then((json) => {
if (
json.errors !== null &&
@ -114,11 +111,6 @@ import("/js/core.js").then(() =>
if (search instanceof HTMLElement) search.value = "";
}
// Write the category to position in the categories hierarchy
if (category !== "/" && category !== "") {
this.categories.push(category);
}
if (
typeof json.title === "string" &&
json.title.length > 0
@ -288,9 +280,6 @@ import("/js/core.js").then(() =>
* @todo add animations of errors
*/
static __search(element) {
// Deinitialization of position in the categories hierarchy
this.categories = [];
return this.__category("/", false)
.then(function () {
core.request("/search", `text=${element.value}`)
@ -392,19 +381,19 @@ import("/js/core.js").then(() =>
/**
* Open product card (interface)
*
* @param {string} id Identifier of a product
* @param {string} identifier Identifier of a product
* @param {bool} force Ignore the damper?
*
* @return {void}
*/
static product(id, force = false) {
this._product(id, force);
static product(identifier, force = false) {
this._product(identifier, force);
}
/**
* Open product card (damper)
*
* @param {string} id Identifier of a product
* @param {string} identifier Identifier of a product
* @param {bool} force Ignore the damper?
*
* @return {void}
@ -418,15 +407,15 @@ import("/js/core.js").then(() =>
/**
* Open product card (system)
*
* @param {string} id Identifier of a product
* @param {string} identifier Identifier of a product
*
* @return {Promise} Request to the server
*/
static __product(id) {
if (typeof id === "number") {
static __product(identifier) {
if (typeof identifier === "number") {
//
return core.request(`/product/${id}`)
return core.request(`/product/${identifier}`)
.then((json) => {
if (
json.errors !== null &&
@ -455,15 +444,18 @@ import("/js/core.js").then(() =>
card.classList.add("card", "unselectable");
const h3 = document.createElement("h3");
h3.setAttribute("title", json.product.id);
const title = document.createElement("span");
title.classList.add("title");
title.innerText = json.product.title;
const name = document.createElement("span");
name.classList.add("name");
name.setAttribute("title", json.product.identifier);
name.innerText = json.product.name;
const brand = document.createElement("small");
brand.classList.add("brand");
brand.innerText = json.product.brand;
const exit = document.createElement("a");
exit.classList.add("exit");
exit.setAttribute("type", "button");
const exit_icon = document.createElement("i");
exit_icon.classList.add("icon", "close");
const images = document.createElement("div");
images.classList.add("images", "unselectable");
@ -528,6 +520,13 @@ import("/js/core.js").then(() =>
images.append(image);
}
const header = document.createElement("p");
header.classList.add('header');
const brand = document.createElement("small");
brand.classList.add("brand");
brand.innerText = json.product.brand;
const description = document.createElement("p");
description.classList.add("description");
description.innerText = json.product.description;
@ -542,10 +541,24 @@ import("/js/core.js").then(() =>
const dimensions = document.createElement("small");
dimensions.classList.add("dimensions");
dimensions.innerText = json.product.dimensions.x +
"x" +
json.product.dimensions.y + "x" +
json.product.dimensions.z;
const x = json.product.dimensions.x;
const y = json.product.dimensions.y;
const z = json.product.dimensions.z;
let formatted = "";
if (x !== '' ) formatted = x;
if (y !== '') {
if (formatted.length === 0) formatted = y;
else formatted += "x" + y;
}
if (z !== '') {
if (formatted.length === 0) formatted = z;
else formatted += "x" + z;
}
dimensions.innerText = formatted;
const weight = document.createElement("small");
weight.classList.add("weight");
@ -555,10 +568,13 @@ import("/js/core.js").then(() =>
cost.classList.add("cost");
cost.innerText = json.product.cost + "р";
h3.append(title);
h3.append(brand);
h3.append(name);
exit.append(exit_icon);
h3.append(exit);
card.append(h3);
card.append(images);
header.append(brand);
card.append(header);
card.append(description);
card.append(compatibility);
footer.append(dimensions);
@ -581,8 +597,8 @@ import("/js/core.js").then(() =>
);
history.pushState(
{ product_card: json.product.id },
json.product.title,
{ product_card: json.product.identifier },
json.product.name,
);
// блокировка закрытия карточки
@ -595,6 +611,8 @@ import("/js/core.js").then(() =>
wrap.remove();
wrap.removeEventListener("mousedown", _from);
wrap.removeEventListener("touchstart", _from);
exit.removeEventListener("click", remove);
exit.removeEventListener("touch", remove);
document.removeEventListener("click", close);
document.removeEventListener("touch", close);
window.removeEventListener("popstate", remove);
@ -615,13 +633,15 @@ import("/js/core.js").then(() =>
from = undefined;
};
exit.addEventListener("click", remove);
exit.addEventListener("touch", remove);
document.addEventListener("click", close);
document.addEventListener("touch", close);
window.addEventListener("popstate", remove);
if (width > card.offsetWidth) {
images.hotline = new core.hotline(
json.product.id,
json.product.identfier,
images,
);
images.hotline.step = -0.3;

View File

@ -8,9 +8,10 @@ main>section[data-section="catalog"] {
}
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"] {
--padding: 0.7rem;
position: relative;
height: 23px;
padding: unset;
padding: var(--padding);
display: flex;
justify-content: center;
align-items: center;
@ -27,7 +28,9 @@ main>section[data-section="catalog"][data-catalog-type="categories"]:last-child
}
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 {
@ -37,7 +40,8 @@ main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[
width: 110%;
height: 110%;
object-fit: cover;
filter: blur(1px);
/* filter: blur(1px); */
filter: brightness(60%);
}
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:hover>img {
@ -45,14 +49,12 @@ main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[
}
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:has(>img)>p {
--padding: 0.7rem;
position: absolute;
left: var(--padding);
bottom: var(--padding);
right: var(--padding);
margin: unset;
width: min-content;
padding: var(--padding);
border-radius: 0.75rem;
background: var(--tg-theme-secondary-bg-color);
}

View File

@ -0,0 +1,30 @@
@charset "UTF-8";
i.icon.close {
--diameter: 22px;
box-sizing: border-box;
position: relative;
display: block;
width: var(--diameter);
height: var(--diameter);
border: 2px solid transparent;
border-radius: 40px;
}
i.icon.close::after,
i.icon.close::before {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
width: 16px;
height: 2px;
background: currentColor;
transform: rotate(45deg);
border-radius: 5px;
top: 8px;
left: 1px;
}
i.icon.close::after {
transform: rotate(-45deg);
}

View File

@ -108,12 +108,15 @@ search:has(input:disabled) {
backdrop-filter: contrast(0.5);
}
*[type="button"] {
cursor: pointer;
}
:is(button, a[type="button"]) {
padding: 8px 16px;
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

@ -2,7 +2,7 @@
section#window {
z-index: 1500;
position: absolute;
position: fixed;
width: 100vw;
height: 100vh;
display: flex;
@ -24,7 +24,7 @@ section#window>div.card {
section#window>div.card>h3 {
margin: 0 0 0.5rem 0;
height: 23px;
padding: 1rem 1.5rem;
padding: 1rem 0;
display: inline-flex;
align-items: center;
gap: 1rem;
@ -32,16 +32,29 @@ section#window>div.card>h3 {
background-color: var(--tg-theme-header-bg-color);
}
section#window>div.card>h3>span.title {
section#window>div.card>h3>span.name {
margin-left: 1.5rem;
overflow: hidden;
text-overflow: ellipsis;
}
section#window>div.card>h3>small.brand {
/* section#window>div.card>h3>small.brand {
margin-left: auto;
font-size: 0.8rem;
font-weight: normal;
color: var(--tg-theme-section-header-text-color);
} */
section#window>div.card>h3>a.exit[type="button"] {
margin-left: auto;
margin-right: 0.5rem;
padding: 0.6rem;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
color: var(--tg-theme-section-header-text-color);
background-color: unset;
}
section#window>div.card>div.images {

View File

@ -16,7 +16,8 @@ use mirzaev\minimal\controller;
use Twig\Loader\FilesystemLoader,
Twig\Environment as twig,
Twig\Extra\Intl\IntlExtension as intl,
Twig\TwigFilter;
Twig\TwigFilter,
Twig\TwigFunction;
// Built-in libraries
@ -43,13 +44,17 @@ final class templater extends controller implements ArrayAccess
/**
* Constructor of an instance
*
* @param session|null $session An object implementing a session instance from ArangoDB
* @param account|null $account An object implementing an account instance from ArangoDB
* @param session|null $session The object implementing a session instance from ArangoDB
* @param account|null $account The object implementing an account instance from ArangoDB
* @param settings|null $settings The object implementing an account instance from ArangoDB
*
* @return void
*/
public function __construct(?session $session = null, ?account $account = null)
{
public function __construct(
?session $session = null,
?account $account = null,
?settings $settings = null
) {
// Initializing of an instance of twig
$this->twig = new twig(new FilesystemLoader(VIEWS));
@ -57,9 +62,41 @@ final class templater extends controller implements ArrayAccess
$this->twig->addGlobal('theme', 'default');
$this->twig->addGlobal('server', $_SERVER);
$this->twig->addGlobal('cookies', $_COOKIE);
$this->twig->addGlobal('settings', settings::active());
$this->twig->addGlobal('settings', $settings);
if (!empty($session?->status())) $this->twig->addGlobal('session', $session);
if (!empty($account?->status())) $this->twig->addGlobal('account', $account);
$this->twig->addGlobal('language', $account?->language->name ?? $settings?->language->name ?? 'en');
// Initialize function of dimensions formattinx
$this->twig->addFunction(
new TwigFunction(
'format_dimensions',
function (
string|int|float|null $x = null,
string|int|float|null $y = null,
string|int|float|null $z = null,
string|null $before = null,
string|null $after = null
) {
// Initialzing the buffer of result
$result = '';
// Generating
if (!empty($x)) $result .= $x;
if (!empty($y))
if (empty($result)) $result = "$y";
else $result .= "x$y";
if (!empty($z))
if (empty($result)) $result = "$z";
else $result .= "x$z";
if (!empty($result)) $result = "$before$result$after";
// Exit (success)
return $result;
}
)
);
// Initializing of twig extensions
$this->twig->addExtension(new intl());

View File

@ -2,11 +2,19 @@
<section class="unselectable" data-section="catalog" data-catalog-type="categories"
data-catalog-level="{{ level ?? 0 }}">
{% for category in categories %}
{% if category.images %}
<a id="{{ category.getId() }}" class="category" type="button" onclick="return core.catalog.category(this);"
data-category-identifier="{{ category.identifier }}">
<img src="{{ category.images.0.storage }}" alt="{{ category.name[ account.language ?? settings.language ?? 'en' ] }}" ondrugstart="return false;">
<p>{{ category.name[ account.language ?? settings.language ?? 'en' ] }}</p>
<img src="{{ category.images.0.storage }}"
alt="{{ category.name[language] }}" ondrugstart="return false;">
<p>{{ category.name[language] }}</p>
</a>
{% else %}
<a id="{{ category.getId() }}" class="category" type="button" onclick="return core.catalog.category(this);"
data-category-identifier="{{ category.identifier }}">
<p>{{ category.name[language] }}</p>
</a>
{% endif %}
{% endfor %}
</section>
{% endif %}

View File

@ -1,10 +1,9 @@
{% macro card(product) %}
{% set title = product.title.ru ~ ' ' ~ product.brand.ru ~ ' ' ~ product.dimensions.x ~ 'x' ~ product.dimensions.y ~ 'x'
~ 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">
<a onclick="core.catalog.product({{ product.getKey() }})">
<img src="{{ product.images.0.storage }}" alt="{{ product.title.ru }}" ondrugstart="return false;">
<p class="title" title="{{ product.title.ru }}">
<a onclick="core.catalog.product({{ product.identifier }})">
<img src="{{ product.images.0.storage }}" alt="{{ product.name[language] }}" ondrugstart="return false;">
<p class="title" title="{{ product.name[language] }}">
{{ title | length > 45 ? title | slice(0, 45) ~ '...' : title }}
</p>
</a>
@ -13,7 +12,6 @@
</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">

View File

@ -5,6 +5,7 @@
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/catalog/2columns.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/close.css" />
{% endblock %}
{% block main %}