This commit is contained in:
root 2022-10-13 07:07:04 +03:00
parent 30fd8f0ec3
commit 6d6712e9d2
21 changed files with 590 additions and 98 deletions

64
composer.lock generated
View File

@ -12,7 +12,7 @@
"source": {
"type": "git",
"url": "https://git.hood.su/mirzaev/minimal",
"reference": "b6f90b700116f20fe48725166ddfb8f6d27ae52d"
"reference": "7777d7af1733d661a36551a0fdcf27a972e4ef81"
},
"require": {
"php": "~8.0"
@ -48,25 +48,28 @@
"docs": "https://git.hood.su/mirzaev/minimal/manual",
"issues": "https://git.hood.su/mirzaev/minimal/issues"
},
"time": "2021-11-12T13:20:13+00:00"
"time": "2022-03-03T21:15:52+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.23.0",
"version": "v1.25.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce"
"reference": "30885182c981ab175d4d034db0f6f469898070ab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce",
"reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab",
"reference": "30885182c981ab175d4d034db0f6f469898070ab",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
@ -81,12 +84,12 @@
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -111,7 +114,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0"
},
"funding": [
{
@ -127,25 +130,28 @@
"type": "tidelift"
}
],
"time": "2021-02-19T12:13:01+00:00"
"time": "2021-10-20T20:35:02+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.23.1",
"version": "v1.25.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6"
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6",
"reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825",
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
@ -160,12 +166,12 @@
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -191,7 +197,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0"
},
"funding": [
{
@ -207,20 +213,20 @@
"type": "tidelift"
}
],
"time": "2021-05-27T12:26:48+00:00"
"time": "2021-11-30T18:21:41+00:00"
},
{
"name": "twig/twig",
"version": "v3.3.3",
"version": "v3.3.8",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "a27fa056df8a6384316288ca8b0fa3a35fdeb569"
"reference": "972d8604a92b7054828b539f2febb0211dd5945c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a27fa056df8a6384316288ca8b0fa3a35fdeb569",
"reference": "a27fa056df8a6384316288ca8b0fa3a35fdeb569",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/972d8604a92b7054828b539f2febb0211dd5945c",
"reference": "972d8604a92b7054828b539f2febb0211dd5945c",
"shasum": ""
},
"require": {
@ -271,7 +277,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.3.3"
"source": "https://github.com/twigphp/Twig/tree/v3.3.8"
},
"funding": [
{
@ -283,7 +289,7 @@
"type": "tidelift"
}
],
"time": "2021-09-17T08:44:23+00:00"
"time": "2022-02-04T06:59:48+00:00"
}
],
"packages-dev": [],
@ -298,5 +304,5 @@
"php": "^8.0.0"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
"plugin-api-version": "2.2.0"
}

View File

@ -66,11 +66,15 @@ final class books_controller extends core
// Инициализация журнала ошибок
$vars['errors'] = [];
if (accounts::init(errors: $vars['errors'])->access('books')) {
// Найден и авторизован аккаунт
if (count($books = books::import($files['books'] ?? [], errors: $vars['errors'])) > 0) {
// Загружены книги
} else {
// Не загружены книги
}
}
// Перенаправление
header('Location: /books', response_code: 303);
@ -122,5 +126,66 @@ final class books_controller extends core
*/
public function delete(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
if (accounts::init(errors: $vars['errors'])->access('books')) {
// Найден и авторизован аккаунт
if (isset($vars['id'])) {
// Найдены обязательные входные параметры
if (books::delete((int) $vars['id'], $vars['errors'])) {
// Удалена книга из базы данных
// Инициализация пути до книги
$book = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $vars['id'];
if (file_exists($book)) {
// Найдена книга
// Удаление книги
exec('rm -rf ' . escapeshellarg($book));
// Запись статуса выполнения в буфер вывода
$status = true;
}
}
}
}
return json_encode([
'status' => $status ?? false,
'errors' => $vars['errors']
]);
}
/**
* Поворот
*
* @param array $vars
*
* @return string|null JSON
*/
public function rotate(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
if (accounts::init(errors: $vars['errors'])->access('books')) {
// Найден и авторизован аккаунт
if (isset($vars['id'], $vars['page'])) {
// Найдены обязательные входные параметры
// Поворот страницы
$status = books::rotate((int) $vars['id'], (int) $vars['page'], $vars['errors']);
}
}
return json_encode([
'status' => $status ?? false,
'errors' => $vars['errors']
]);
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\controllers;
use mirzaev\surikovlib\controllers\core;
use mirzaev\surikovlib\models\accounts_model as accounts;
use Twig\Environment as view;
/**
* Контроллер страницы "контакты"
*
* @package mirzaev\surikovlib\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class contacts_controller extends core
{
public function index(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'contacts' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\controllers;
use mirzaev\surikovlib\controllers\core;
use mirzaev\surikovlib\models\accounts_model as accounts;
use Twig\Environment as view;
/**
* Контроллер страницы "кеменов"
*
* @package mirzaev\surikovlib\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class kemenov_controller extends core
{
public function index(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'kemenov' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\controllers;
use mirzaev\surikovlib\controllers\core;
use mirzaev\surikovlib\models\accounts_model as accounts;
use Twig\Environment as view;
/**
* Контроллер страницы "суриков"
*
* @package mirzaev\surikovlib\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class surikov_controller extends core
{
public function index(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'surikov' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
}

View File

@ -33,7 +33,7 @@ final class accounts_model extends core
/**
* Хеш
*/
public string $hash;
public ?string $hash;
/**
* Время активности хеша
@ -361,21 +361,17 @@ final class accounts_model extends core
* Проверить разрешение
*
* @param string $permission Разрешение
* @param int|null $id Идентификатор аккаунта
* @param array &$errors Журнал ошибок
*
* @return bool|null Статус разрешения, если оно записано
*/
public static function access(string $permission, int|null $id = null, array &$errors = []): ?bool
public function access(string $permission, array &$errors = []): ?bool
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
// Инициализация аккаунта
$account = isset($id) ? self::read(['id' => $id], $errors) : self::account($errors);
return isset($account->permissions[$permission]) ? (bool) $account->permissions[$permission] : null;
return isset($this->permissions[$permission]) ? (bool) $this->permissions[$permission] : null;
} catch (exception $e) {
// Запись в журнал ошибок
$errors['account'][]= [

View File

@ -17,12 +17,62 @@ use exception;
*/
final class books_model extends core
{
/**
* Запись в базу данных
*
* @param string $title Название
* @param string|null $description Описание
* @param int|null $account Аккаунт (идентификатор)
* @param array &$errors Журнал ошибок
*
* @return int|null Идентификатор записанной книги
*/
public static function write(string $title, ?string $description = null, ?int $account = null, array &$errors = []): ?int
{
// Инициализация журнала ошибок
$errors['books'] ?? $errors['books'] = [];
try {
// Инициализация аккаунта
$account = accounts::init($account, $errors);
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `books` (`account`, `title`, `description`) VALUES (:account, :title, :description)");
// Инициализация параметров
$params = [
':account' => $account->id,
':title' => $title,
':description' => $description
];
// Отправка запроса
$request->execute($params);
if ($id = static::$db->lastInsertId()) {
// Получен идентификатор загруженной книги (подразумевается)
return (int) $id;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['books'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Чтение
*
* @param array $expression Выражение поиска
* @param int $limit Ограничение по количеству
* @param int $page Страница (сдвиг)
* @param int $page Страница (для списка книг)
* @param array &$errors Журнал ошибок
*
* @return array Книги
@ -76,48 +126,26 @@ final class books_model extends core
}
/**
* Запись в базу данных
* Удаление из базы данных
*
* @param string $title Название
* @param string|null $description Описание
* @param int|null $account Аккаунт (идентификатор)
* @param int $id Идентификатор
* @param array &$errors Журнал ошибок
*
* @return int|null Идентификатор записанной книги
* @return bool Статус выполнения
*/
public static function write(string $title, ?string $description = null, ?int $account = null, array &$errors = []): ?int
public static function delete(int $id, array &$errors = []): bool
{
// Инициализация журнала ошибок
$errors['books'] ?? $errors['books'] = [];
try {
// Инициализация аккаунта
$account = accounts::init($account, $errors);
if (empty($account) || !accounts::access('books', $account->id)) {
// Не удалось найти аккаунт или разрешение на управление книгами не выдано
throw new exception('У вас нет разрешения на управление книгами');
}
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `books` (`account`, `title`, `description`) VALUES (:account, :title, :description)");
// Инициализация параметров
$params = [
':account' => $account->id,
':title' => $title,
':description' => $description
];
$request = static::$db->prepare("DELETE FROM `books` WHERE `id` = :id LIMIT 1");
// Отправка запроса
$request->execute($params);
$request->execute([':id' => $id]);
if ($id = static::$db->lastInsertId()) {
// Получен идентификатор загруженной книги (подразумевается)
return (int) $id;
}
return true;
} catch (exception $e) {
// Запись в журнал ошибок
$errors['books'][] = [
@ -128,7 +156,7 @@ final class books_model extends core
];
}
return null;
return false;
}
/**
@ -155,12 +183,6 @@ final class books_model extends core
// Инициализация аккаунта
$account = accounts::init($account, $errors);
if (empty($account) || !accounts::access('books', $account->id)) {
// Не найден аккаунт или разрешение на управление книгами не выдано
throw new exception('У вас нет разрешения на управление книгами');
}
// Инициализация буфера инициализированных книг
$initialized = [];
@ -238,6 +260,66 @@ final class books_model extends core
return $writed ?? [];
}
/**
* Чтение
*
* @param int $id Идентификатор
* @param int $page Страница (сдвиг)
* @param array &$errors Журнал ошибок
*
* @return bool Статус выполнения
*/
public static function rotate(int $id, int $page = 1, array &$errors = []): bool
{
// Инициализация журнала ошибок
$errors['books'] ?? $errors['books'] = [];
try {
// Инициализация запроса
$request = static::$db->prepare("SELECT EXISTS (SELECT * FROM `books` WHERE `id` = :id LIMIT 1)");
// Отправка запроса
$request->execute([':id' => $id]);
if ($request->fetch(pdo::FETCH_NUM)[0] === 1) {
// Найдена книга
// Инициализация пути книги
$book = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $id;
// Инициализация пути до файла
$file = $book . DIRECTORY_SEPARATOR . "$page.jpg";
// Инициализация пути до нового местоположения файла
$old = $book . DIRECTORY_SEPARATOR . 'old' . DIRECTORY_SEPARATOR . "$page.jpg";
// Инициализация директории
if (!file_exists($book)) throw new exception('Не удалось найти директорию книги');
// Инициализация директории оригинальных изображений
if (!file_exists($book . DIRECTORY_SEPARATOR . 'old')) if (!mkdir($book . DIRECTORY_SEPARATOR . 'old', 0755, true)) throw new exception('Не удалось записать директорию для оригинальных страниц книги');
// Перемещение страницы в директорию оригинальных страниц
exec("mv $file $old");
// Переворачивание файла и возвращение на нужное местоположение
exec("jpegtran -rotate 90 $old > $file");
return true;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['books'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return false;
}
/**
* Подсчёт количества страниц
*

View File

@ -68,6 +68,7 @@
#authentication>div#account>p>b {
margin-right: 8px;
font-weight: 900;
}
#authentication>div#account>p>span {

View File

@ -26,6 +26,50 @@ main>section#books>article.book {
margin-right: 20px;
display: flex;
flex-direction: column;
border-radius: 3px;
overflow: hidden;
background: #b38b8b;
transition: .1s ease-in-out;
font-weight: 900;
}
main>section#books>article.book:hover {
scale: 1.05;
z-index: 1000;
}
main>section#books>article.book>button {
position: absolute;
display: flex;
top: 10px;
right: 10px;
width: 25px;
height: 25px;
flex-direction: column;
justify-content: center;
font-size: 110%;
opacity: 0;
pointer-events: none;
border-radius: 3px;
border: none;
color: #ff5757;
background-color: rgba(0, 0, 0, .5);
}
main>section#books>article.book:hover>button {
opacity: 1;
pointer-events: all;
transition: .2s ease-in-out;
}
main>section#books>article.book>button:hover {
color: #ff6b6b;
background-color: rgba(0, 0, 0, .4);
}
main>section#books>article.book>button:active {
color: #e04d4d;
background-color: rgba(0, 0, 0, .6);
}
main>section#books>article.book:nth-child(3n) {
@ -42,7 +86,8 @@ main>section#books>article.book>img {
height: 100%;
object-fit: cover;
object-position: right;
overflow: hidden;
text-align: center;
/* overflow: hidden; */
/* clip-path: polygon(5px calc(100% - 5px), calc(100% - 5px) calc(100% - 5px), calc(100% - 5px) 5px, 5px 5px); */
}
@ -58,8 +103,16 @@ main>section#books>article.book>h4 {
-moz-hyphens: auto;
-ms-hyphens: auto;
hyphens: auto;
color: #e8e8e8;
background-color: rgba(0, 0, 0, .5);
}
main>section#books>article.book>h4:hover {
color: #fff;
background: rgba(0, 0, 0, 50%);
}
main>section#books>article.book>h4:active {
color: #e7e4e4;
}
main>section#books>article.book>p {
@ -96,11 +149,35 @@ main>section#book>nav>ul>li:only-of-type {
margin-right: auto;
}
main>section#book>nav>ul>li.previous {
margin-left: auto;
main>section#book>nav>ul>li {
margin-right: 15px;
}
main>section#book>nav>ul>li:first-of-type,
main>section#book>nav>ul>li.previous {
margin-left: auto;
}
main>section#book>nav>ul>li:last-of-type,
main>section#book>nav>ul>li.next {
margin-right: auto;
}
main>section#book>nav>ul>li.icon {
display: flex;
}
main>section#book>nav>ul>li.icon>i {
height: 1rem;
margin: auto;
color: #9b3d10;
}
main>section#book>nav>ul>li.icon>i:hover {
color: #bd4f1c;
}
main>section#book>nav>ul>li.icon>i:active {
color: #813410;
}

View File

@ -1,6 +1,15 @@
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:wght@200;400;500;600;700&display=swap');
* {
font-family: "open sans";
font-family: 'IBM Plex Serif', serif;
text-decoration: none;
transition: .2s ease-out;
}
button,
*[type="button"] {
cursor: pointer;
font-weight: 900;
}
::selection,
@ -12,7 +21,8 @@ body {
margin: 0;
display: grid;
grid-template-rows: auto auto 150px;
grid-template-columns: minmax(100px, auto) 300px minmax(500px, auto) minmax(100px, auto);;
/* grid-template-columns: minmax(100px, auto) 300px minmax(500px, auto) minmax(100px, auto); */
grid-template-columns: minmax(100px, auto) 300px minmax(500px, 1000px) minmax(100px, auto);
background-color: #e5ddd1;
}
@ -112,6 +122,7 @@ header>.menu>nav>.link {
margin-right: 50px;
text-decoration: none;
color: #e5ddd1;
font-weight: 900;
}
header>.menu>nav>.link:last-child {

View File

@ -0,0 +1,7 @@
main>section#page>article *:first-child {
margin-top: 0;
}
main>section#page>article *:last-child {
margin-bottom: 0;
}

View File

@ -8,12 +8,16 @@ use mirzaev\minimal\core;
use mirzaev\minimal\router;
define('VIEWS', realpath('..' . DIRECTORY_SEPARATOR . 'views'));
define('STORAGE', '..' . DIRECTORY_SEPARATOR . 'storage');
define('STORAGE', realpath('..' . DIRECTORY_SEPARATOR . 'storage'));
define('TYPE', 'mysql');
define('BASE', 'surikovlib');
define('HOST', '127.0.0.1');
define('LOGIN', 'root');
define('PASSWORD', '');
define('PASSWORD', 'sUrikov_topchik_228!');
ini_set('error_reporting', E_ALL);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
// Автозагрузка
require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
@ -29,9 +33,14 @@ $router->write('/account/deauthentication', 'accounts', 'deauthentication', 'POS
$router->write('/account/deauthentication', 'accounts', 'deauthentication', 'GET');
$router->write('/books', 'books', 'index', 'GET');
$router->write('/books/$id', 'books', 'index', 'GET');
$router->write('/books/$id/cover', 'books', 'cover', 'GET');
$router->write('/books/$id/0', 'books', 'cover', 'GET');
$router->write('/books/write', 'books', 'write', 'POST');
$router->write('/books/$id/$page', 'books', 'index', 'GET');
$router->write('/books/$id/$page/rotate', 'books', 'rotate', 'POST');
$router->write('/books/$id/delete', 'books', 'delete', 'POST');
$router->write('/storage/books/$id/$file', 'books', 'read', 'GET');
$router->write('/storage/books/write', 'books', 'write', 'POST');
$router->write('/kemenov', 'kemenov', 'index', 'GET');
$router->write('/surikov', 'surikov', 'index', 'GET');
$router->write('/contacts', 'contacts', 'index', 'GET');
// Инициализация ядра
$core = new core(namespace: __NAMESPACE__, router: $router);

View File

@ -0,0 +1,25 @@
'use strict';
function remove(id, element = null) {
if (typeof id === 'number') {
// Получены входные параметры
// Запрос
fetch(`https://surikovlib.ru/books/${id}/delete`, {
method: 'POST'
}).then(
(value) => {
return value.json();
}
).then(
(response) => {
if (response.status === true) {
// Удалена книга
// Удаление элемента
if (typeof element === 'object') element.remove();
}
}
);
}
}

View File

@ -0,0 +1,24 @@
'use strict';
function rotate(id, page, reload = false) {
if (typeof id === 'number' && typeof page === 'number') {
// Получены входные параметры
// Запрос
fetch(`https://surikovlib.ru/books/${id}/${page}/rotate`, {
method: 'POST'
}).then(
(value) => {
return value.json();
}
).then(
(response) => {
if (response.status === true) {
// Перевёрнута страница
if (reload === true) location.reload();
}
}
);
}
}

View File

@ -5,13 +5,23 @@
<section id="book">
<h2>{{ book.title|e }}</h2>
<img class="unselectable" src="/storage/books/{{ book.id|e }}/{{ page|e }}" alt='Страница отсутствует'>
<img id="page" class="unselectable" src="/storage/books/{{ book.id|e }}/{{ page|e }}" alt='Страница отсутствует'>
<nav>
<ul>
{% if page != 0 %}
<li class="previous unselectable" type="button"><a href="/books/{{ book.id|e }}/{{ page|e - 1 }}" title="Страница №{{ page|e - 1 }}">Назад</a></li>
<li class="previous unselectable" type="button"><a href="/books/{{ book.id|e }}/{{ page|e - 1 }}"
title="Страница №{{ page|e - 1 }}">Назад</a></li>
{% endif %}
<li class="next unselectable" type="button"><a href="/books/{{ book.id|e }}/{{ page|e + 1 }}" title="Страница №{{ page|e + 1 }}">Вперёд</a></li>
{% if account.permissions.books is defined and account.permissions.books == 1 %}
<li class="middle icon unselectable"><i class="fa-solid fa-rotate-right unselectable" type="button"
onclick="rotate({{ book.id|e }}, {{ page|e }}, true)" title="Повернуть страницу"></i></li>
<script type="text/javascript" src="/js/rotate.js"></script>
{% else %}
<li class="middle icon unselectable"><i class="fa-solid fa-rotate-right unselectable" type="button"
onclick="const image = document.getElementById('page'); image.style.rotate = ((isNaN(buffer = parseInt(image.style.rotate)) ? 0 : buffer) + 90) + 'deg'; (((isNaN(buffer) ? 0 : buffer) / 90) % 2) === 0 ? image.style.margin = '15% 0px 20% 0px' : image.style.margin = null" title="Повернуть страницу"></i></li>
{% endif %}
<li class="next unselectable" type="button"><a href="/books/{{ book.id|e }}/{{ page|e + 1 }}"
title="Страница №{{ page|e + 1 }}">Вперёд</a></li>
</ul>
</nav>
</section>

View File

@ -15,9 +15,15 @@
{% endif %}
{% for book in books %}
<article class="book">
{% if account.permissions.books is defined and account.permissions.books == 1 %}
<button onclick="return remove({{ book.id|e }}, this.parentElement);" title="Удалить"><i class="fa-solid fa-xmark"></i></button>
{% endif %}
<img src="/storage/books/{{ book.id|e }}/0" class="unselectable" alt='Обложка книги "{{ book.title|e }}"'>
<h4><a href="/books/{{ book.id|e }}">{{ book.title|e }}</a></h3>
</article>
{% endfor %}
{% if account.permissions.books is defined and account.permissions.books == 1 %}
<script type="text/javascript" src="/js/delete.js"></script>
{% endif %}
</section>
{% endblock %}

View File

@ -1,8 +1,8 @@
<header class="unselectable">
<section class="menu">
<nav>
<a class="link" href="/surikov" title="Архивный фонд">Кеменов</a>
<a class="link" href="/kemenev" title="Список книг">Суриков</a>
<a class="link" href="/kemenov" title="Архивный фонд">Кеменов</a>
<a class="link" href="/surikov" title="Список книг">Суриков</a>
<a id="logo" href="/" title="Главная страница">
<img src="/img/surikovlib_logo_1_white.svg">
</a>

View File

@ -1,9 +1,5 @@
<script type="text/javascript" src="/js/auth.js"></script>
<script type="text/javascript" src="/js/tasks.js"></script>
<script src="https://kit.fontawesome.com/d7e922c226.js" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script type="text/javascript" src="/js/bootstrap/bootstrap.min.js"></script>
<script type="text/javascript" src="/js/bootstrap/forms_validator.js"></script>
<script src="https://kit.fontawesome.com/d67f03b1ae.js" crossorigin="anonymous"></script>
{% block js %}
{% endblock %}

View File

@ -0,0 +1,35 @@
{% extends "core.html" %}
{% block main %}
<link href="/css/pages.css" rel="stylesheet">
<section id="page">
<article>
<h2>Суриков</h2>
<p>Библиотека научно-вспомогательного фонда <b>Музея-усадьбы В.И. Сурикова</b> начала формироваться с 1948 года, когда
первые сотрудники самостоятельно стали собирать её, приобретая за свои средства необходимую для работы
литературу, прежде всего по осмыслению жизни и творчества <b>В.И. Сурикова</b>, а также специальную
профессиональную литературу по экспонированию и иной деятельности в музее. В это время было начато системное
взаимодействие с <b>Центральной библиотечной системой им. Горького</b>, поэтому многие издания, поступавшие из
библиотеки в музей, имеют её опознавательный знак.
Позже, уже с приходом Л. П. Греченко, директора музея-усадьбы с 1970 по 2008 гг., было принято решение
ежемесячно закупать книги из бибколлектора (ныне «Центр книги Красноярский бибколлектор»). </p>
<p>Но в 1990-е годы в период экономического спада и дефицита закупка прекратилась, с этого времени библиотека
продолжила формироваться за счёт <b>личного вклада</b> сотрудников музея. Особое значение библиотеке уделяла <b>Нина
Ярославовна Скалиш</b>, главный хранитель музея на протяжении 45 лет.
Основой библиотеки является научная литература, монографии о жизни и творчестве В. И. Сурикова, изданные с
1914 по 2018 гг. (кроме того, статьи, каталоги и художественная литература). Библиотека музея включает
архивный фонд В. С. Кеменова: рукописи, документы и фотографии, собранные известным суриковедом. Его
исследования, не вошедшие в первый том «Историческая живопись В.И. Сурикова», являются фундаментальными и
охватывают период с 1890 по 1916 годы жизни В. И. Сурикова. </p>
<p>Стоит отметить тот факт, что музейная библиотека является кладезью <b>настоящих библиографических редкостей</b>, к
которым можно отнести дореволюционные издания о великом русском художнике, книги с дарственной надписью его
потомков; а также книги и периодику из личного собрания В. М. Крутовского с его подписью; и многочисленные
дореволюционные нотные издания, оставшиеся в доме художника от семьи музыкантов, квартирантов Красиковых.
Таким образом, <b>общее количество всех изданий по В.И. Сурикову, собранных в библиотеке музея, составляет
более 100</b>. Сейчас музейная библиография публикуется впервые, ведь следуя миссии музея мы делаем открытым
доступ к тем книжным ценностям, которые помогут будущим исследователям в изучении творческого наследия В. И.
Сурикова.</p>
</article>
</section>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "core.html" %}
{% block main %}
<link href="/css/pages.css" rel="stylesheet">
<section id="page">
<article>
<h2>Кеменов</h2>
<p>Научная деятельность <b>Владимира Семеновича Кеменова</b> (1908 1988), многолетнего вице-президента
Академии художеств СССР, составила целую эпоху в советском искусствоведении. Вклад ученого в его развитие
высоко оценен специалистами. Интересы В. С. Кеменова простирались «От Леонардо да Винчи до Рокуэлла Кента»
(так назывался его последний сборник статей) и охватывали западноевропейское (XV-XVII вв.), русское и
советское (XVIII-XX вв.), современное зарубежное искусство, теорию эстетики.</p>
<p>Однако центральное место в его творчестве занимал В. И. Суриков, о котором учёный писал с 1930-х гг. до конца
жизни. Неудивительно, что в 1989 г. вдова учёного Л. Г. Крамаренко передала большую часть архива В. С.
Кеменова <b>Музею-усадьбе В. И. Сурикова в Красноярске</b>. Сотрудники музея перевезли документы из Москвы,
разместили в административном здании (флигель усадьбы) и бережно сохранили в отдельном шкафу. В 2011 2012
гг. кандидат исторических наук И. А. Черкасов провёл разбор и описание архива, сформировал <b>личный
фонд</b> В. С. Кеменова (Ф. 1. Оп. 1-2. 81 ед. хр.), многие документы которого <b>до сих пор не введены
в научный оборот</b>.</p>
</article>
</section>
{% endblock %}

View File

@ -0,0 +1,35 @@
{% extends "core.html" %}
{% block main %}
<link href="/css/pages.css" rel="stylesheet">
<section id="page">
<article>
<h2>Суриков</h2>
<p>Библиотека научно-вспомогательного фонда <b>Музея-усадьбы В.И. Сурикова</b> начала формироваться с 1948 года, когда
первые сотрудники самостоятельно стали собирать её, приобретая за свои средства необходимую для работы
литературу, прежде всего по осмыслению жизни и творчества <b>В.И. Сурикова</b>, а также специальную
профессиональную литературу по экспонированию и иной деятельности в музее. В это время было начато системное
взаимодействие с <b>Центральной библиотечной системой им. Горького</b>, поэтому многие издания, поступавшие из
библиотеки в музей, имеют её опознавательный знак.
Позже, уже с приходом Л. П. Греченко, директора музея-усадьбы с 1970 по 2008 гг., было принято решение
ежемесячно закупать книги из бибколлектора (ныне «Центр книги Красноярский бибколлектор»). </p>
<p>Но в 1990-е годы в период экономического спада и дефицита закупка прекратилась, с этого времени библиотека
продолжила формироваться за счёт <b>личного вклада</b> сотрудников музея. Особое значение библиотеке уделяла <b>Нина
Ярославовна Скалиш</b>, главный хранитель музея на протяжении 45 лет.
Основой библиотеки является научная литература, монографии о жизни и творчестве В. И. Сурикова, изданные с
1914 по 2018 гг. (кроме того, статьи, каталоги и художественная литература). Библиотека музея включает
архивный фонд В. С. Кеменова: рукописи, документы и фотографии, собранные известным суриковедом. Его
исследования, не вошедшие в первый том «Историческая живопись В.И. Сурикова», являются фундаментальными и
охватывают период с 1890 по 1916 годы жизни В. И. Сурикова. </p>
<p>Стоит отметить тот факт, что музейная библиотека является кладезью <b>настоящих библиографических редкостей</b>, к
которым можно отнести дореволюционные издания о великом русском художнике, книги с дарственной надписью его
потомков; а также книги и периодику из личного собрания В. М. Крутовского с его подписью; и многочисленные
дореволюционные нотные издания, оставшиеся в доме художника от семьи музыкантов, квартирантов Красиковых.
Таким образом, <b>общее количество всех изданий по В.И. Сурикову, собранных в библиотеке музея, составляет
более 100</b>. Сейчас музейная библиография публикуется впервые, ведь следуя миссии музея мы делаем открытым
доступ к тем книжным ценностям, которые помогут будущим исследователям в изучении творческого наследия В. И.
Сурикова.</p>
</article>
</section>
{% endblock %}