diff --git a/CHANGELOG.md b/CHANGELOG.md
index afff9b7..a6ea72b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,7 +11,52 @@
## Не выпущено
-Новые изменения будут добавлены после build 0.38.
+Новые изменения будут добавлены после build 0.39.
+
+## 3.3 build 0.39 — 25 августа 2026
+
+### Документы и публичные шаблоны
+
+- Карта публичных шаблонов показывает, из каких штатных компонентов AVE.cms
+ собираются основные типы страниц и где каждый компонент редактируется.
+- Поля оформленных информационных страниц снова доступны в редакторе документа,
+ включая подписи, адреса и содержимое карт на странице контактов.
+- Исправлено сохранение документа из режима исходного HTML-кода редактора.
+- Публичный адрес под названием документа в таблице стал активной ссылкой и
+ открывается в новой вкладке.
+- Существующий документ можно сохранять со своим URL, даже если рядом с ним
+ позднее появился системный маршрут установленного модуля.
+
+### Рубрики и темы
+
+- Дополнительные шаблоны рубрики открываются в полноразмерной рабочей панели,
+ поддерживают проверку и корректно возвращаются из полноэкранного режима.
+- Из редактора дополнительного шаблона можно перейти к связанному документу.
+- Улучшено отображение представлений и публичных шаблонов в разделе темы.
+
+### Поиск
+
+- Поиск распознаёт артикулы без разделителя между буквами и цифрами, сохраняет
+ правила русской и английской раскладки и учитывает управляемые синонимы.
+- Добавлена осторожная коррекция опечаток с подсказкой «Возможно, вы искали»;
+ исходный запрос пользователя не заменяется без объяснения.
+- Переиндексация получила более устойчивый пошаговый прогресс и словарь
+ поисковых вариантов.
+
+### Каталог модулей
+
+- Модуль слайдеров добавлен в публичный каталог: слайды, ссылки, навигация,
+ адаптивные изображения и внешний вид управляются из панели.
+- Галереи получили обновлённый публичный просмотр изображений.
+- Модуль SMS-входа получил журнал событий и расширенную диагностику отправки.
+- Обновлены модули поиска и связанных материалов.
+- Торговые, проектные и служебные модули по-прежнему исключены из публичной
+ core-сборки и каталога.
+
+### Выпуск
+
+- Для build 0.39 подготовлены чистая core-сборка, компактный установщик и
+ накопительное обновление с build 0.20.
## 3.3 build 0.38 — 5 августа 2026
diff --git a/PACKAGE-MANIFEST.json b/PACKAGE-MANIFEST.json
index af4c335..a703c34 100644
--- a/PACKAGE-MANIFEST.json
+++ b/PACKAGE-MANIFEST.json
@@ -2,18 +2,19 @@
"format": "ave-core-package-v2",
"product": "AVE.cms",
"version": "3.3",
- "build": "0.38",
+ "build": "0.39",
"package": "core",
- "built_at": "2026-08-05T18:33:56+00:00",
+ "built_at": "2026-08-25T09:00:39+00:00",
"installed_modules": 0,
"bundled_optional_modules": [],
"optional_after_install": [
"setup/"
],
- "core_fingerprint": "a8ac3d2be6a3c4b43c7d871c4e5ece44b1641f1fdace69329e296468e1d192d2",
+ "core_fingerprint": "cd348589924dbf95a20d6e41994c39f036bd55372250ad6caa8c3484158bd6a5",
"files": {
".gitignore": "20d4fb177d47da6944a7a1c2dbf5918fd62766881aa52b00901f9054b1db4b49",
".htaccess": "71c06b7bc41b09c347a562ca72768acaa0ee1ef8388c749fc67956c793d8a826",
+ "CHANGELOG.md": "58ab5f834aad74ac1f8101983102fce3f0708d720999efadcdff9efdefa2ed64",
"README.md": "19b820f1b092780039aae011e133dc6a1e5f1cf213b435e0ff1962b592b9095b",
"adminx/.htaccess": "d03ea2d2972af8b7545a2d57c6253f303d15adeb30b775e161d887ca5ff73378",
"adminx/Support/AdminLocale.php": "a7fe84d86d65c9f021106172b1789c8bc6221c36b2fe39cf883ca9a505e3e8ad",
@@ -28,7 +29,7 @@
"adminx/Support/Roles.php": "6be335dd643317f77bd7c2db92f25895e601fe72818befd62f6aeb3ecac9fce7",
"adminx/Support/RouteGuard.php": "e9e20c097c0db354851546585be72bfc71a5d14652871f8bd1160e845d4dab1b",
"adminx/Support/SavedViews.php": "4548ca129f270184e61cb670e7f44a1c103099a6a6c2820bed1a11bc18f227c4",
- "adminx/Support/SectionHelp.php": "6a5101c1f7b61ea1b94aa4a37985d3a5955aaed8b73537f918f90f7749dfa08b",
+ "adminx/Support/SectionHelp.php": "4ce72b8895ad4f813d3a49e56a895c20f2821ad7f546c87f6e891793592dee4c",
"adminx/Support/SensitiveRoutes.php": "d14d809310b43fdb66ec141b0573ec1020aea3720d20269d39f201f411d9f9c5",
"adminx/Support/SystemFeatures.php": "f18ada623710f8c3efc6588c037227f6074e75bad9a3820aeef60b4d8d4b20a4",
"adminx/Support/SystemHealth.php": "286aee452e90f71ad64437bd39d6dd985d312eb85bbb711490d3a0b631a16aea",
@@ -36,9 +37,9 @@
"adminx/Support/Twig/AdminLocaleExtension.php": "1fb6c1d1c35d74fdb4a578cd15b3f19441725db6f11b336dc6948ed982842f3a",
"adminx/Support/Twig/AdminLocaleNodeVisitor.php": "ccf387680fe8b0e11bd0fe0d23769be8b7ed9afc363cb9a802db5c4aeb7d405b",
"adminx/Support/Twig/AdminTextNode.php": "e51d8d3ee49411f42c8a874cd283f60c1fa0af6506b34568a119ebe2d18d7268",
- "adminx/Support/ViewOverrideEditor.php": "c3804e0b399cacd604ad2765885366a4baefe0ee7a441475276fe7d3074dab36",
+ "adminx/Support/ViewOverrideEditor.php": "9704c974f36b6f9e204b6c609582d61b0a0f65aa182a3f1381fe7a7d1c0b15e7",
"adminx/Support/WorkItemContext.php": "d4b87b78ff1f2bdd89d77bc0483ebb46e5c11616635211ead9a43b34568e3a59",
- "adminx/assets/css/adminkit.css": "15409efc9387120866479c3351a4a291ee7d8207694a84b228fc5d35fc184cbe",
+ "adminx/assets/css/adminkit.css": "0883ec02682b3069765388c61e0d9f8262786599fbd8bec51701affbab585a5e",
"adminx/assets/fonts/NunitoSans-Bold.woff2": "2d48580b7e1be60fa94b8092ebe86471fe0adfe5ca75242a7af2042ac9da825f",
"adminx/assets/fonts/NunitoSans-ExtraBold.woff2": "66612ee9d1cdc341c9eec8a43104bbf515e7cc52821558bfc7dfcf33c8cb0cc1",
"adminx/assets/fonts/NunitoSans-Regular.woff2": "878adc25278872933f004783f0ebbe092cebb3f9161aa30cffb2b9a8a07db040",
@@ -46,16 +47,16 @@
"adminx/assets/fonts/OpenSans-Regular.woff2": "a26ed465673123933c5510730a7286f49016a40fa94062021d1dc4c9bfeddbd3",
"adminx/assets/fonts/OpenSans-SemiBold.woff2": "7d963e1d2f61c11c2e8bd2eb528777b5a55569708d50a7ee7bd097d7fafe6da4",
"adminx/assets/img/logo.svg": "685d9bf037b33fbcfc200947d5051c5325762f8ab8a29ac9e1e587076321e463",
- "adminx/assets/js/adminx.js": "4303a0a0c89c8e339143e6d1e0269a51b02b1fe72383f83af6a2a8d3b4ee1cf0",
+ "adminx/assets/js/adminx.js": "34d38f1291372e5ce10d051d9cbc144f61020f833fce263f5041fdc6926c20d1",
"adminx/assets/js/bulk-actions.js": "d7ee80678e6703aff2418a4679ba8e5027e7220cb089848bcced6d9f100b3aae",
"adminx/assets/js/command-palette.js": "f62f0bad5ddf2b6e813e7eda094d73df63dce09a6e581025971e8f10430e8d9f",
"adminx/assets/js/editor-codemirror.js": "90a04c219e84f5391a40b0d1fc807ecfe4ca582a198bc202bdc5aa6dccec51fa",
- "adminx/assets/js/editor-tiptap.js": "b924d1deccda0e85d947ec936ead0d32f85aa5517893d197316f75a614ac5092",
- "adminx/assets/js/media-picker.js": "eec036a03bd1d0d7995708a15f3337cc6e5d2e8ede7d5be3b0c5eeaeed782ebc",
+ "adminx/assets/js/editor-tiptap.js": "d5c7596bb17de87cb061c18daf629f322d03e2c81be19dab78897e93691d52de",
+ "adminx/assets/js/media-picker.js": "74481279a7d352c7c72893b4043e02454ff3cc28b6582c5463dfa712b17e23a2",
"adminx/assets/js/saved-views.js": "30c02121634f6f035dcff6a730e6fe300a1892fc9321971f142dcff600802f30",
"adminx/assets/src/bulk-actions.js": "29bcc568366ce4a86a71b12a0f8be050bd71a9eabb0f65af6d9ca57281db9201",
"adminx/assets/src/command-palette.js": "ff86b0dc0f1ad59d551af772643dbe094cdfb2367d402eca49fd1a4c5b1bb9db",
- "adminx/assets/src/editor-tiptap.js": "e4cf6d82b1759708e9e2df5bb54136570e572a99138cd57f6eee7f91257ed7c4",
+ "adminx/assets/src/editor-tiptap.js": "1cb23f5262b50aa8ac0fbf19d98b77ce063af966f47d9b2ab6b0e5e3fadf28ae",
"adminx/assets/vendor/codemirror/addon/dialog/dialog.css": "5df690d771f12118348562dd2a90060433ae2edeccd0908abcaa4480b1e3e468",
"adminx/assets/vendor/codemirror/addon/dialog/dialog.js": "1eea0e500dce10d8594d8d681a91d1b71a4209355c9c29885e86e26b2d76685e",
"adminx/assets/vendor/codemirror/addon/edit/closetag.js": "700b8aa7c46e040287e32ffd1edb86a42d09edc5b2a9594241590b154c0232bd",
@@ -184,8 +185,8 @@
"adminx/modules/Blocks/migrations/012_merge_visual_blocks.php": "5c9cd6efd66a027134621859da1059b2c403bacb732885d6b405699cbce8fc14",
"adminx/modules/Blocks/module.php": "53eb434566d820814f5b25382200b75ed02b0067c066c8f8cb62bee13529b964",
"adminx/modules/Blocks/view/index.twig": "39f9225b6a95c65549d0c861afb7db7879ca338c251290653dded6ab7cb715cc",
- "adminx/modules/Catalog/Controller.php": "425ebd7b8e54ef659879e7bf8212120feda968a7560e2a614b505be882fed410",
- "adminx/modules/Catalog/Model.php": "e7c7cffd91754e04dac322c31a64b420739c671079a7f8e7effb550f5019ee13",
+ "adminx/modules/Catalog/Controller.php": "22a19db35e22824abf3bea9053050c2cdd74c937022de905684c5b627d52de57",
+ "adminx/modules/Catalog/Model.php": "d524730981ad388928cfc29208a821ed1d474bd01d6db1749fd37fdfb4e4958e",
"adminx/modules/Catalog/assets/catalog-drawer.css": "b3e4f798d7bdf329aa91987ae5be435910ddccfeb8a32211059846ab9dc6b7c4",
"adminx/modules/Catalog/assets/catalog.css": "49e7a33fc444f8330806e1cc5cbb0d523b673c64faafd25dbf0fe33067643efd",
"adminx/modules/Catalog/assets/catalog.js": "1fc4acc30b05fd702dee9ff62958141aeab7593ca38d6f6360567af7c3dc9742",
@@ -196,8 +197,8 @@
"adminx/modules/Catalog/language/ru/interface.xml": "15d8de452eef296375ba40d557ce15ac0363881a7b763b7111c8cc221e9861ff",
"adminx/modules/Catalog/language/ru/runtime.xml": "fe59945f4ceb2bf2d85699623369e7e5889613b8cecd8551a390c78023531d14",
"adminx/modules/Catalog/migrations/005_add_catalog_purpose.sql": "006ff237e25042a4fa9b9f74d5079741eeee1aa52db0cd996ea4c8b817e6700f",
- "adminx/modules/Catalog/module.php": "69f6f46206bec880a6f08329e0b44e35bcf3c7e57ad5e85e3b720b7b4adb7f38",
- "adminx/modules/Catalog/view/edit.twig": "7fa6f39eec641b23f2191a9df2186a4d4855e50d4b1cc506118a94313cf481a7",
+ "adminx/modules/Catalog/module.php": "c36086c3162d1eba433af05c879dcfb9cff830ee3c53da38705a72cc19b3777b",
+ "adminx/modules/Catalog/view/edit.twig": "13bd9ebabdc8bb470a6c8ceb813b122d33da5c5604a23bb7ad3a840cc061cde2",
"adminx/modules/Catalog/view/index.twig": "754c7c4dc81176e8c99b3d9e8c1fda2f62f03df341c3df1f7393f72d43c6f8e2",
"adminx/modules/Console/Controller.php": "af0309a50d7f4366279be8ea12959cf7718a3c68b895a65ec5846554fd6a4857",
"adminx/modules/Console/Model.php": "53aad480b8d4e5d68c4e9345d219ea5cbff6d04eee7ce4aa15deab6f00dac1cc",
@@ -253,11 +254,11 @@
"adminx/modules/Dashboard/view/widgets/recent-documents.twig": "d03f36f047574f8ce57fdcfd79b26d9b20ca01dba69ce7c71a3e9a277f0105d0",
"adminx/modules/Dashboard/view/widgets/shortcuts.twig": "af165425ea7889cdc3edbd72c7f0dd9dc5c4c06486bc4ab37816112cfb696718",
"adminx/modules/Dashboard/view/widgets/system-kpis.twig": "52dab540a189443b76811ac4b457a566cf90134366749aa9c7d82e2d23ffdaa3",
- "adminx/modules/Database/Backup.php": "08192cdadd31b0d304393cec761ca78d696d10e7b9585389662fa71f53e8e4c0",
+ "adminx/modules/Database/Backup.php": "131bcff544bf3d2093ae5d14d984070fe5819bf0ae1aa9ec02a48d42b85d1e1b",
"adminx/modules/Database/BackupRestore.php": "1fb34abdec27ed24665ec0749f4ed44167f3881d3477129d5d4de6c7b36d6653",
"adminx/modules/Database/Controller.php": "87577df32e3cacc36edb57747ac9169d0e32a30b2b2f11dbbc4db18be486e3e6",
"adminx/modules/Database/Model.php": "52c23131d07bc798fb2a3489f91ae909985e60d2965e3cd5fbaf5ab441d85477",
- "adminx/modules/Database/SqlDumpReader.php": "f0b3a501d0afea0ae50d35b43e901844e14d9faec286738956dc6f2b1c5ea4b3",
+ "adminx/modules/Database/SqlDumpReader.php": "e71f81a1fd4859fddced14da210f1530eee41131287e84d82d43d3033dca5ae2",
"adminx/modules/Database/assets/database.css": "9bbcaefb3c3f3e6230250cf90cb20ffe933f86812ddb8c49d17a3f65097aef5d",
"adminx/modules/Database/assets/database.js": "191c97d3d7586440ca736c5cffc228e4ea9f8915ba61e5a55146f849df7e9d12",
"adminx/modules/Database/language/en/client.xml": "4464d65adfd150e597436cade1d36b0c0436654fb4c710218fe2fad117b12369",
@@ -269,7 +270,7 @@
"adminx/modules/Database/migrations/001_mysql_legacy_index_compatibility.php": "e9a021b5643933d6ca000d54bd1a12e339080939aa84fd3ec28d0093a5b8222c",
"adminx/modules/Database/migrations/002_module_lifecycle_recovery.php": "779b04c8b35f3d81657848016536a9e3ef70fb76dd3ce5b1ba4b4a72166f7a2d",
"adminx/modules/Database/migrations/003_document_fields_text_composite_index.php": "541a8df87dd5fec6946f26dcca88f78a2bf5e09608f9300baa8532202020ffbf",
- "adminx/modules/Database/module.php": "550471695f5bf7ab5045f0b22c891f65b398346fb03f5f0a0cc3bcf3fed379a7",
+ "adminx/modules/Database/module.php": "1592d603f73695883643df7aa3fe90c257cd120f99e272b4759b0cdf5c3d3da3",
"adminx/modules/Database/view/index.twig": "4e3d3a8ccdeeb2812d4b6d3ae471c8f162d8ac0e30c5dfcc4847356b4cc063a9",
"adminx/modules/Directories/Controller.php": "bae664ccff5a63a215fa1b59b721d0b0a4055ac3633319476d09889ead0d7d27",
"adminx/modules/Directories/Model.php": "ead423e3d0a07badf5a773780ee37d92394240fc753c10fad274a3131db7c2ce",
@@ -283,23 +284,23 @@
"adminx/modules/Directories/module.php": "f6b7f0bfb96d1c2c7aa52c51e60abaa09cdba7a4309d8363394a3a02a8df96a0",
"adminx/modules/Directories/view/index.twig": "0bde23bc578b9ffad3c47534c304c0016b661a36588d6d3c9ffb92e2daa388a0",
"adminx/modules/Documents/BulkEditor.php": "6ca33e4ee73f83e043b13363bddb391199a1da902e74dfa7709a10ac32be574b",
- "adminx/modules/Documents/Controller.php": "e461ac2766b15194741b0a4ca2c2aafac9edab9b9f602516011dd5168269104e",
+ "adminx/modules/Documents/Controller.php": "283aec04707c7b7fdd01e2ba69c1961b925f259655e9a455889bdd5dfc66f4fe",
"adminx/modules/Documents/DocumentHookRunner.php": "7e0cd24ed5791b6d5010e4a05351b709be09b6df1e700a3ff06685b9078b2b1d",
"adminx/modules/Documents/DocumentMediaReplacement.php": "e97d960c852162e655a4389cc5f477f0178597da50e733b00bdf5bcab076c65d",
"adminx/modules/Documents/EditConflict.php": "ebc3ebb92c3fc3ccf3909d2ff517014fc888ee20576a520b4979f68bde632b97",
- "adminx/modules/Documents/Model.php": "6ac1f3e85c73e5e7c8a531b31f9b72ea1a78ba561355102301e386baf0eafdac",
+ "adminx/modules/Documents/Model.php": "5287c9adc603c942d1ffae135df360717ee18b1d1b2b9c29bb35d183b477b54a",
"adminx/modules/Documents/Revisions.php": "8caef88f7456000b001757cff13393ae36991b33c4c3070c27f3eaa4e8bf6492",
- "adminx/modules/Documents/assets/documents.css": "5e9dbbbfbf276ad94a32657dac8a8cf0a5c184e77fb6b1d40e0261876dbe84eb",
- "adminx/modules/Documents/assets/documents.js": "b65c2396c0f866e1895d84637875a935ca4a0886378c65068e5b4215b6dd94c8",
+ "adminx/modules/Documents/assets/documents.css": "396d4cc7625b3331a41d94f040ac3fabafd9c0b993181b490ceea8f365e1a5ad",
+ "adminx/modules/Documents/assets/documents.js": "dce151189d8244be41fb6b1bbb85a26241c788d91111ccb5314c361150024c3d",
"adminx/modules/Documents/assets/redirects.js": "95ca395c6b4fd3814da37450c27da351e75f509e26421c975dd8e0ee5f657efd",
"adminx/modules/Documents/language/en/bulk-editor.xml": "b7af92bd151c7899a56cc461f4a09edaa842213cb6e6b4b4363af31aee228e50",
"adminx/modules/Documents/language/en/client.xml": "9ccb6f2aa9a83862abff2c1fa1c6b10760600c3202293fd7e42073c9cfb5fae4",
- "adminx/modules/Documents/language/en/interface.xml": "f3a30b3c565d2e01c841c843dc529fed9844dc2e095f90b7902008ded7214ed2",
+ "adminx/modules/Documents/language/en/interface.xml": "87b858717419a48ed12e1181183b8de0285483df4c9de37403730f8a7c034b52",
"adminx/modules/Documents/language/en/runtime.xml": "7009fa27d67ab168fecd9d9bf182216dfb0397d9c006eb6563ee3c464daa5b5e",
"adminx/modules/Documents/language/en/zz-interface.xml": "839ea37d585ff10d7b819a4fcd4bda876c94d51019489cc8d35af15fbc905f2b",
"adminx/modules/Documents/language/ru/bulk-editor.xml": "29500cbda63189ed9a0afdb57b20f998241594b254d0225a776a8be32d108834",
"adminx/modules/Documents/language/ru/client.xml": "3e14cb51de149aca6c3f309ffb3973a47f80f638805325ff0a5cc23dd1025302",
- "adminx/modules/Documents/language/ru/interface.xml": "718386366f913c590c2bb881f49ad3b7d436de82faeec30655a7c2f9b14d58e0",
+ "adminx/modules/Documents/language/ru/interface.xml": "f8d60edba8ac5b6d0eb6deaa3b9b89733cd0b8d3eb951c105a9874ea6769c305",
"adminx/modules/Documents/language/ru/runtime.xml": "6c3e9a2dbf862bef5a76ff0bb600084fcd9d5d1a7f0cbfd02377242b74fcd759",
"adminx/modules/Documents/language/ru/zz-interface.xml": "2d819845ff2ae5246203374a685f8a5ad881ba9067166161eb96d5c9271026dd",
"adminx/modules/Documents/migrations/001_correct_breadcrumb_title.sql": "e2b612f19cbc2c419d1a3d2c684909b4055811a7121f4debdf6b83e99bd214cf",
@@ -318,12 +319,12 @@
"adminx/modules/Documents/migrations/014_document_relation_edges.sql": "3491ade2987535f8db1b3b00ff27f5a2062b02a3a1cb2e707d2752ff446b566c",
"adminx/modules/Documents/migrations/015_document_creation_presets.sql": "a123f8867ab6f6346002d08317acac37062c2792913ad14102c81812d45571bd",
"adminx/modules/Documents/migrations/016_reconcile_document_content_columns.php": "06671bb26bf29479dce24888fb3712c0d20e2aac5b3d70196604ce4097c875fc",
- "adminx/modules/Documents/module.php": "36069766b3397735293f9f7d491c6f874eb8fc3c12c7d5d983ec8eee6b4655a8",
- "adminx/modules/Documents/view/_product_panels.twig": "e07898ea6a7cc0004ac7545b689afdc6b474e6d8b24357f5c5190b4ca5589142",
+ "adminx/modules/Documents/module.php": "dc7642c19f3fabda2ce0577dbf2002de389ee2f5616c26a6f6cdd88c639f91c9",
+ "adminx/modules/Documents/view/_product_panels.twig": "d0e681a94f6e48c061dbc61cc47c384bd52c4eb66d648e36784b68d0c8ada603",
"adminx/modules/Documents/view/api.twig": "a035a2d566298587ce8530c98915e80e4e7f9b0f41a60e0e55d4547b996a6c40",
"adminx/modules/Documents/view/bulk-editor.twig": "27d9baca8cf591dc2ee81b26fda3efc9017c6809c1d23168a749d01bddfebdfa",
- "adminx/modules/Documents/view/edit.twig": "a7934bde27ca84a47b9a5865b8bedb3e28756b91337e479592e1fefba93ec6aa",
- "adminx/modules/Documents/view/index.twig": "004594c77dba92188e58d73228af397ac825e723c3c1aba9ee7b94072f828ba1",
+ "adminx/modules/Documents/view/edit.twig": "94eb351309fb3d6fdab844e253ab2d7e3b03e1d846b35da19b7e498733ad27a2",
+ "adminx/modules/Documents/view/index.twig": "8bb67e66a55007fda1374d83a3bc0b19fdc518a47d191a04913772d8c8ce132c",
"adminx/modules/Documents/view/redirects.twig": "4a4903edc81e4770220852d9be9f02a983ddde6f829a4360ea01d3b68456582b",
"adminx/modules/Documents/view/views.twig": "821f19085ba3997b6ef8f005e838f6fd449f0b32bce0d2804271311856cce8f0",
"adminx/modules/Events/Controller.php": "c57fc25e815c47c14626371d044079e6711f0a4bce2004d2b9256db62f0d0e85",
@@ -367,7 +368,7 @@
"adminx/modules/Groups/module.php": "fcc5b6212c5d6b77d9a17c9a4878c50233a5b48b373cb6a113825a82c82b248a",
"adminx/modules/Groups/view/list.twig": "15af0a1544c7933f57e6bb45ec2a3302d12a025b671c98e1009bae01f5bce605",
"adminx/modules/Groups/view/simulator.twig": "82f0c5aba5acc6e7a079c03e355edefe4061efca60d34b3195ee16ffdc951306",
- "adminx/modules/Media/Controller.php": "cf853d384714160ef9fdb8d4ad108fefe331572fa2cd43e3d05faf67c3e0d368",
+ "adminx/modules/Media/Controller.php": "fa78cff18ddba8d3f3c4db74caf65039e5144d982031e71c394425d31fce2514",
"adminx/modules/Media/GlobalSearchProvider.php": "799e9b9f1455ad1070bb388245ea6adc5c76b9bf5756bfe0d46b96f3378be92d",
"adminx/modules/Media/ImagePresets.php": "8874cbf436a1a33f655c9a395b5f16e143c04c7df1d957c43e3d4ce3ae0645d5",
"adminx/modules/Media/MediaAudit.php": "2deeacd899b4410cc620b40b6e448c388f54eb553b5a57a23475ac0b5cae02f3",
@@ -376,7 +377,7 @@
"adminx/modules/Media/MediaUsageIndex.php": "d39d232bc56352c9cc2335a2e3bd2a7b7f415709c70ece4b38f1a462c2969415",
"adminx/modules/Media/Model.php": "1ac78ca828c6f492857e731a715d54939525dd29545bf81fb4065fcc02f0bc50",
"adminx/modules/Media/assets/media.css": "e7db9f602bc1a46644ccf172f977ebe0798c572c73d33f64584c9569d531611f",
- "adminx/modules/Media/assets/media.js": "1ce96968f03008c9e0d536070579962e80070b946920ee03d9f4e965664044e3",
+ "adminx/modules/Media/assets/media.js": "2fdd0c2541150e5c2bbb4ac3ef54f1f40edc029d48383297d432af0431607111",
"adminx/modules/Media/language/en/analyzer.xml": "5c6e8bec29fe73d0403cf041c7580800ecc2677d94f2148ffaf44c04d337c6d4",
"adminx/modules/Media/language/en/client.xml": "06cecdb6cacdd0871334a3da0a3c7be27d7f835e2a048d37ea31ae4f37469270",
"adminx/modules/Media/language/en/improvements.xml": "5873d8eb9c43db0a584161ae3f086dfd101a8df25bbd4d518a2fd283f4f8cb3d",
@@ -389,7 +390,7 @@
"adminx/modules/Media/language/ru/runtime.xml": "538db360abf2d61e32d6476846173ea4f5115703021362314a9100df3854d6f7",
"adminx/modules/Media/migrations/001_image_presets.sql": "303dec5f331135ab3fda9d264b7a7b674b987cdefa0a7904c544a5d63554f15a",
"adminx/modules/Media/migrations/002_media_search_index.sql": "e84168429acdd900d494c91e398a8974ae97023d8bee3385321c2947dd627378",
- "adminx/modules/Media/module.php": "af83896b4cef13943f197b4c121a534a7ebea86a771ae12714a2c8c4d51e4495",
+ "adminx/modules/Media/module.php": "45634e3fc5bd65c1bf545d1aa79912692a38297d390288d956af0f646dbc8c1c",
"adminx/modules/Media/view/audit.twig": "06936cd87c80b5c7f6ecec8729f227c2ab3fd492e61b11ae7040e6423939b518",
"adminx/modules/Media/view/file.twig": "b317dacbc61411831a9029e30fb1ff3457fa1d34164df3b891b6a9a2fdbe32ce",
"adminx/modules/Media/view/index.twig": "c3a4c42477df3a10fcc8ee558bb03fe25782a58f6f47734cb078686798a2d06a",
@@ -409,42 +410,44 @@
"adminx/modules/Modules/language/ru/runtime.xml": "271e31a8ae1586ed045861933a6db150427c78be7560942185633f35cf90686a",
"adminx/modules/Modules/module.php": "aad8c5f3a11dec21eb14a5d9a83e30c88cb4ea1206e78bc758804d015a828456",
"adminx/modules/Modules/view/index.twig": "2ca402c0886f611129e884129f6e0b0022e14a8c385d5d4ad55abe49ee39f5c2",
- "adminx/modules/Navigation/Controller.php": "99dca17606ed02b4554db50bb99c0414efac51f871c7fab9113f0c6ce77df4fc",
- "adminx/modules/Navigation/Model.php": "f9e59561d2c1b918af994e827d206ea7f0683dff7aa342a6de9193bd877348ba",
+ "adminx/modules/Navigation/Controller.php": "968da27af968f5f6e29b0e015f4c5df438c17735daf646a9d6343c055acfd48c",
+ "adminx/modules/Navigation/Model.php": "58d08733e246f92abc02d9ccfa74b88627970588fd2223688a59d03a4d6c4b87",
"adminx/modules/Navigation/Revisions.php": "165a2e2d8ad218c1437fffbed9297b7be25d0ae6567f0d72aa78da872cefa1b8",
"adminx/modules/Navigation/assets/navigation.css": "e7ad029b253b19546ed2c0eb2a3527dbb3b03a56db13c6f3089c3da9c12933ca",
"adminx/modules/Navigation/assets/navigation.js": "72d68867419fab1b3e7e822a42a7fbcd866ccdbef8fa3051a245e23f23f5ec30",
"adminx/modules/Navigation/language/en/client.xml": "9a0ad0248941cd6377ada049666387581bea0dd76353d34779b4c1343b74ba50",
- "adminx/modules/Navigation/language/en/interface.xml": "5ec5a64240356c4b87f65baea911762e64e88dd652c1fd7c801079247d697b79",
+ "adminx/modules/Navigation/language/en/interface.xml": "2a6bfcf0fdfc36fd87d666842008635dc12b73c3038fa486e45a4d117956a53e",
"adminx/modules/Navigation/language/en/runtime.xml": "341b984ede453a135b6e155fd29c08d9d5a2ab16e62046379f982d9a56dc4a81",
"adminx/modules/Navigation/language/ru/client.xml": "46f18714dea8e751f33baa049f3684d824109e49e23b1b325ef37c8a043f1c7e",
- "adminx/modules/Navigation/language/ru/interface.xml": "cd5ac333b85534be6a81377a1b40fbd449174cf2d1f019d25b3bb3f5ba0713a5",
+ "adminx/modules/Navigation/language/ru/interface.xml": "6b460827a6f170057ca6c4310d747a73f67d8468eb98c12cf4238a54ab6c6918",
"adminx/modules/Navigation/language/ru/runtime.xml": "45d994c39fa7fa092525ed4a61d6775008f3993f4cdcdd615d61e5f15a749cff",
"adminx/modules/Navigation/migrations/001_create_navigation_tables.sql": "43c937b8d7d7f5b70569c591db58ee8592386b988e8cf148fae723b1f68b3012",
"adminx/modules/Navigation/migrations/002_navigation_revisions.sql": "cf24c9e9cdb95c2ba4ccea63ec4375d62f29370d5ef909a82a0c990624b8ad02",
- "adminx/modules/Navigation/module.php": "8420e2f45024b5a1b076cd86a6658d19fe04156d337f87bcbe597b30a56dfce0",
- "adminx/modules/Navigation/view/index.twig": "39092089d109c382f1714e5d724d1e857a8e579ca44b929b40708c9a3f6697e8",
- "adminx/modules/PublicSite/Controller.php": "5805f86bed300709232e0a45fedf6950857dc8f23cb499f9c17786b61a67c722",
+ "adminx/modules/Navigation/module.php": "5eb07314b6c156893288f1616e248ac316ae3a3b561c996d63e772e4ea4e8b6f",
+ "adminx/modules/Navigation/view/index.twig": "b7dd1784238fc53456b1676a7bfd4d14c2d1efdd7f15cee34c2c4613bd0e7590",
+ "adminx/modules/PublicSite/Controller.php": "8c00240eca08149df4572732d1e8b03eeca5910d40f03c2742021035f16aa621",
"adminx/modules/PublicSite/Model.php": "912fcd71f2ba7215fc9a411882c1eb5e0953b6ebaedfbb92f01b7d3de477a015",
"adminx/modules/PublicSite/PresentationModel.php": "3407e5e29165c264c65527433cb94d7d13a80f724dc4d1bf23a49cd2de88b125",
"adminx/modules/PublicSite/PresentationsController.php": "e2c7cc517f1a38d6b5e6828b1d652caa6b63622532d2f1f3c5f176e164ba6533",
"adminx/modules/PublicSite/PublicViewTemplates.php": "d1d7f7bff22703500f17807d5fe6b02d42a2fcc4cb6d7e7224fe0a47f7dc1ffa",
+ "adminx/modules/PublicSite/TemplateMap.php": "22bc2649771e972ea23f2387c6bd9abb02ea3ee89054f38e5492e9401b400959",
"adminx/modules/PublicSite/assets/presentations.css": "fddb0b4300129f7d7c33853b7e31bce131eb6f867e577443429dcafac8cc67d4",
"adminx/modules/PublicSite/assets/presentations.js": "3a74e7c7ae3269b98eca52d2d61db1f4f21871c6f4a5425cc7bab58d24efd963",
- "adminx/modules/PublicSite/assets/public-site.css": "69a17727b3a18a88d103886c2d7f79f2c59539ac70d735379e591657e7f04489",
+ "adminx/modules/PublicSite/assets/public-site.css": "b61c2c699e85ef1ae36e306d83d7d9508bde41702f06037a7ec8c6e69744cb67",
"adminx/modules/PublicSite/language/en/client.xml": "5a93fd6babc20670e54795211cf44ed155417aa933e5d4a50be21aebfa7bd510",
"adminx/modules/PublicSite/language/en/interface.xml": "e5e868af542e9fe6c981dff18e31a0050d2d7ae2b8b217e7b71bd98b496b5f5b",
"adminx/modules/PublicSite/language/ru/client.xml": "f49060544db65a516b2a643420871571b4da5911ae800075c01ae35e55a5d639",
"adminx/modules/PublicSite/language/ru/interface.xml": "67a7d1a651d8dd4ca3edc6c281d68c15146a8e84cb18833f9f4041fbbd57a619",
"adminx/modules/PublicSite/migrations/001_create_presentations.sql": "023d97f077af57107549dd9e420f5ed55a278fb8318caeb8d86f2dc7750dbfbb",
- "adminx/modules/PublicSite/module.php": "10fc91443e4a98b3f529d07fe1cde7bbced8f4f84335fb125d4b2bf509edc2f0",
+ "adminx/modules/PublicSite/module.php": "46694327d2ac6315da3f9ae02b87a038cfea4da4314ef30c1bc451efe6b279b7",
"adminx/modules/PublicSite/view/_placement_pagination.twig": "b10413bdba07695500803625486b56ae5283c5a0a6dff83c668c8f93d1eab182",
- "adminx/modules/PublicSite/view/_tabs.twig": "16229bf10dcfc7fa2da84a7041912f52fe597ea4209088826236c454d98bd52c",
+ "adminx/modules/PublicSite/view/_tabs.twig": "92db2936b265d3b07056ea3aea8ab9dc30e05849b6b3f7d04bf86231fc273643",
"adminx/modules/PublicSite/view/diagnostics.twig": "242682325bd7c99c75f9694d0e7ff6217124bf093364c027d13f609db660f4b7",
"adminx/modules/PublicSite/view/index.twig": "54ea9422b90235e2926924f11c43b56d90ca8e6c4f255501fcc9c52068cb6f92",
"adminx/modules/PublicSite/view/map.twig": "11fba1f54578b4908c669a7dae128949f53a7f1f03746f45a5059c92f473385d",
"adminx/modules/PublicSite/view/placements.twig": "9e1ef173a28ed883ea236da0611a4c2e74de09937c5ca4b5a28ff8caa98c9776",
"adminx/modules/PublicSite/view/presentations.twig": "d0b245362001ef2a1508fc5c4fa462895b4bbff104025544656828c4513d4cc1",
+ "adminx/modules/PublicSite/view/template-map.twig": "274f8754d8276caa92ff56678b7309dc9d3cf6ff672c96cf3dab78318f7bb3c5",
"adminx/modules/Requests/Controller.php": "b0171c1bfcbf1674b9ba0f8e9461dfa7b3ba5d7bce368b8a0e0dcc2659d32a67",
"adminx/modules/Requests/Model.php": "3ab6c5b11cccb31512e61026ae650c414e4bc30debaac3c2b306e9116e3fc49d",
"adminx/modules/Requests/Revisions.php": "78483f6667f25c050cbb2d224baa8ccf8d977b30eb84538c3cfb18c7af764bdc",
@@ -475,7 +478,7 @@
"adminx/modules/Requests/view/index.twig": "164490c16e041b928e1ecf604915656399690055d1f7de4bc88caf5f5abd485d",
"adminx/modules/Requests/view/native-audit.twig": "42120fd41aa4afa39560fea3f2625a4e78a61cb4f9ccc0c0388ca319d4f39c06",
"adminx/modules/Rubrics/AdminView.php": "a866300dda84253046f88d19a4467ae1a545c8b1dcac7672d35be986bb38a180",
- "adminx/modules/Rubrics/Controller.php": "f3895259809f9b536fbe8d827dec5c3ea2909133ea7b98391bdde6b999c223bf",
+ "adminx/modules/Rubrics/Controller.php": "d60d21f8d90048a59eb97e924d7f7539f4ea3979aade0883109118b05b0fc1d4",
"adminx/modules/Rubrics/FieldAdapter.php": "edc4b730499111917360670632573b8d28cb7c192f71e88e8c60c7c8c3e981d1",
"adminx/modules/Rubrics/FieldAdminEditors.php": "d3bb0a8cd7e4b44db93b780ec95c49d3974bb3fc3167110a2f8ef044e921c348",
"adminx/modules/Rubrics/FieldConditionImpact.php": "77600152ef7e6a98cbfcdff1bc638aa6b1a664048bc2b839188b56a1ebf0a735",
@@ -487,7 +490,7 @@
"adminx/modules/Rubrics/FieldEditors/RelationFieldEditor.php": "9504549a3b81893ec8a114a286a960450bbd302cb2f8f9af97660c8332f91925",
"adminx/modules/Rubrics/FieldEditors/SimpleFieldEditor.php": "e961e7ab2bf3a16d6d9131513f11d2d4b9520ecee5572ae7e1b7ae8475d1cacb",
"adminx/modules/Rubrics/FieldTypes.php": "c6cc64b0e1b1fb29b65275d337e5747b9f9f68ca46171e7e7f07129cd27b8409",
- "adminx/modules/Rubrics/Model.php": "97007d19f651bf8fce9599d542bace4ed4408bc0f65407383ba5589b8b7eaa48",
+ "adminx/modules/Rubrics/Model.php": "21f19765b1ca8e4ca7bd43fa8bd9737e26008cc152e77737215bbcc0572855c7",
"adminx/modules/Rubrics/RubricFieldPresets.php": "80109bd9f066f077ad19e48d7c7599e4dbf7b1d0c06c54db9fc921992bca2e77",
"adminx/modules/Rubrics/RubricFieldSetLinks.php": "41cd27e5947680eb184dafba5fd95f707ccad0c20fab2ca948cf882ada0adefe",
"adminx/modules/Rubrics/RubricRevisions.php": "83d3659ae0b1d2ce75fe02b0ebfc11d04857556ff352dc2b5408a1481291c658",
@@ -495,11 +498,11 @@
"adminx/modules/Rubrics/RubricTrash.php": "0496ca44ac1ba27cc7af1fe9673ee27c7fc4b174507b0038549b90733d22570b",
"adminx/modules/Rubrics/assets/field-sets.css": "fe5253279caa882f9623b058c92ab461c476c080baf2399425e522a36b75336c",
"adminx/modules/Rubrics/assets/field-sets.js": "efbb011014f756dd59ac1357441d06c9b69b278a9d934f7cd9d2d1d7398cd313",
- "adminx/modules/Rubrics/assets/rubrics.css": "98ec76c4301d02bdfef75de9af85e3ae493bfb3c491827b2a9c6e2552fdcae10",
- "adminx/modules/Rubrics/assets/rubrics.js": "38ddc91374036bf9aa12cc55abd5ee412b829a729379ed7f9727d84bc12be84d",
- "adminx/modules/Rubrics/language/en/interface.xml": "b13fb75bc6770cc6970ee314b2f3d8c492d6227532533d46381c8919fce8bc78",
+ "adminx/modules/Rubrics/assets/rubrics.css": "72898787697fd0e26c61105c6f1fe5b509d731234e60b438742e30cda8fd6cc9",
+ "adminx/modules/Rubrics/assets/rubrics.js": "d0384f3fedd7a765a5d0006a1ca5080a9cc37fade556e95c2561927d197ea2ca",
+ "adminx/modules/Rubrics/language/en/interface.xml": "7edfdc6fe447448dbed987bfbfb8244557b2a7fab89a2ff3dd1e29eca57a6033",
"adminx/modules/Rubrics/language/en/runtime.xml": "4cad3d97c6a2695d038286cedc2c4509230c3bc9fa789dba2da9db89e8d9c93d",
- "adminx/modules/Rubrics/language/ru/interface.xml": "3654fb74ac081dbb291f5ad13f0c83b2a83b9ce9bc2ebdfa06f9e0a49417b2b0",
+ "adminx/modules/Rubrics/language/ru/interface.xml": "7df4b3f148b367ca3185174c0feb3129f7ce28f99791a2f49de256d78bae7730",
"adminx/modules/Rubrics/language/ru/runtime.xml": "1bf3f8341af4561a4b6b1da98a28b689105285758da0fed959a6222fd83757d1",
"adminx/modules/Rubrics/migrations/001_create_rubrics_tables.sql": "a827a92e9d83361569b7e1e7c65b65cb019d1675a9078e05002de25b3e62c72c",
"adminx/modules/Rubrics/migrations/002_rubric_field_settings.sql": "4406f7ce1a9e9b0a105c4a33588791d41f12a55449baeca2565d5573e79f798d",
@@ -517,10 +520,10 @@
"adminx/modules/Rubrics/migrations/014_rubric_purpose.php": "2913a1a7bb6f4c5665ad05704dcbbde78c7ab6b75b0ad7643fd00e482af69411",
"adminx/modules/Rubrics/migrations/015_merge_directory_permissions.php": "648b3e232e9426f84d69238295515342e42695e46288b901950cde2e8979b0e6",
"adminx/modules/Rubrics/migrations/016_rubric_trash.sql": "09385e8d14d5d4d977db59c60035338404019783cc44c6c784cc2172faab08f0",
- "adminx/modules/Rubrics/module.php": "4aec29585bc8cb20be30b4a741eeb124f5836bd714593ff717f0ce59a889ebb1",
+ "adminx/modules/Rubrics/module.php": "ae3e957e9c3e099e66fcd14d9ddcf76a172207ff68d77d6279ca7a4c15c37c36",
"adminx/modules/Rubrics/view/_content_tabs.twig": "e4c0a9b899b5085aeaec2e1793b8eeac4a7619959fffc7ce39151af1250e0d9f",
"adminx/modules/Rubrics/view/field-sets.twig": "911dd7cc9546411e1b29724466328c7d2db8db630b2b52d33e8443a066b16810",
- "adminx/modules/Rubrics/view/index.twig": "896fb1f4b8e56803f857857b288f78207efe60cd659277f257abf85b2e8e1cc8",
+ "adminx/modules/Rubrics/view/index.twig": "4c01e7b020d16528b2a2df68e35bb2e4639efabe30adc4f19508d85701e788e3",
"adminx/modules/Security/Controller.php": "c7f2263f460057191402dacf8948b822f9b33bbacbe94e6e9ad871613147fc86",
"adminx/modules/Security/assets/security.css": "fd8c477d07c38450587153402380040b72fa9a92c60edd532c4b21046cfe375a",
"adminx/modules/Security/assets/security.js": "f270dd0b910b9aa4815680b8d80d66ffb31bc7862f74e628cc60efba4205a99b",
@@ -533,7 +536,7 @@
"adminx/modules/Security/migrations/001_create_ip_blocks.sql": "832aff733e2c25a43164c60a601dba08c21fa29ac9e826568055dcdb65417a76",
"adminx/modules/Security/module.php": "557308dd9dbe114e5c7129e1cfbc06f25ded104e08c473513e2f388ba945198f",
"adminx/modules/Security/view/index.twig": "b70bb298a3e1aed377d31bc6dd6b7937cbdcb71cd7098113b8b026ba9dbfab84",
- "adminx/modules/Settings/Constants.php": "03c0af9ffe9c17fe879974c49396567ac0584f2474101715baf6ada847ac3ff4",
+ "adminx/modules/Settings/Constants.php": "5f8d15924c33f2e23e6f57f2934229e9c16333334f8ce3679793b35e5acbe0c2",
"adminx/modules/Settings/Controller.php": "90aff255d936753ab763ec534a097e9988f6c737bd5be2ba1194a7700a372e72",
"adminx/modules/Settings/Model.php": "9e16004c78a03d39db1bdfd5e23b88b77e8afc7b8f57a2a21997602ace277440",
"adminx/modules/Settings/ProductionDiagnostics.php": "b23d473a5f6e7c231c68abe4d48159b177699b5f0c23020e96252b240f86c87d",
@@ -577,8 +580,8 @@
"adminx/modules/Themes/Revisions.php": "f3927eadf841873b7a64976e28f2752b6d1be839327922720a0cd33e30d0884d",
"adminx/modules/Themes/assets/themes.css": "c54dd5451693f5117a22c9b7d7a742847e6c6d97a7700d38dd28111337392d38",
"adminx/modules/Themes/assets/themes.js": "1ab50ed9c1ff67ad4e4b8b295c245ad5f040c7b11bab102d007953ba1f79042e",
- "adminx/modules/Themes/assets/view-overrides.css": "62e6d1a90c8819f50318240e93941054aaeb1b15442d3cebb954a4e839075699",
- "adminx/modules/Themes/assets/view-overrides.js": "e531167445281fe7beb93110d6e089ab258a4d110108c3fd44fd4ff981dc7c6d",
+ "adminx/modules/Themes/assets/view-overrides.css": "0edd417ee79470e3a650a232b859fb68390b38dfd0b401a5367326a6dd416ca8",
+ "adminx/modules/Themes/assets/view-overrides.js": "480b8a128320f633d68fc439065fd6706c4208ac07828f438019563d783a6aae",
"adminx/modules/Themes/language/en/client.xml": "5f9d0a1ca7320e8590468e626433fff92b55cb9ca38d8723d4a363feb77dc3b5",
"adminx/modules/Themes/language/en/interface.xml": "30365f6c02c048fa2d0e94feb82bda45bdf8cc9fa665a7943593bb60636f368a",
"adminx/modules/Themes/language/en/runtime.xml": "66f4efbea3acf5be49cda9931841873e7c8bd68e263ef7a247233b4d762e9887",
@@ -587,7 +590,7 @@
"adminx/modules/Themes/language/ru/runtime.xml": "f8046256e75de65f6f20c8d5b5bd6a927653ca4dc8b6b8f205756b255b624c51",
"adminx/modules/Themes/migrations/001_create_theme_asset_revisions.sql": "21a2ce27d0b97be6493cdb86bffbfb0cb8b16762cc7b0025d83bec71d56a760c",
"adminx/modules/Themes/migrations/002_place_theme_navigation.php": "62a2f2953f3d5bb8df7e39c83a1de83a56008952540c8c28c6a12f33a8fd7f95",
- "adminx/modules/Themes/module.php": "7f961194564ff6b2d7a6c450fad2f966118d71589a6e2d9f1f183d4726f35c7b",
+ "adminx/modules/Themes/module.php": "5f84abd13c0d03f6be58f7c0f4693bc9ba9486401baf957cf620c680fda562f3",
"adminx/modules/Themes/view/index.twig": "68a06ac9ee43b0052c877e89c411a1d149f212418def78e44a1f4a73d80344a9",
"adminx/modules/Themes/view/view-overrides.twig": "876f8f47ff598962614198fd89e5f112f04294215d1ffbe5f6ea3bb294af60ea",
"adminx/modules/Updates/Controller.php": "1aef3e6cec043e4e66ab862b4f0c7081d452bc8a1295fc125860eb0b02fa89ce",
@@ -627,7 +630,7 @@
"adminx/view/main.twig": "4aa00f5a2c50e3cc4d339c0ad9377526db6e8a65575843be59dfd20df5210344",
"configs/db.config.example.php": "e42bdb47aa7d1bc9cd85cb36e05acf940cafd951d6220664c485d1b971f18809",
"configs/index.php": "48e6b249e3d83530187c095ede0198af498c59f384bd458db74f9e0c2306eba9",
- "configs/public.config.php": "54ad564eeb68979b3040fc3cddda8e9b5607c016fd91a045bdb2ef3d0a1afa03",
+ "configs/public.config.php": "882ea6a03ee8df191be8ea68ba78d39ae600d918a0880ebdff158fc38f534aa6",
"help/README.md": "d12965e7851dec5241dd74db731c110a7e0f4acfd7139ae28ce21d53193b1123",
"help/administration/README.md": "af3efa4bcd54eb23236735d461484e13b6319751904ca8856412b931e2ae032d",
"help/administration/console.md": "5efb2adbee857eaf0b8c2816ef1556addb2c9b21420bf972c6bdf62911fa9ad4",
@@ -647,14 +650,14 @@
"help/content/README.md": "273451d12b6360df56cc58323bfb8fd7521b4e3daf8f7ad93c874fe6877af048",
"help/content/blocks.md": "1d55be2a9f1a48df426e5856a54307d77b352d0037d68f08ed7e16a322d9ef8c",
"help/content/bulk-editor.md": "821107322b125c9344bd04346037fbd19f8ccd0ad6da822fe78af3299d01f746",
- "help/content/catalog.md": "2e278644df3a40d05fad7b2a753a90398667b4bb6cbd88dcee8177e3a930b3b8",
+ "help/content/catalog.md": "8ca86447bc45f8f14688d7f74521569b50d4176b9ec2ecca0bad25d91a0ef0b8",
"help/content/content-model-tools.md": "baaf738aec454b1855ea110b8f5e1ef3ceaaad51cb920d4551cd088daeab7e76",
"help/content/content-studio.md": "91ef485e614a9a71a07d4e6c143377f7052ddf3b00d3f2f0d0a32258bdb14dbe",
"help/content/directories.md": "ff6c5ea9bd7fe1fb9d0aa1875d03fc3bc00b2bd2b0ec83d6496cd55168db294c",
"help/content/documents.md": "273851452c0fb8fddd47019fea600da187025dcca271bdd91d59acae15d6b4c5",
- "help/content/navigation.md": "a02d0f88b5a3aa67fd14a2c26a48c9c3c4bb73f4824fa453c20f3e2ef4ef24b8",
+ "help/content/navigation.md": "50425db83ae70b8be72c69ecc6c54f77a15f029a244185021de1cb56efbb344f",
"help/content/presentations.md": "6e349bb4d1d1577617e6bf3d987b86ca6b255f64a47cd1ddb0e38c5242b788cb",
- "help/content/public-site.md": "ea0edea4b3afda64987e5643397237fab227f5fdfa9d9551b73b93b32558f818",
+ "help/content/public-site.md": "9b7cd51d6d36d21daa73b47cb243b84eeb79f0ff48e9eb61c5951e82c24f7c73",
"help/content/requests.md": "c008b1026520c85400fb9240cb117fcaf384531c06e859c1e36ccbb3a79a8590",
"help/content/rubrics.md": "2c9842ce5647fc85663b02cef14f141c673694f0a20856c9262b6ccbea436c3c",
"help/content/templates.md": "c6fae2ef91c741479d1f34fe66d00e83f2d2ec1aba1a4099773a04a6e1b6552c",
@@ -676,7 +679,7 @@
"help/database/security.md": "581d5a89b5ea7cc01f7800878dd879b48e48a912f4a4c81c5f5dadd9b990e556",
"help/database/tables.md": "a027eb9c5e3c65bce645e6a7afd8ad9e37adfcf2e514926836d8a14cb0258075",
"help/database/transactions.md": "da679da1e7656be20048566f25b51c6018a0d7eff1f2e5d54228fb3d54071aff",
- "help/debug/README.md": "41b7954c443330efe28cd3a14519c7c8076165a9d96c0dc3d3781a111e757085",
+ "help/debug/README.md": "665915c103ebb1bba4360508d2bd111b2bc093fbd7989f3ab7adafd01e8d145d",
"help/debug/custom-data.md": "445ed27dbda5b0ca035504e2f0cda023746def5edd3aa1f62189e133946e293b",
"help/en/README.md": "cdf84634ac731f1f434ec496b171307c38fccd6e6df335dd4dbf074e3e2cc12e",
"help/en/administration/README.md": "478a59925324a3dfb3da193b8e83b9a45e38fe77cf41644efbc35cb0b65e0298",
@@ -696,14 +699,14 @@
"help/en/content/README.md": "7c871512fea7f1bf635d5a816e322161dedbd8c158d520cd0385467e8dadd1ff",
"help/en/content/blocks.md": "34657f0a7ab977bf78d5b7645bf80a0755f40830bab4d4620ca7f39ea5992210",
"help/en/content/bulk-editor.md": "c99d5eb5058d6714f0281b84bfdf9020fc63ef27519c095af5f92fa2d5e96b44",
- "help/en/content/catalog.md": "99a6e5c3e17ac0c7f2e04cbde4f790334f687b3aebed5815be5006ab361e530a",
+ "help/en/content/catalog.md": "8f12199f4d727e0ad715f91e980de638357fe1fa5af0702351fd3ee30afe29e1",
"help/en/content/content-model-tools.md": "fc50f8c334a129d3ead4a58503a7659abc2b1fdea7c9d277feff1e3ba4bb7b6b",
"help/en/content/content-studio.md": "194fe41a1eefdf7da3c9a47b8539ed3ed9d785d2a75816a39da6d2784ec40925",
"help/en/content/directories.md": "cfadf2361ead3bed9b63435041af8c91e24ec8b492906f0d9b976047edc123fb",
"help/en/content/documents.md": "2831a54d7c9e02b89231c48c406475dff64721e48ce76d3bd92b768043422333",
- "help/en/content/navigation.md": "18f6e033c5c01eecbd7a3bc4b7c557f272f0039c811ea6f4ec2354206b0a5f21",
+ "help/en/content/navigation.md": "585f6212c72faf32fd67bca6f03715b5e5754ae9d6b162174cf40e76cd9c2454",
"help/en/content/presentations.md": "f255470ae47453d2e458b1829316c0e1e8e7fae73c9a1c1190d89f522b78d871",
- "help/en/content/public-site.md": "619cf5d38d49f6dc1c54836d4e27853da03a82f3404b69ce7e0c395a3222ec45",
+ "help/en/content/public-site.md": "17352cd32290a5172ce7ec149b53de83a63f7ac60d0e42e7cbbbc17363a74c76",
"help/en/content/requests.md": "71aea0d1fa6dc9b6f6dd41f1c7a4d80af93c0b5e18341dc26f59d1f0c156604b",
"help/en/content/rubrics.md": "bd69370ac19ab0aaf6d4c1cba615ae21f57cc3e241ecb6706f23a69a794fb3c0",
"help/en/content/templates.md": "f91c141dfee841a1af8321625788cecb9dabaef75901e188127815b6d5b5ce6b",
@@ -725,7 +728,7 @@
"help/en/database/security.md": "f2d0b218fe9ba8c8c5fcef2b6a832cebc58e0af26400a13f0c4a32d7d2ce1d93",
"help/en/database/tables.md": "d117e9b6ecf04f8b8cbcc99e81874a09b84871dc8273a248ba676304f37e8c67",
"help/en/database/transactions.md": "9cd1b15425647281d40d11d7d0453f8758a1c41859ce3bf1de5d091ccbf2246d",
- "help/en/debug/README.md": "4000fae72a034ec3be9d1059fd0e0a850efa8911dbd4b8498d8cb0b23ddfa4a0",
+ "help/en/debug/README.md": "e148c32ba9c0c3231c02094263410c19519b81b38f5e74564dfaec4f91dc6f7b",
"help/en/debug/custom-data.md": "0cd068b8d74bd98240bb86d9d4bc01d2630129eaeff8eec88b1630a094016f32",
"help/en/fields/README.md": "46c4fb2b44f1fef2f25ed2be3c730dd1d4e1aa0f48950e6069c3772acdcc5b17",
"help/en/fields/development.md": "4a953779ec8f08a79e235bd658d58ebc69f5758e2d8c55d49d5bc074e5aa0a41",
@@ -753,7 +756,7 @@
"help/en/helpers/Request.md": "3e919b34a2559e818938895a6e92d3ca87550774712d03913044e58a4d304d99",
"help/en/helpers/Response.md": "ca68c1d5cdd133daaa78077e00a15fd702bc47890ad33c224656ab4c33c17e52",
"help/en/helpers/RussianStemmer.md": "c55946a78392ac8c0534187cb2b4232f5f0aa6276114e0c8e31aeb75beaf9fa0",
- "help/en/helpers/SearchText.md": "42213dae0a4634936c01a67a4beff51167921b5e2e7f71c62179acc1eb2ddc1e",
+ "help/en/helpers/SearchText.md": "7962291f5e82ae3ce3a5c6749b479e2e5a02d83202910f57d4d9bef03791e657",
"help/en/helpers/Secure.md": "c95d170799ea7bae219b16f55385db17fd5f52db2898b59fb949fd87fa0a8e50",
"help/en/helpers/SoftDelete.md": "34912f62fa21bff29d5124280f11ce911f74f2a9fe836ac230879d0f992094a8",
"help/en/helpers/Str.md": "8c850a76b9932046bdf0232735a0050bbaa8ba4dcc2526d819862adfe2af368f",
@@ -769,7 +772,7 @@
"help/en/hooks/lifecycle.md": "ec82cd9ccb2bc48e9054ec1876c40552859da4ead45129f920026f821f84e87f",
"help/en/installation/README.md": "a122a89fc4a910064f02fc906a0fdf3a9098147f9ed2f98a75185ed66d190f29",
"help/en/installation/admin-directory.md": "26a7b2198078f8ea11dba7fa6a0699cf35c66e78fc88279c82012e6a01f59a38",
- "help/en/media/README.md": "1f346b26bd6cb0f7238204ed0cb1d56cea9f6bac7f96e06d062fc8d01226d702",
+ "help/en/media/README.md": "3e6f40e20bc3e7e5c359600b0047534690e9e6f61b119aae91b282f797b68bda",
"help/en/media/analyzer.md": "d49175369396da1cc3d50ec4cbc7684f36971021532c648375999e37f90a4f20",
"help/en/modules/README.md": "fac8da620ec1f087bef3c7cc3a4178d95747ccece3524ee6c53ba6c3a65b4a21",
"help/en/modules/antispam.md": "1808f910d530f7068e60e2a9306c15ae5b69cccc30ee3e492f35cd535004f65d",
@@ -777,9 +780,9 @@
"help/en/modules/benchmark.md": "e7b9d233abd386db41413f45c79639a3bebfc37241bdbab461028e87a5615f87",
"help/en/modules/cache-warmup.md": "d92183948e03dd4f9a18308d36df7d5ba57e7e87f268b5f3f1de38ed99c518dc",
"help/en/modules/comments.md": "55f45f748f32fbd36233c43574e212f3fa3fdd1ce8eb819f9d1cafb53215ed97",
- "help/en/modules/commerce.md": "d916b293c7bfb3567fee02f5d9f15de0d5329dde60d5c5d43f24bce660860156",
+ "help/en/modules/commerce.md": "e31cbf7ead9d758401e35d7723f0189a354ce818a4fcf5a0118c73795088dcf0",
"help/en/modules/commerceml.md": "610f6b2f9abbeeac604da0408192ae52dae7d6f3fb09e0d321e14d1cc190b808",
- "help/en/modules/contacts.md": "3d4d7b109a7a7bf20ceda0cb0da65634131a837c7d39c4a1433f317c3bad3512",
+ "help/en/modules/contacts.md": "81c818e9cbcd1d61f34d9199219ea160a04bf88af110b32c3527c65fba65d39e",
"help/en/modules/content-packages.md": "4b89df6d05fa75e484e38e404d53fa7ecc6b61aa6930fbf74cf9b6611bfbf532",
"help/en/modules/contributions.md": "ec283e61f09d6008a7208fced495a2748685c50bbfe22b384f458cf338e63125",
"help/en/modules/crossposting.md": "7b8fc0b89c90e27c8e90431221cfbf7eca4c3cd2b5495a0c8ed5d76906dc1daa",
@@ -818,10 +821,10 @@
"help/en/modules/rss.md": "22958cb500ddfcd83201086b36e43365d51f9f125b8663ec38e2e30eded4d0ae",
"help/en/modules/scheduler.md": "fc0c34af054e0081c6ad8cb1abddcff014c0a6eb77178c111fb0b15e0585c75f",
"help/en/modules/search-analytics.md": "3e21c935d76171a5a74cba9fc2904092d2d30062918c2f7ff1988aae0e499264",
- "help/en/modules/search.md": "6bd582bc687436d99d292655ab4f9c3e3726c53a5dcc7d9dd94744545d22daf0",
+ "help/en/modules/search.md": "10cec34b6a4eb452b514f224e59e9f600904548887c63f19a2cb61e6ed6bbe62",
"help/en/modules/seo-audit.md": "e18adcb79847fb5a1cedc8cfff03b5938eac1c2e2d22816ecc5e5b02b48b95d3",
"help/en/modules/site-readiness.md": "dbccccf0633f0e72d32cc8a2b0de319dea576f28b67f0d9c2907106c97fef1c1",
- "help/en/modules/smsc-auth.md": "d66bdd5cb748db83b565e7cbc8150bd38099de778e94c9c4cb0ff1d065703479",
+ "help/en/modules/smsc-auth.md": "1c7a3337404e92da4e7f60f53d11fb6e791c18ee8e1533590deec111e3efe1ec",
"help/en/modules/system-health.md": "f43c3745ee1437f392724d35299d208dd73ff344a3efff60273cc7b23c6fa731",
"help/en/modules/traffic-analytics.md": "5ed8a48a4d05350d8dced3db8fadcedbf32a2bf80147da39872a0f591ca8ce84",
"help/en/modules/tutorial.md": "2e6bcf3e7de1799fadddbe5b5dd56de613c91baf2a4d6c45232eb2144279780c",
@@ -852,7 +855,7 @@
"help/helpers/Request.md": "93b98e898273dc5a1e0cc77babd14487181464bff2c30cb8e08f5425c520ae61",
"help/helpers/Response.md": "d96965599876b9244a6a2deaee2f18f9d033959a0f0f7b942c33e3aa1fcf143f",
"help/helpers/RussianStemmer.md": "10a849612da6b01d3fba062550a7765b1bfd6f558c2751e5ceb67887924bd2c3",
- "help/helpers/SearchText.md": "314fcbffaa95ba52840bf07d0a0070372b51f0db62dcea4ff770184a467d6152",
+ "help/helpers/SearchText.md": "cb28d568ffefe43002611300c8273f2e8a489742c3123a3a4ca0795324e88d56",
"help/helpers/Secure.md": "17ffdeb9c226be816e0c00dc92c90bd86643261709bc889fd238afef5963b61a",
"help/helpers/SoftDelete.md": "787d30d2880a304d22b3086a3a5f8deb5d282b5617bb0c74e0b642295f54c39d",
"help/helpers/Str.md": "34b54c40ba492a5899e32fcd695380ec458518ca287ccaafbda4c0f38d9bfcf7",
@@ -868,17 +871,17 @@
"help/hooks/lifecycle.md": "0d73b7eea62dbc1cc5f3a07449f465105ad8a477adf85b22d2adbc53529b81d2",
"help/installation/README.md": "6a9c434bbe81f493ad3b07ad83fd8c8a8739423f67f3e657b5a9a3dba0d2738a",
"help/installation/admin-directory.md": "fcb31d8bad1c18dd7e5c6843dc4f7b8c4139dbceaa670a7c4e0c060479f5ef9d",
- "help/media/README.md": "9a8027ffa29ad342e194868890174fbdf59b5dbbbd7ef39db9f1cf79888b2070",
+ "help/media/README.md": "517d9f25119bcac6910f49a9a4c9876fca9dcf6f128e0ce8eb4160648e3bc20d",
"help/media/analyzer.md": "a057f3e95842d8d5168f9d456be656c91dc2382c26ce9f205bb620a902132ad4",
- "help/modules/README.md": "01f2d804dbc841e849e41fc07e9d86d5118adc30a3da98a39672200c2ae17d65",
+ "help/modules/README.md": "7baf0060effebd1f0f6870b75e617eb3aeabc8bd36bf174de790cd23717e69d6",
"help/modules/antispam.md": "ddcb858c6c80ee140addbdcd0e27ae3f7900100f1745ee006edabf52d0955fff",
"help/modules/banners.md": "e8faa2a923c5f5c39a0b7caf80a511d03fb680bd2604ddc59ec0362a244ee77f",
"help/modules/benchmark.md": "6c31f16fc3f083f29c65398c38b78609a3d94da168d30a93825af8ee9650eb0d",
"help/modules/cache-warmup.md": "56c05ec252b44a1a0d1c4b09efa90cf9d450b0b138787903c898f56b377b8749",
"help/modules/comments.md": "04bcdea5aa7740e3749b51dcc9fc2954e98b22298d3e4fcdcc9865d94e1d0376",
- "help/modules/commerce.md": "7fac85f0cea873bc57baa79c5bc57bd4d5bdb11af291d105e01785ab84f3cedc",
+ "help/modules/commerce.md": "0255c08bdf10f7db8e530c15e987c5d137ee115434ca0a0d71a831cc9fbfad9f",
"help/modules/commerceml.md": "60a0a5c541b560b5a6a4ed8213d9145caace68c15be534af13f5f817eb5251ba",
- "help/modules/contacts.md": "8df21307a300a335aaf243cb42a107829114ac031da128cfdfc6f4b94452dd3a",
+ "help/modules/contacts.md": "cb4284709f7b469164e3ef4c817893cdcaf4eba1b460164fb36f75a798bc457d",
"help/modules/content-packages.md": "8dde0ebc897b7891a6afadb6491f4797bef26622918addab67f0f4ebfd719678",
"help/modules/contributions.md": "3213f68af10e1b6a200aa61a3aaa9dad270da7ccd675b43bf7cc1ded90b55e60",
"help/modules/crossposting.md": "d6d0922c8d48d3f35418a00227a96d9343de024ecbc8affc1712c0a223f4547d",
@@ -917,10 +920,11 @@
"help/modules/rss.md": "0f8fe5fa5bd4991b432068a5a520d4a45d41dd6a2c6011f7f293f74c8d9c1588",
"help/modules/scheduler.md": "a39cf392ce9cf5af63e92063b95aea63979a24b27218a4b090b94834ef90b536",
"help/modules/search-analytics.md": "0444e0dacabe408358bcdfda4b6b2a475095e8b1c0ffc1361ff58c4db0576d35",
- "help/modules/search.md": "3dd37c32598c12654dd4ab97ec0c50d42ffc5083485ed37be0f135296af23afe",
+ "help/modules/search.md": "a39ffbd45e7d5786cd16d2678f5386c55c3081063177f6525b12849da1c251d3",
"help/modules/seo-audit.md": "b0fb78eda0e83c0711b7717fcc730e7bc0cb86c6d692f21aa749cc4975f0ee27",
"help/modules/site-readiness.md": "2c7f845ece693ef14f23ea2b3b226f38ebfed305fc67bf842fae971f0e12255a",
- "help/modules/smsc-auth.md": "13b844d102adb451d154ee9aff9b44d8b5faaac67c4491d73c832962f9a194d2",
+ "help/modules/sliders.md": "f3022a36b5c483f49d63b86b4651989e98636018fc8a7449ca45b2875277a3fe",
+ "help/modules/smsc-auth.md": "361b46fbc4812513f59e8ba15d095607851f835d7765ac396c3efd79ecde10e3",
"help/modules/system-health.md": "671d44d89f0d3ada4fcaa4ef3d8a7662a6c77dea6c0cd567d8f3e923d29e12a5",
"help/modules/traffic-analytics.md": "12f966acc6ffdf73e2d40682c6db203f8dc3918ba5461a78607c1d783ed54d02",
"help/modules/tutorial.md": "4239eef8d793b7bb799c901c821b229e456c7602ac694c0e3ffc5028b4eb57c8",
@@ -944,7 +948,7 @@
"system/App/Common/AdminAssets.php": "dcd58f19fda48263b2912a10dfdc24e3f12967683649c4f614a8d54c62d51b86",
"system/App/Common/AdminLocation.php": "d2729b0d627ddbdeb7f0c4b110664ed949f373946f80929c51688391c64352de",
"system/App/Common/ApiTokenRepository.php": "0795e1add3723708240949b7aeada364fbeadd74366884b57e9142efcfaf617c",
- "system/App/Common/AuditLog.php": "4e2dfceba55fd653d071261766c9051322a03a994034677463782ca0d09f3f87",
+ "system/App/Common/AuditLog.php": "5306d21085ff7ef7313f5798fbd416872825ff7d2831f94cd9744de010e1cc2b",
"system/App/Common/Auth.php": "647421cae2ec8bbf273d687ac1fda134c7bcc28db4977e01f5896c79e7c2c83d",
"system/App/Common/Auth/IdentityLinker.php": "7da4d902405bb4ead2278e5bc3578cd0318422fe82318bec94257ed44d5ba98c",
"system/App/Common/Auth/PublicUserNames.php": "0345d1f452e0a9accaa9b98eea71f27a428692a5b4f2ad225a10ad8bcf777684",
@@ -1019,7 +1023,7 @@
"system/App/Common/Registry.php": "7b4ddbb3f96a7ad54a8d43e151cb595e598d3620b368d21721927ce9d54e1ea3",
"system/App/Common/ResponseSecurityHeaders.php": "b159a7105d3c1fb3ffdefbbc5997741d7b0f83bfb26fabcac96f61b53969364f",
"system/App/Common/Router.php": "4b0dd3fd9f732177070ce3326b691a8decfbe698aa0d8cf687a18f6c02b73d91",
- "system/App/Common/RuntimeConstantSchema.php": "1ea1a029a29e4b0284d047f6ee6ec97dd50f4b42556e909a1a03481a4a380105",
+ "system/App/Common/RuntimeConstantSchema.php": "e67a2de96a2496e0463a3b9e9d66fa6b2ef0e4b7dff768469ef472ba639797d7",
"system/App/Common/RuntimeConstants.php": "decb1277dada75ee2b5c7d531f064db7ffecef5b3ad71d306111b2a45121d34f",
"system/App/Common/RuntimeDirectoryGuard.php": "2263f100530f7b0a7790f698581dff14181d4e2a917c12ad1ea888e5df1f59b2",
"system/App/Common/Session.php": "4a870a109c75ca941bebfd798d64b1396dde7d56ec060d9491f7d71b70a13deb",
@@ -1041,7 +1045,7 @@
"system/App/Common/WebMaintenance.php": "a91f9ee8371da6c6a020d9ce39e1f9f7b517b2644ea51ada65f1f487ff337e6b",
"system/App/Content/BasketTables.php": "1c4d1d9a5ff894c6ebb0edbcfd7bb6f791124e46dd8bed3b70fb0ef2c15069a0",
"system/App/Content/Blocks/ContentBlockRegistry.php": "53bc204e7dd1f9d61781049627162341a4d7e656783915161fa5c4eae4cfa228",
- "system/App/Content/CatalogMenuRepository.php": "d2b384255fb9e67264f071064dafd645913a73ee640e559d600b1671455443eb",
+ "system/App/Content/CatalogMenuRepository.php": "6ee0890c5407fa37f5f4a88c5e27ca58b27407a8362e012320bc26fd072cf523",
"system/App/Content/CatalogTables.php": "5abd93b17142ea5c57a0726c8c6e3dcaf896790cf4ba31ea69be19a828ac9517",
"system/App/Content/ContactsTables.php": "d0042c761b854903b483e1b3a30d6014c596eef1a03c40cdbedae4fecb3c25d8",
"system/App/Content/ContentPlacementMap.php": "55950a599eb5385e2d81833815f0e2c08bdd06c61450565052ec1c19d38e00fe",
@@ -1050,7 +1054,7 @@
"system/App/Content/Directories/DirectoryRepository.php": "47d4f7a91841ff437c4448aa71f6a2b38c621dcf5a1204bd2ff4948f3ad671c1",
"system/App/Content/DocumentTerms.php": "353d0a36e0cef77d4016f9edf9c2a2a3a27db953a3eb4cb91d7ec49af18b0609",
"system/App/Content/Documents/ContentCacheInvalidator.php": "d2bfd72d4df1a05e281b88543fabb8356ffc9799d37286c3767c6a0173ea91b1",
- "system/App/Content/Documents/DocumentAliasRegistry.php": "aa87cb8b65990f480f5b4f5dea83948dec9758fb4069b43eeba4be7f7752be51",
+ "system/App/Content/Documents/DocumentAliasRegistry.php": "803ccfa70c3648e2e4c625aaa827597431d4ea54dbf83618b904f699f9560846",
"system/App/Content/Documents/DocumentAliasTemplate.php": "47bbcead460d5d4160daea31ebfd36ecc3657f74f905162f20ee0d1eae78e3db",
"system/App/Content/Documents/DocumentCreationPresetRepository.php": "a58de0560e194d8386932a4e2e0e807a097ba1bad1f56cc8d11587f6c20e54c3",
"system/App/Content/Documents/DocumentExcerpt.php": "8c5742f976a492531af95f2291e8c824c3a9804a54a27997f9f51a716a57f19d",
@@ -1061,7 +1065,7 @@
"system/App/Content/Documents/DocumentMediaFinalization.php": "5ea218f79f835a4fc25da0199aab87137e620b60c31d8e2f587a1b911b3af6b1",
"system/App/Content/Documents/DocumentMediaFinalizer.php": "785487028e221726c6bc15662f83ed985701a8e5d21e9fc9605e816ef4c835b7",
"system/App/Content/Documents/DocumentMediaPath.php": "23b40c98d3cf9a73df3b7f7083b58b60dd80b8bda71e3e3a6354342cc72322a8",
- "system/App/Content/Documents/DocumentMutationService.php": "5ad32bacb4ad5c2b2760db613be291f154ea3882695c2aa05607b1c5bc80f25f",
+ "system/App/Content/Documents/DocumentMutationService.php": "868e1301cf822516debe1f441f80eb1897d0a1617f8d27a6a2a1dced8bafa6f1",
"system/App/Content/Documents/DocumentPickerRepository.php": "0e8efb5b42aadd74725213b8302b53b5147d7e0eb2e83d301a5f073bb0c13142",
"system/App/Content/Documents/DocumentRevisionPayload.php": "c7f83350de98b6d98531508eafecc4a2b5a0551c7f2ba77a9e5b954fd1287ea0",
"system/App/Content/Documents/DocumentRubricCodeRunner.php": "a9c04196b76ab91c7b1f8b53afbd45fc6727805c953fb5fe5ff36b69846d389f",
@@ -1091,10 +1095,10 @@
"system/App/Content/Fields/FieldSettingsForm.php": "e436743e17fab7aaf14dab6ce034fa2ece2f5d165a4aa04450d034606c8e2a73",
"system/App/Content/Fields/FieldType.php": "47979aaa49b773e2a00288ffccb746218b58ece8fa6d178a90d0cc9c853055d9",
"system/App/Content/Fields/FieldValidationException.php": "1a1d6d5278d6f8ff697e5afe75e0a6f59db2b12121c93b786fd87f8b81287717",
- "system/App/Content/Fields/FieldValidator.php": "5408849670818b379a66d5db9b7aa0d94ebbd8438d7788124f62cd221dae5561",
+ "system/App/Content/Fields/FieldValidator.php": "49a2342783f994d054ca037907cc7101a9cf3a4ad5f288804673c609eb2b0d63",
"system/App/Content/Fields/FieldValueCodec.php": "5868e9aaa11a8aa2c4dde33e8b358861b4a5a49f6ff235c1d0e22b836dc3df55",
"system/App/Content/Fields/HtmlSanitizer.php": "003ddd83bc6b3c89e1ccdbfe4abf94e013c4a6b1272cf29c1ed36790f3ac7919",
- "system/App/Content/Fields/MediaFieldValue.php": "5821060785f859b414a448d4998a9e7b40df69d536574f0fbe600f1d7611ea35",
+ "system/App/Content/Fields/MediaFieldValue.php": "2cc5db5159f68eb0132937d63641eced0b79fd1465c69cdb1340afadfe88381e",
"system/App/Content/Fields/PublicFieldRuntime.php": "9abd8e5dd43c56b27dcbf87d258cd9764ae1d67e95a397a63a6c6516f7a417f8",
"system/App/Content/Fields/PublicFieldTemplateContext.php": "785d6757710dee59d8d30756917235bb4f49eb5bd743c01d2365c05d74bf0a5a",
"system/App/Content/Fields/PublicFieldTemplateResolver.php": "72c0750c50129cbabc0a97147fe2f2f4b2b3811cd77f4de197dc3753db46903a",
@@ -1188,7 +1192,7 @@
"system/App/Content/Rubrics/RubricMutationService.php": "e3190ef1ddd94bdaf077148ce84c5758b2a32683369730a2b847dbbd0d72a74c",
"system/App/Content/Templates/TemplateMutationService.php": "8c7e52202454e35eb0c8176325cc23ba760ac40ff0430cbba5e1446c83cf7a87",
"system/App/Frontend/Auth/AuthService.php": "1cef24cffd7773f3783ec96479b8e7daa1808b03a34dfc8da37b84e6e09144b8",
- "system/App/Frontend/Auth/Controller.php": "04793123f0d4bef77141e3f8471d454ac5b4d2b17dce4630eca40da0e2f0b576",
+ "system/App/Frontend/Auth/Controller.php": "14a425c24653fdfda4225550635130bcdf67a6d63a1eb3817ad86a41d292efe9",
"system/App/Frontend/Auth/Feature.php": "dbaeac564886c7949e0e47c85fc5496c25221d880c4f03cee40cc5e15d042a1c",
"system/App/Frontend/Auth/FormTemplateRepository.php": "bcd8797e63cb67803aef2267a2c62e3ad368b5fd947057a768adbb1288ee9281",
"system/App/Frontend/Auth/OAuth/HttpClient.php": "b09acf92b312bd05ca78500fccaac3b77d36a1046d64d3615a00d60af4e3594f",
@@ -1197,9 +1201,9 @@
"system/App/Frontend/Auth/OAuth/OAuthService.php": "c310a2a6756425a4b2875b12c048b0476dd5b424e89561ac1fa73aebfb3cf045",
"system/App/Frontend/Auth/OAuth/ProviderInterface.php": "c99525fd51a1bebc24c189f52f1a08bcef087b98ffa960d1b378deb68022d9ba",
"system/App/Frontend/Auth/OAuth/ProviderRegistry.php": "e0ff4bc12ef2349d4f914299c78715438d9fbd44ab98e00937534786baa07800",
- "system/App/Frontend/Auth/Phone/ChallengeRepository.php": "1e53d355aaf0adcc8e10a2b1b50e638916fd688c62a7ed6a4bd9c161c14a6390",
+ "system/App/Frontend/Auth/Phone/ChallengeRepository.php": "2bfd7ad7b4640e653d3ad0e1ed5f1779ad8df2b569bee071c040d44996c15ea8",
"system/App/Frontend/Auth/Phone/Controller.php": "bb07bfb74a89742c2dbeeb7edf79f02ae43ef6a252d02a3bb7c3c78144cf280d",
- "system/App/Frontend/Auth/Phone/PhoneAuthService.php": "783b671cb6b6f80eb4ecbd7bec86d417dd7030c7768ba01f742600e4a6b55a89",
+ "system/App/Frontend/Auth/Phone/PhoneAuthService.php": "4f31afa6d600a754cf76805b2a475bf47ffbf7a540b65fb99df098f227211a5a",
"system/App/Frontend/Auth/Phone/ProviderInterface.php": "55107c8f6a89bea01b91314dfe523b684bccf5bf9f1a3c8f047fd3a509aa6d00",
"system/App/Frontend/Auth/Phone/ProviderRegistry.php": "fbeb488c9b63843085b680500ecafd61389a29f55e0d76692550939e378d2f16",
"system/App/Frontend/Auth/ProfileRepository.php": "9cd7be35b048faf407a925e6369a5895b8bfafe6cf756eb3c4321ea4979a9af6",
@@ -1233,7 +1237,7 @@
"system/App/Frontend/ConfiguredPaginationRenderer.php": "df4d28a386e4180fd0dbddbc30f08ca8c6345e2f3b0d196482fa9d2a5d4f4d32",
"system/App/Frontend/ContentCollectionTags.php": "337085d73f0ad609b5a37a4fa72537459f4ae2e5f503e39a8750be95127d89b5",
"system/App/Frontend/Debug/PublicDebugCollector.php": "6755ad710e9d045b2bfa74745354f717c101236b5d64ad5530fd779468969a00",
- "system/App/Frontend/Debug/PublicDebugToolbar.php": "89a0fd4a1cab3ac8e3576cd8116332edbaaf6e09d3e05b0daa5798cd8f59c6ff",
+ "system/App/Frontend/Debug/PublicDebugToolbar.php": "7a92630b59483e63d623fa769e13c246e6bd657c6d027a5d14ab8f95986da0de",
"system/App/Frontend/Debug/PublicLayoutInspector.php": "8a7f5ea4cff9834ad13cac7e0d2380d878978e406e71860e1ad9ca183e427dab",
"system/App/Frontend/DocumentAccessPolicy.php": "13102e35dada9eb0e174189b976605257b48a32756e801d1ec5c358d0c0912b8",
"system/App/Frontend/DocumentApi/Controller.php": "7c5ffecf7e1515710931d7c31b5b2c5f555f8d35778a1fc202a8136b07c2490c",
@@ -1336,7 +1340,7 @@
"system/App/Helpers/Request.php": "29cc2cf20e1e4acd6e2f23c3de9b9606aec3f44d0b3577613b4fde376f7ae858",
"system/App/Helpers/Response.php": "beea1596703526b7a2424459e392487e8bc8a9ce216f8d70bd2b09564709e0b9",
"system/App/Helpers/RussianStemmer.php": "60a896c8254290ae554273132c103c1c209a14944f52cfd631e7a3ae13145529",
- "system/App/Helpers/SearchText.php": "1747521adb640161c6f4df648134c8349769e3cd7ddbc726118af81f1a4dcbf0",
+ "system/App/Helpers/SearchText.php": "12e2fa63a99dd47dd00316eba33f9e162cbfb90e5a5fb9c816fc9c21de0c6ec1",
"system/App/Helpers/Secure.php": "99a5a170cd574e5b6a6c3b433c5b8bac92407fba62088c93e893e215f18162a3",
"system/App/Helpers/SoftDelete.php": "9b85f227bbbc12e4bddd9bcf211b477044041b87bf94d1a422f80d46a10c3ccd",
"system/App/Helpers/SplitTest.php": "d0c875c264df66509b53693dccae8d9f482b069dfe98eb5820481077f3c4508c",
@@ -1350,7 +1354,7 @@
"system/language/ru/lang.xml": "7d46cc9822f064a1af4d6528ccfe2dd4a9cc318912e327fd807befd2ca3dfd11",
"system/preload.php": "66bfb1aaf36ba85945b729b09a37e96ead0be1cc2749ebd9abd4b9da4f25ba0e",
"system/public.php": "3f72b5bf44bc0ba5ba6fb6b3adc2e2f9d5e9902d9cdfa3d7b748f4aebd179b87",
- "system/release.php": "e9aeb913c895912f8dae625b0311fb38cd871c20232eede2948e934ba4bff656",
+ "system/release.php": "73af5bab9ab6ad78aa6c4fc4509dd4419cf68e66142381c1500c01a72b655c8e",
"system/vendor/Captcha/fonts/palatino_linotype_bold.png": "38b6a1d4a4bd1d0c056205119c5e24176913ac0d898371f53a0339952a319365",
"system/vendor/Captcha/fonts/perpetua_bold.png": "55dbbe97dae128dea47b5e8d640414b3d813119c854c688037a20a581ec8fdc4",
"system/vendor/Captcha/fonts/times_bold.png": "d3d32ce1f418e3c3c3b1afe3b6acabadb36207bc12368f5e86420cf1a529ff8f",
@@ -1932,7 +1936,7 @@
"system/vendor/Twig/Util/TemplateDirIterator.php": "b52ad2c2cda8efdb9b6b3719fdd64c5a3e38bba1ca440c1941132ac60d756ec7",
"templates/index.php": "1a0b826446e8c8211372f1f45a6ace298395116cd80bc1cbb2cdef7762038a95",
"templates/public/index.php": "c57a4bb3eb5ae6c0394cc748a14dc7f703a9d819333f181f0edffffa680100bc",
- "templates/public/templates/.htaccess": "ef94d4c2ac2a038b6c2ae673d9573333a0a20f7fac9e2d75af11c6c387a157a3",
+ "templates/public/templates/.htaccess": "ca761a92f2b6eb47feffcf200a8fd846e7fdd8abe065008d29beff0319ca3496",
"templates/public/theme.json": "4fb596d58dbda7f58c26c4f3640c56d6f6981f402ffbd368229adddc23683690",
"tmp/.htaccess": "ca761a92f2b6eb47feffcf200a8fd846e7fdd8abe065008d29beff0319ca3496",
"uploads/.htaccess": "90cd571b4c9f09a63732254fbcdca92cc7da8560a6e5b4d9ec7e58ac40e528bf",
@@ -1942,6 +1946,7 @@
"file_owners": {
".gitignore": "core",
".htaccess": "core",
+ "CHANGELOG.md": "core",
"README.md": "core",
"adminx/.htaccess": "core",
"adminx/Support/AdminLocale.php": "core",
@@ -2357,6 +2362,7 @@
"adminx/modules/PublicSite/PresentationModel.php": "core",
"adminx/modules/PublicSite/PresentationsController.php": "core",
"adminx/modules/PublicSite/PublicViewTemplates.php": "core",
+ "adminx/modules/PublicSite/TemplateMap.php": "core",
"adminx/modules/PublicSite/assets/presentations.css": "core",
"adminx/modules/PublicSite/assets/presentations.js": "core",
"adminx/modules/PublicSite/assets/public-site.css": "core",
@@ -2373,6 +2379,7 @@
"adminx/modules/PublicSite/view/map.twig": "core",
"adminx/modules/PublicSite/view/placements.twig": "core",
"adminx/modules/PublicSite/view/presentations.twig": "core",
+ "adminx/modules/PublicSite/view/template-map.twig": "core",
"adminx/modules/Requests/Controller.php": "core",
"adminx/modules/Requests/Model.php": "core",
"adminx/modules/Requests/Revisions.php": "core",
@@ -2848,6 +2855,7 @@
"help/modules/search.md": "core",
"help/modules/seo-audit.md": "core",
"help/modules/site-readiness.md": "core",
+ "help/modules/sliders.md": "core",
"help/modules/smsc-auth.md": "core",
"help/modules/system-health.md": "core",
"help/modules/traffic-analytics.md": "core",
diff --git a/adminx/Support/SectionHelp.php b/adminx/Support/SectionHelp.php
index ff86803..59aab58 100644
--- a/adminx/Support/SectionHelp.php
+++ b/adminx/Support/SectionHelp.php
@@ -81,6 +81,7 @@
self::item('#^/documents/(?:create|\d+/edit)$#', 'Редактор документа', 'Создаёт и изменяет страницу сайта: её адрес, публикацию, SEO и значения полей выбранной рубрики.', array('Набор полей и их группы задаются рубрикой.', 'Изменение alias может автоматически создать редирект со старого URL.', 'Ctrl+S сохраняет изменения и оставляет редактор открытым; ревизии позволяют вернуться к прошлой версии.'), 'Смена рубрики существующего документа запрещена без отдельной миграции полей.'),
self::item('#^/documents#', 'Документы', 'Основной реестр страниц и товарных карточек сайта, построенных на рубриках и их полях.', array('Фильтруйте документы по рубрике, состоянию и названию.', 'Публикацию, копирование и удаление можно выполнять из списка или пакетно.', 'Удалённый документ сначала попадает в корзину и только затем может быть удалён окончательно.'), 'Документ №1 и системная страница 404 защищены от окончательного удаления.', 'content/documents'),
self::item('#^/public-site/presentations$#', 'Представления', 'Создаёт повторно используемое оформление карточек, списков, слайдеров и других результатов.', array('Черновик проверяется на реальном материале и не меняет публичный сайт.', 'Публикация фиксирует версию, а назначение выбирает место и режим применения.', 'Представление меняет внешний вид готовых данных, но не заменяет рубрику, запрос, блок или модуль.'), 'Для совместимости каждое новое назначение начинает работу в режиме «Текущий вывод».', 'content/public-site'),
+ self::item('#^/public-site/template-map$#', 'Карта шаблонов', 'Показывает фактическую цепочку сборки обычных страниц, списков, каталога и товаров.', array('Общий шаблон создаёт внешний каркас страницы.', 'Шаблон рубрики размещает поля, блоки, запросы и модульные теги.', 'Тема оформляет только подключённые компоненты, если не включён отдельный режим оболочки темы.'), 'Карта работает только на чтение; каждая кнопка открывает штатный редактор выбранного слоя.', 'content/public-site'),
self::item('#^/public-site/map$#', 'Визуальная карта сайта', 'Показывает деревья меню, связанные страницы и опубликованные документы вне навигации.', array('Уровни строятся из действующих пунктов навигации.', 'Ссылка документа открывает его штатный редактор.', 'Документ вне меню может использоваться запросом, каталогом или прямой ссылкой.'), 'Карта является обзором и ничего не публикует.', 'content/public-site'),
self::item('#^/public-site/placements$#', 'Размещения компонентов', 'Показывает, в каких сохранённых шаблонах используются блоки, подборки, навигации и модульные теги.', array('Вкладка «Размещения» показывает каждое найденное место отдельно.', 'Вкладка «Где используется» объединяет одинаковые компоненты и показывает число связей.', 'Кнопки справа открывают конкретный исходный шаблон и конкретный подключённый компонент.'), 'Карта предназначена для поиска связей и ничего не меняет на публичном сайте.', 'content/public-site'),
self::item('#^/public-site/templates$#', 'Шаблоны публичного сайта', 'Редактирует общие файловые Twig-компоненты активной темы.', array('Список материалов имеет системный резерв.', 'Оболочка страницы и остальные компоненты темы редактируются без поиска файла вручную.', 'Сохранение проверяет Twig, создаёт ревизию и очищает публичный кеш.'), 'Шаблоны рубрик, запросов и блоков остаются в собственных разделах.', 'content/public-site'),
diff --git a/adminx/Support/ViewOverrideEditor.php b/adminx/Support/ViewOverrideEditor.php
index 0809695..af9f085 100644
--- a/adminx/Support/ViewOverrideEditor.php
+++ b/adminx/Support/ViewOverrideEditor.php
@@ -100,6 +100,7 @@
}
try {
+ ThemeAssets::registerViewOverrides();
Twig::twig()->createTemplate($content, 'public_view_' . preg_replace('/[^a-z0-9_]/i', '_', (string) $code));
} catch (\Throwable $e) {
return array('ok' => false, 'message' => 'Ошибка Twig: ' . $e->getMessage());
diff --git a/adminx/assets/css/adminkit.css b/adminx/assets/css/adminkit.css
index e7e6b82..67c68ce 100644
--- a/adminx/assets/css/adminkit.css
+++ b/adminx/assets/css/adminkit.css
@@ -5088,6 +5088,30 @@ ul.sortable {
flex: 0 0 auto;
white-space: nowrap;
}
+.media-picker-manage {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.media-picker-manage[hidden] {
+ display: none;
+}
+.media-picker-folder-form {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 12px;
+}
+.media-picker-folder-form[hidden] {
+ display: none;
+}
+.media-picker-folder-form .input-wrap {
+ min-width: 0;
+ flex: 1 1 auto;
+}
+.media-picker-folder-form .btn-icon {
+ min-width: 40px;
+}
.media-picker-url {
display: flex;
gap: 8px;
@@ -5279,6 +5303,20 @@ ul.sortable {
.media-picker-tools .media-picker-search {
flex-basis: calc(100% - 50px);
}
+ .media-picker-manage {
+ width: 100%;
+ }
+ .media-picker-manage .btn-primary {
+ min-width: 0;
+ flex: 1 1 auto;
+ }
+ .media-picker-folder-form {
+ align-items: stretch;
+ flex-wrap: wrap;
+ }
+ .media-picker-folder-form .input-wrap {
+ flex-basis: 100%;
+ }
.media-picker-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
diff --git a/adminx/assets/js/adminx.js b/adminx/assets/js/adminx.js
index 1fe7019..f2de9b9 100644
--- a/adminx/assets/js/adminx.js
+++ b/adminx/assets/js/adminx.js
@@ -221,6 +221,87 @@
}
};
+ // ------------------------------------------------------------------ //
+ // Простые формы модулей: стандартный Ajax-контракт без отдельного JS.
+ // ------------------------------------------------------------------ //
+ Adminx.initAjaxForms = function () {
+ document.addEventListener('submit', function (event) {
+ var form = event.target.closest ? event.target.closest('form[data-ax-ajax-form]') : null;
+ if (!form || form.getAttribute('data-ax-submitting') === '1') { return; }
+ event.preventDefault();
+
+ var submit = event.submitter || form.querySelector('button[type="submit"],input[type="submit"]');
+ var data = new FormData(form);
+ if (submit && submit.name && !data.has(submit.name)) { data.append(submit.name, submit.value || '1'); }
+
+ form.setAttribute('data-ax-submitting', '1');
+ form.setAttribute('aria-busy', 'true');
+ if (submit) { submit.disabled = true; }
+ Adminx.Loader.show();
+
+ Adminx.Ajax.request(form.getAttribute('action') || window.location.href, {
+ method: (form.getAttribute('method') || 'POST').toUpperCase(),
+ body: data
+ }).then(function (payload) {
+ var response = Adminx.Ajax.handle(payload);
+ if (payload.ok && response.success !== false) {
+ form.dispatchEvent(new CustomEvent('adminx:form-saved', { bubbles: true, detail: response }));
+ if (form.hasAttribute('data-ax-reset-on-success')) { form.reset(); }
+ }
+ }).catch(function (error) {
+ Adminx.Toast.show(error && error.message ? error.message : Adminx.t('server_unavailable', 'Сервер недоступен. Повторите попытку.'), 'error');
+ }).then(function () {
+ Adminx.Loader.hide();
+ form.removeAttribute('data-ax-submitting');
+ form.removeAttribute('aria-busy');
+ if (submit) { submit.disabled = false; }
+ });
+ });
+ };
+
+ // ------------------------------------------------------------------ //
+ // Пакетная загрузка обходит серверный max_file_uploads без изменения php.ini.
+ // ------------------------------------------------------------------ //
+ Adminx.Upload = {
+ BATCH_SIZE: 20,
+
+ files: function (url, files, makeData, onProgress) {
+ files = Array.prototype.slice.call(files || []);
+ if (!files.length) { return Promise.resolve({ files: [], batches: 0, total: 0 }); }
+
+ var self = this;
+ var uploaded = [];
+ var batches = Math.ceil(files.length / self.BATCH_SIZE);
+ var run = function (batch) {
+ if (batch >= batches) {
+ return Promise.resolve({ files: uploaded, batches: batches, total: files.length });
+ }
+
+ var start = batch * self.BATCH_SIZE;
+ var chunk = files.slice(start, start + self.BATCH_SIZE);
+ var data = makeData(chunk, batch, batches);
+ return Adminx.Ajax.post(url, data).then(function (payload) {
+ var response = payload.data || {};
+ if (!payload.ok || response.success === false) {
+ var error = new Error(response.message || 'Не удалось загрузить файлы');
+ error.uploaded = uploaded.slice();
+ error.batch = batch;
+ throw error;
+ }
+
+ var result = response.data || {};
+ uploaded = uploaded.concat(result.files || []);
+ if (typeof onProgress === 'function') {
+ onProgress(Math.min(start + chunk.length, files.length), files.length, uploaded.length, response);
+ }
+ return run(batch + 1);
+ });
+ };
+
+ return run(0);
+ }
+ };
+
// ------------------------------------------------------------------ //
Adminx.Sidebar = {
KEY: 'adminx-sidebar',
@@ -900,6 +981,7 @@
Adminx.Tabs.init();
Adminx.Drawer.init();
Adminx.Tooltip.init();
+ Adminx.initAjaxForms();
Adminx.initLogout();
Adminx.initCopyTag();
Adminx.initSectionHelp();
diff --git a/adminx/assets/js/editor-tiptap.js b/adminx/assets/js/editor-tiptap.js
index 4fe4ea6..b541414 100644
--- a/adminx/assets/js/editor-tiptap.js
+++ b/adminx/assets/js/editor-tiptap.js
@@ -152,4 +152,4 @@ ${t}
`,h=Array.from({length:o}).map((m,g)=>u&&c[g]&&c[g].text||"");return f+=`| ${h.map((m,g)=>a(m,l[g])).join(" | ")} |
`,f+=`| ${l.map((m,g)=>{let y=Math.max(3,m),b=d[g];return b==="left"?`:${"-".repeat(y)}`:b==="right"?`${"-".repeat(y)}:`:b==="center"?`:${"-".repeat(y)}:`:"-".repeat(y)}).join(" | ")} |
`,(u?s.slice(1):s).forEach(m=>{f+=`| ${Array.from({length:o}).fill(0).map((g,y)=>a(m[y]&&m[y].text||"",l[y])).join(" | ")} |
-`}),f}var Xx=Gx,ba=_.create({name:"table",addOptions(){return{HTMLAttributes:{},resizable:!1,renderWrapper:!1,handleWidth:5,cellMinWidth:25,View:Vx,lastColumnResizable:!0,allowTableNodeSelection:!1}},content:"tableRow+",tableRole:"table",isolating:!0,group:"block",parseHTML(){return[{tag:"table"}]},renderHTML({node:n,HTMLAttributes:e}){let{colgroup:t,tableWidth:r,tableMinWidth:i}=Wx(n,this.options.cellMinWidth),s=e.style;function o(){return s||(r?`width: ${r}`:`min-width: ${i}`)}let l=["table",I(this.options.HTMLAttributes,e,{style:o()}),t,["tbody",0]];return this.options.renderWrapper?["div",{class:"tableWrapper"},l]:l},parseMarkdown:(n,e)=>{let t=[],r=Array.isArray(n.align)?n.align:[];if(n.header){let i=[];n.header.forEach((s,o)=>{var l;let a=ws((l=r[o])!=null?l:s.align),c=a?{align:a}:{};i.push(e.createNode("tableHeader",c,[{type:"paragraph",content:e.parseInline(s.tokens)}]))}),t.push(e.createNode("tableRow",{},i))}return n.rows&&n.rows.forEach(i=>{let s=[];i.forEach((o,l)=>{var a;let c=ws((a=r[l])!=null?a:o.align),u=c?{align:c}:{};s.push(e.createNode("tableCell",u,[{type:"paragraph",content:e.parseInline(o.tokens)}]))}),t.push(e.createNode("tableRow",{},s))}),e.createNode("table",void 0,t)},renderMarkdown:(n,e)=>Xx(n,e),addCommands(){return{insertTable:({rows:n=3,cols:e=3,withHeaderRow:t=!0}={})=>({tr:r,dispatch:i,editor:s})=>{let o=Kx(s.schema,n,e,t);if(i){let l=r.selection.from+1;r.replaceSelectionWith(o).scrollIntoView().setSelection(T.near(r.doc.resolve(l)))}return!0},addColumnBefore:()=>({state:n,dispatch:e})=>_h(n,e),addColumnAfter:()=>({state:n,dispatch:e})=>Vh(n,e),deleteColumn:()=>({state:n,dispatch:e})=>Wh(n,e),addRowBefore:()=>({state:n,dispatch:e})=>Kh(n,e),addRowAfter:()=>({state:n,dispatch:e})=>Jh(n,e),deleteRow:()=>({state:n,dispatch:e})=>qh(n,e),deleteTable:()=>({state:n,dispatch:e})=>Xh(n,e),mergeCells:()=>({state:n,dispatch:e})=>pa(n,e),splitCell:()=>({state:n,dispatch:e})=>ma(n,e),toggleHeaderColumn:()=>({state:n,dispatch:e})=>Sn("column")(n,e),toggleHeaderRow:()=>({state:n,dispatch:e})=>Sn("row")(n,e),toggleHeaderCell:()=>({state:n,dispatch:e})=>Gh(n,e),mergeOrSplit:()=>({state:n,dispatch:e})=>pa(n,e)?!0:ma(n,e),setCellAttribute:(n,e)=>({state:t,dispatch:r})=>Uh(n,e)(t,r),goToNextCell:()=>({state:n,dispatch:e})=>ga(1)(n,e),goToPreviousCell:()=>({state:n,dispatch:e})=>ga(-1)(n,e),fixTables:()=>({state:n,dispatch:e})=>(e&&ha(n),!0),setCellSelection:n=>({tr:e,dispatch:t})=>{if(t){let r=K.create(e.doc,n.anchorCell,n.headCell);e.setSelection(r)}return!0}}},addKeyboardShortcuts(){return{Tab:()=>this.editor.commands.goToNextCell()?!0:this.editor.can().addRowAfter()?this.editor.chain().addRowAfter().goToNextCell().run():!1,"Shift-Tab":()=>this.editor.commands.goToPreviousCell(),Backspace:xs,"Mod-Backspace":xs,Delete:xs,"Mod-Delete":xs}},addProseMirrorPlugins(){return[...this.options.resizable&&this.editor.isEditable?[Qh({handleWidth:this.options.handleWidth,cellMinWidth:this.options.cellMinWidth,defaultCellMinWidth:this.options.cellMinWidth,View:this.options.View,lastColumnResizable:this.options.lastColumnResizable})]:[],ep({allowTableNodeSelection:this.options.allowTableNodeSelection})]},addNodeView(){let n=this.options.resizable&&this.editor.isEditable,e=this.options.View;return n||!e?null:({node:t,view:r,HTMLAttributes:i})=>{let s=I(this.options.HTMLAttributes,i);return new e(t,this.options.cellMinWidth,r,s)}},extendNodeSchema(n){let e={name:n.name,options:n.options,storage:n.storage};return{tableRole:V(E(n,"tableRole",e))}}}),iM=$.create({name:"tableKit",addExtensions(){let n=[];return this.options.table!==!1&&n.push(ba.configure(this.options.table)),this.options.tableCell!==!1&&n.push(Ss.configure(this.options.tableCell)),this.options.tableHeader!==!1&&n.push(vs.configure(this.options.tableHeader)),this.options.tableRow!==!1&&n.push(Cs.configure(this.options.tableRow)),n}});var ip=Cs;var sp=Ss;var op=vs;var Yx=$.create({name:"textAlign",addOptions(){return{types:[],alignments:["left","center","right","justify"],defaultAlignment:null}},addGlobalAttributes(){return[{types:this.options.types,attributes:{textAlign:{default:this.options.defaultAlignment,parseHTML:n=>{let e=n.style.textAlign;return this.options.alignments.includes(e)?e:this.options.defaultAlignment},renderHTML:n=>n.textAlign?{style:`text-align: ${n.textAlign}`}:{}}}}]},addCommands(){return{setTextAlign:n=>({commands:e})=>this.options.alignments.includes(n)?this.options.types.map(t=>e.updateAttributes(t,{textAlign:n})).some(t=>t):!1,unsetTextAlign:()=>({commands:n})=>this.options.types.map(e=>n.resetAttributes(e,"textAlign")).some(e=>e),toggleTextAlign:n=>({editor:e,commands:t})=>this.options.alignments.includes(n)?e.isActive({textAlign:n})?t.unsetTextAlign():t.setTextAlign(n):!1}},addKeyboardShortcuts(){return{"Mod-Shift-l":()=>this.editor.commands.setTextAlign("left"),"Mod-Shift-e":()=>this.editor.commands.setTextAlign("center"),"Mod-Shift-r":()=>this.editor.commands.setTextAlign("right"),"Mod-Shift-j":()=>this.editor.commands.setTextAlign("justify")}}}),lp=Yx;var Qx=Th.extend({addAttributes:function(){return Object.assign({},this.parent?this.parent():{},{class:{default:null},webp:{default:null}})},parseHTML:function(){return[{tag:"picture",getAttrs:function(n){var e=n.querySelector("img[src]"),t=n.querySelector('source[type="image/webp"], source[srcset$=".webp"]');return e?{src:e.getAttribute("src"),alt:e.getAttribute("alt"),title:e.getAttribute("title"),class:e.getAttribute("class"),width:e.getAttribute("width"),height:e.getAttribute("height"),webp:t?t.getAttribute("srcset"):null}:!1}},{tag:'img[src]:not([src^="data:"])'}]},renderHTML:function(n){var e=Object.assign({},n.HTMLAttributes||{}),t=e.webp;delete e.webp;var r=["img",I(this.options.HTMLAttributes,e)];return t?["picture",{},["source",{srcset:t,type:"image/webp"}],r]:r}});(function(n,e){"use strict";var t=n.Adminx||(n.Adminx={});function r(o,l,a){var c=e.createElement(o);return Object.keys(l||{}).forEach(function(u){u==="class"?c.className=l[u]:u==="dataset"?Object.keys(l[u]).forEach(function(d){c.dataset[d]=l[u][d]}):c.setAttribute(u,l[u])}),a!=null&&(c.innerHTML=a),c}function i(o){return' '}function s(o,l,a){t.RichEditor.modalInput({title:o,value:l||""},a)}t.RichEditor={instances:[],init:function(o){o=o||e;var l=o.querySelectorAll("textarea[data-rich-editor]:not([data-rich-editor-ready])");Array.prototype.forEach.call(l,this.create.bind(this))},extensions:function(){return[Mh.configure({link:!1}),jf.configure({openOnClick:!1,autolink:!0,protocols:["http","https","mailto","tel"],HTMLAttributes:{rel:"noopener noreferrer nofollow"}}),Qx.configure({allowBase64:!1}),ba.configure({resizable:!0}),ip,op,sp,lp.configure({types:["heading","paragraph"]})]},create:function(o){var l=o.getAttribute("data-rich-preset")||"full",a=r("div",{class:"rich-editor rich-editor-"+l}),c=r("div",{class:"rich-editor-toolbar",role:"toolbar"}),u=r("div",{class:"rich-editor-surface"}),d=r("div",{class:"rich-editor-content"}),f=parseInt(o.getAttribute("data-editor-height"),10)||0;f>0&&(f=Math.max(140,Math.min(900,f)),a.classList.add("rich-editor-fixed-height"),a.style.setProperty("--rich-editor-height",f+"px")),o.style.display="none",o.parentNode.insertBefore(a,o),a.appendChild(c),a.appendChild(u),u.appendChild(d),a.appendChild(o);var h=new zd({element:d,extensions:this.extensions(),content:o.value||"",editorProps:{attributes:{class:"rich-editor-prose",spellcheck:o.getAttribute("spellcheck")||"true"},handleDoubleClickOn:function(p,m,g,y){return!g||g.type.name!=="image"?!1:(h.chain().setNodeSelection(y).run(),t.RichEditor.openImageEditor(h,g.attrs,y),!0)}},onUpdate:function(){o.value=h.getHTML(),o.setAttribute("data-rich-editor-dirty","1"),o.dispatchEvent(new Event("input",{bubbles:!0}))},onSelectionUpdate:function(){t.RichEditor.syncToolbar(a,h)},onTransaction:function(){t.RichEditor.syncToolbar(a,h)}});this.buildToolbar(c,h),o.setAttribute("data-rich-editor-ready","1"),o._adminxTiptap=h,this.instances.push(h),this.bindForm(o,h),this.syncToolbar(a,h)},button:function(o,l,a,c){var u=r("button",{class:"btn btn-ghost btn-icon btn-sm rich-editor-btn",type:"button","data-rich-command":o,"data-tooltip":l,"aria-label":l},i(a));return u.addEventListener("click",function(){c()}),u},buildToolbar:function(o,l){var a,c=this;function u(){a=r("div",{class:"rich-editor-group"}),o.appendChild(a)}function d(h,p,m,g){a.appendChild(c.button(h,p,m,g))}function f(){var h=r("select",{class:"select rich-editor-block-select","data-rich-block-select":"","aria-label":"\u0424\u043E\u0440\u043C\u0430\u0442 \u0431\u043B\u043E\u043A\u0430","data-tooltip":"\u0424\u043E\u0440\u043C\u0430\u0442 \u0431\u043B\u043E\u043A\u0430"});[["paragraph","\u0410\u0431\u0437\u0430\u0446"],["heading-1","\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A H1"],["heading-2","\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A H2"],["heading-3","\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A H3"],["heading-4","\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A H4"],["heading-5","\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A H5"],["heading-6","\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A H6"]].forEach(function(p){var m=r("option",{value:p[0]});m.textContent=p[1],h.appendChild(m)}),h.addEventListener("change",function(){if(h.value==="paragraph"){l.chain().focus().setParagraph().run();return}l.chain().focus().setHeading({level:parseInt(h.value.replace("heading-",""),10)}).run()}),a.appendChild(h)}u(),d("undo","\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C","arrow-back-up",function(){l.chain().focus().undo().run()}),d("redo","\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C","arrow-forward-up",function(){l.chain().focus().redo().run()}),u(),d("bold","\u0416\u0438\u0440\u043D\u044B\u0439","bold",function(){l.chain().focus().toggleBold().run()}),d("italic","\u041A\u0443\u0440\u0441\u0438\u0432","italic",function(){l.chain().focus().toggleItalic().run()}),d("strike","\u0417\u0430\u0447\u0435\u0440\u043A\u043D\u0443\u0442\u044C","strikethrough",function(){l.chain().focus().toggleStrike().run()}),d("code","\u0411\u043B\u043E\u043A \u043A\u043E\u0434\u0430","code",function(){l.chain().focus().toggleCodeBlock().run()}),u(),f(),d("bulletList","\u041C\u0430\u0440\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0441\u043F\u0438\u0441\u043E\u043A","list",function(){l.chain().focus().toggleBulletList().run()}),d("orderedList","\u041D\u0443\u043C\u0435\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0441\u043F\u0438\u0441\u043E\u043A","list-numbers",function(){l.chain().focus().toggleOrderedList().run()}),d("blockquote","\u0426\u0438\u0442\u0430\u0442\u0430","blockquote",function(){l.chain().focus().toggleBlockquote().run()}),u(),d("alignLeft","\u041F\u043E \u043B\u0435\u0432\u043E\u043C\u0443 \u043A\u0440\u0430\u044E","align-left",function(){l.chain().focus().setTextAlign("left").run()}),d("alignCenter","\u041F\u043E \u0446\u0435\u043D\u0442\u0440\u0443","align-center",function(){l.chain().focus().setTextAlign("center").run()}),d("alignRight","\u041F\u043E \u043F\u0440\u0430\u0432\u043E\u043C\u0443 \u043A\u0440\u0430\u044E","align-right",function(){l.chain().focus().setTextAlign("right").run()}),u(),d("link","\u0421\u0441\u044B\u043B\u043A\u0430","link",function(){c.openLink(l)}),d("image","\u0418\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435","photo-plus",function(){c.openMediaPicker(l)}),d("table","\u0422\u0430\u0431\u043B\u0438\u0446\u0430","table-plus",function(){l.chain().focus().insertTable({rows:3,cols:3,withHeaderRow:!0}).run()}),d("showBlocks","\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0431\u043B\u043E\u043A\u0438","box-model-2",function(){var h=o.closest(".rich-editor");h&&(h.classList.toggle("rich-editor-show-blocks"),c.syncToolbar(h,l))}),u(),n.CodeMirror&&d("source","\u0418\u0441\u0445\u043E\u0434\u043D\u0438\u043A","code-dots",function(){c.openSource(l)}),d("clear","\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0444\u043E\u0440\u043C\u0430\u0442","eraser",function(){l.chain().focus().unsetAllMarks().clearNodes().run()})},syncToolbar:function(o,l){var a={bold:l.isActive("bold"),italic:l.isActive("italic"),strike:l.isActive("strike"),code:l.isActive("codeBlock"),bulletList:l.isActive("bulletList"),orderedList:l.isActive("orderedList"),blockquote:l.isActive("blockquote"),alignLeft:l.isActive({textAlign:"left"}),alignCenter:l.isActive({textAlign:"center"}),alignRight:l.isActive({textAlign:"right"}),link:l.isActive("link"),showBlocks:o.classList.contains("rich-editor-show-blocks"),source:o.classList.contains("rich-editor-source-open")};Object.keys(a).forEach(function(f){var h=o.querySelector('[data-rich-command="'+f+'"]');h&&h.setAttribute("aria-pressed",a[f]?"true":"false")});var c=o.querySelector("[data-rich-block-select]");if(c){for(var u="paragraph",d=1;d<=6;d++)if(l.isActive("heading",{level:d})){u="heading-"+d;break}c.value=u}},bindForm:function(o,l){var a=o.closest("form");a&&a.addEventListener("submit",function(){var c=o.closest(".rich-editor");if(c&&c.classList.contains("rich-editor-source-open")&&c._richSourceEditor){c._richSourceEditor.save(),o.value=c._richSourceEditor.getValue();return}o.getAttribute("data-rich-editor-dirty")==="1"&&(o.value=l.getHTML())})},openSource:function(o){var l=o.view.dom.closest(".rich-editor");if(!(!l||!n.CodeMirror)){var a=l.querySelector(".rich-editor-surface"),c=l.querySelector("textarea[data-rich-editor]"),u=l.querySelector("[data-rich-inline-source]"),d=!l.classList.contains("rich-editor-source-open");if(!d){var f=l._richSourceEditor?l._richSourceEditor.getValue():c?c.value:"";f!==o.getHTML()&&o.commands.setContent(f||"
"),c&&(c.value=o.getHTML()),l.classList.remove("rich-editor-source-open"),this.setSourceControls(l,!1),this.syncToolbar(l,o),o.commands.focus();return}if(!u){u=r("div",{class:"rich-editor-inline-source","data-rich-inline-source":""});var h=r("textarea",{"data-rich-source-textarea":""});u.appendChild(h),a.parentNode.insertBefore(u,a.nextSibling),l._richSourceEditor=n.CodeMirror.fromTextArea(h,{mode:"htmlmixed",theme:e.documentElement.getAttribute("data-theme")==="dark"?"material-ocean":"default",lineNumbers:!0,lineWrapping:!0,matchBrackets:!0,autoCloseTags:!0,styleActiveLine:!0,indentUnit:2}),l._richSourceEditor.on("change",function(m){!c||l._richSourceSyncing||(c.value=m.getValue(),c.setAttribute("data-rich-editor-dirty","1"),c.dispatchEvent(new Event("input",{bubbles:!0})))})}var p=Math.max(260,Math.round(a.getBoundingClientRect().height||0));l._richSourceSyncing=!0,l._richSourceEditor.setValue(o.getHTML()),l._richSourceSyncing=!1,l._richSourceEditor.setSize("100%",p),l.classList.add("rich-editor-source-open"),this.setSourceControls(l,!0),this.syncToolbar(l,o),setTimeout(function(){l._richSourceEditor.refresh(),l._richSourceEditor.focus()},0)}},setSourceControls:function(o,l){o.querySelectorAll(".rich-editor-toolbar [data-rich-command], .rich-editor-toolbar [data-rich-block-select]").forEach(function(a){a.getAttribute("data-rich-command")!=="source"&&(a.disabled=!!l)})},openLink:function(o){var l=this,a=o.getAttributes("link")||{},c=r("div",{class:"overlay rich-editor-link-overlay"}),u=r("div",{class:"modal rich-editor-link-modal",role:"dialog","aria-modal":"true","aria-labelledby":"richEditorLinkTitle"});u.innerHTML='',c.appendChild(u),e.body.appendChild(c),requestAnimationFrame(function(){c.classList.add("show")});var d=c.querySelector("[data-link-url]"),f=c.querySelector("[data-link-class]"),h=c.querySelector("[data-link-selection]"),p=function(){c.classList.remove("show"),setTimeout(function(){c.remove(),e.removeEventListener("keydown",y)},180)},m=function(){var b=String(d.value||"").trim(),k=String(f.value||"").trim().replace(/\s+/g," ");if(!b){o.chain().focus().unsetLink().run(),p();return}o.chain().focus().extendMarkRange("link").setLink({href:b,class:k||null}).run(),p()},g=function(b,k){d.value=b||"",h.textContent=k||"\u0410\u0434\u0440\u0435\u0441 \u0432\u044B\u0431\u0440\u0430\u043D",d.dispatchEvent(new Event("input",{bubbles:!0}))},y=function(b){if(b.key==="Escape"){if(e.querySelector(".rich-editor-document-picker-overlay.show, .media-picker-overlay.show"))return;p()}b.key==="Enter"&&b.target!==f&&(b.preventDefault(),m())};c.addEventListener("click",function(b){if(b.target===c||b.target.closest("[data-link-cancel]")){p();return}if(b.target.closest("[data-link-apply]")){m();return}if(b.target.closest("[data-link-unset]")){o.chain().focus().unsetLink().run(),p();return}if(b.target.closest("[data-link-document]")){l.openDocumentLinkPicker(function(k){var w=String(k.alias||"").trim(),C=w?/^(?:[a-z]+:|\/|#)/i.test(w)?w:"/"+w:"/index.php?id="+k.id;g(C,"\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442 #"+k.id+" \xB7 "+(k.title||"\u0411\u0435\u0437 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u044F"))});return}b.target.closest("[data-link-media]")&&t.MediaPicker&&t.MediaPicker.open({type:"all",title:"\u0424\u0430\u0439\u043B \u0434\u043B\u044F \u0441\u0441\u044B\u043B\u043A\u0438",description:"\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u0438\u043B\u0438 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0438\u0437 \u043C\u0435\u0434\u0438\u0430\u0442\u0435\u043A\u0438.",onPick:function(k){k&&k.url&&g(k.url,"\u041C\u0435\u0434\u0438\u0430 \xB7 "+(k.name||k.url))},onUrl:function(k){k&&g(k,"\u0412\u043D\u0435\u0448\u043D\u0438\u0439 \u0430\u0434\u0440\u0435\u0441 \u043C\u0435\u0434\u0438\u0430")}})}),e.addEventListener("keydown",y),d.focus(),d.select()},openDocumentLinkPicker:function(o){var l=this,a=r("div",{class:"overlay rich-editor-document-picker-overlay"}),c=r("div",{class:"modal picker-modal rich-editor-document-picker",role:"dialog","aria-modal":"true","aria-labelledby":"richEditorDocumentPickerTitle"});c.innerHTML='
\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430...
',a.appendChild(c),e.body.appendChild(a),requestAnimationFrame(function(){a.classList.add("show")});var u=a.querySelector("[data-document-link-search]"),d=a.querySelector("[data-document-link-list]"),f=a.querySelector("[data-document-link-status]"),h=a.querySelector("[data-document-link-count]"),p=[],m=null,g=0,y=function(){a.classList.remove("show"),setTimeout(function(){a.remove(),e.removeEventListener("keydown",b)},160)},b=function(w){w.key==="Escape"&&(w.stopImmediatePropagation(),y())},k=function(){var w=++g,C=new URLSearchParams;C.set("q",u.value.trim()),C.set("limit",40),f.hidden=!1,f.textContent="\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430...",fetch((n.ADMINX_BASE||(t.base?t.base():""))+"/documents/picker?"+C.toString(),{headers:{Accept:"application/json"},credentials:"same-origin"}).then(function(O){return O.json()}).then(function(O){w===g&&(p=(O.data||{}).items||[],d.innerHTML=p.map(function(M,D){return'#'+l.escape(M.id)+" "+l.escape(M.title||"\u0411\u0435\u0437 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u044F")+" /"+l.escape(String(M.alias||"").replace(/^\/+/,"")||"\u0431\u0435\u0437 alias")+" "+l.escape(M.rubric_title||"\u0420\u0443\u0431\u0440\u0438\u043A\u0430 #"+M.rubric_id)+" "}).join(""),f.hidden=p.length>0,f.textContent=u.value.trim()?"\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E":"\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u044B",h.textContent=p.length?"\u041F\u043E\u043A\u0430\u0437\u0430\u043D\u043E: "+p.length:"")}).catch(function(){p=[],d.innerHTML="",f.hidden=!1,f.textContent="\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B"})};a.addEventListener("click",function(w){if(w.target===a||w.target.closest("[data-document-link-close]")){y();return}var C=w.target.closest("[data-document-link-index]");if(C){var O=p[parseInt(C.getAttribute("data-document-link-index"),10)];O&&(o(O),y())}}),u.addEventListener("input",function(){clearTimeout(m),m=setTimeout(k,220)}),e.addEventListener("keydown",b),k(),u.focus()},openMediaPicker:function(o){this.openImageEditor(o,{},null)},openImageEditor:function(o,l,a){var c=this;l=l||{};var u=typeof a=="number",d=r("div",{class:"overlay rich-editor-image-overlay"}),f=r("div",{class:"modal rich-editor-image-modal",role:"dialog","aria-modal":"true","aria-labelledby":"richEditorImageTitle"});f.innerHTML='\u0418\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u043E
",d.appendChild(f),e.body.appendChild(d),requestAnimationFrame(function(){d.classList.add("show")});var h=d.querySelector("[data-image-src]"),p=d.querySelector("[data-image-webp]"),m=d.querySelector("[data-image-picture]"),g=d.querySelector("[data-image-webp-field]"),y=d.querySelector("[data-image-alt]"),b=d.querySelector("[data-image-class]"),k=d.querySelector("[data-image-width]"),w=d.querySelector("[data-image-height]"),C=d.querySelector("[data-image-scale]"),O=d.querySelector("[data-image-scale-value]"),M=d.querySelector("[data-image-preview-img]"),D=d.querySelector("[data-image-empty]"),B=d.querySelector("[data-image-preview-size]"),P={naturalWidth:parseInt(l.width,10)||0,naturalHeight:parseInt(l.height,10)||0,ratio:l.width&&l.height?parseInt(l.width,10)/parseInt(l.height,10):0,loadingSrc:""},Se=function(){var L=parseInt(k.value,10)||0,F=P.naturalWidth&&L?Math.round(L/P.naturalWidth*100):100;F=Math.max(10,Math.min(200,F)),C.value=F,O.textContent=F+"%",B.textContent=L&&w.value?L+" x "+w.value+" px":""},Te=function(L,F,Ct){L=Math.max(1,Math.round(Number(L)||0)),F=Math.max(1,Math.round(Number(F)||0)),!(!L||!F)&&(Ct?(P.naturalWidth=L,P.naturalHeight=F,P.ratio=L/F):P.ratio||(P.ratio=L/F),k.value=L,w.value=F,Se())},Q=function(L,F){if(L=String(L||"").trim(),P.loadingSrc=L,!L){M.hidden=!0,D.hidden=!1,B.textContent="";return}var Ct=new n.Image;Ct.onload=function(){if(P.loadingSrc===L){M.src=L,M.hidden=!1,D.hidden=!0;var xa=F&&F.width?F.width:Ct.naturalWidth,wa=F&&F.height?F.height:Ct.naturalHeight;!k.value||!w.value||L!==l.src?Te(xa,wa,!0):(P.naturalWidth=xa||parseInt(k.value,10),P.naturalHeight=wa||parseInt(w.value,10),P.ratio=P.naturalWidth/P.naturalHeight,Se())}},Ct.onerror=function(){P.loadingSrc===L&&(M.hidden=!0,D.hidden=!1,D.querySelector("span").textContent="\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435")},Ct.src=L},Z=function(L){t.MediaPicker&&t.MediaPicker.open({type:"image",title:L==="webp"?"\u0412\u044B\u0431\u0440\u0430\u0442\u044C WebP":"\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435",description:L==="webp"?"\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0444\u0430\u0439\u043B WebP \u0434\u043B\u044F \u0442\u0435\u0433\u0430 source.":"\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0435 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u0438\u0437 \u043C\u0435\u0434\u0438\u0430\u0442\u0435\u043A\u0438.",onPick:function(F){if(!(!F||!F.url)){if(L==="webp"){p.value=F.url,m.checked=!0,g.hidden=!1;return}h.value=F.url,!y.value&&F.name&&(y.value=String(F.name).replace(/\.[^.]+$/,"").replace(/[-_]+/g," ")),k.value="",w.value="",Q(F.url,F)}},onUrl:function(F){F&&(L==="webp"?(p.value=F,m.checked=!0,g.hidden=!1):(h.value=F,k.value="",w.value="",Q(F)))}})},J=function(){d.classList.remove("show"),setTimeout(function(){d.remove(),e.removeEventListener("keydown",ka)},180)},dt=function(){var L=String(h.value||"").trim();if(!L){h.focus();return}var F={src:L,webp:m.checked&&String(p.value||"").trim()||null,alt:String(y.value||"").trim()||null,class:String(b.value||"").trim().replace(/\s+/g," ")||null,width:parseInt(k.value,10)||null,height:parseInt(w.value,10)||null};u?o.chain().focus().setNodeSelection(a).updateAttributes("image",F).run():o.chain().focus().setImage(F).run(),J()},ka=function(L){if(L.key==="Escape"){if(e.querySelector(".media-picker-overlay.show"))return;J()}};m.addEventListener("change",function(){g.hidden=!m.checked,m.checked&&p.focus()}),h.addEventListener("change",function(){k.value="",w.value="",Q(h.value)}),k.addEventListener("input",function(){var L=parseInt(k.value,10)||0;L&&P.ratio&&(w.value=Math.max(1,Math.round(L/P.ratio))),Se()}),w.addEventListener("input",function(){var L=parseInt(w.value,10)||0;L&&P.ratio&&(k.value=Math.max(1,Math.round(L*P.ratio))),Se()}),C.addEventListener("input",function(){var L=parseInt(C.value,10)||100;O.textContent=L+"%",P.naturalWidth&&P.naturalHeight&&(k.value=Math.max(1,Math.round(P.naturalWidth*L/100)),w.value=Math.max(1,Math.round(P.naturalHeight*L/100)),B.textContent=k.value+" x "+w.value+" px")}),d.addEventListener("click",function(L){if(L.target===d||L.target.closest("[data-image-cancel]")){J();return}var F=L.target.closest("[data-image-pick]");if(F){Z(F.getAttribute("data-image-pick"));return}if(L.target.closest("[data-image-original]")&&P.naturalWidth&&P.naturalHeight){Te(P.naturalWidth,P.naturalHeight,!1);return}if(L.target.closest("[data-image-apply]")){dt();return}L.target.closest("[data-image-remove]")&&u&&(o.chain().focus().setNodeSelection(a).deleteSelection().run(),J())}),e.addEventListener("keydown",ka),l.src?Q(l.src):h.focus()},modalInput:function(o,l){var a=r("div",{class:"overlay"}),c=r("div",{class:"modal rich-editor-prompt",role:"dialog","aria-modal":"true"});c.innerHTML='
',a.appendChild(c),e.body.appendChild(a),requestAnimationFrame(function(){a.classList.add("show")});var u=a.querySelector("[data-input]"),d=function(){a.classList.remove("show"),setTimeout(function(){a.remove(),e.removeEventListener("keydown",h)},180)},f=function(){var p=u.value||"";d(),l(p)},h=function(p){p.key==="Escape"&&d(),p.key==="Enter"&&f()};a.addEventListener("click",function(p){(p.target===a||p.target.closest("[data-cancel]"))&&d(),p.target.closest("[data-ok]")&&f()}),e.addEventListener("keydown",h),u.focus(),u.select()},escape:function(o){return String(o==null?"":o).replace(/[&<>"]/g,function(l){return{"&":"&","<":"<",">":">",'"':"""}[l]})}},e.addEventListener("adminx:content-ready",function(o){t.RichEditor.init(o.detail&&o.detail.root?o.detail.root:e)}),e.readyState==="loading"?e.addEventListener("DOMContentLoaded",function(){t.RichEditor.init()}):t.RichEditor.init()})(window,document);})();
+`}),f}var Xx=Gx,ba=_.create({name:"table",addOptions(){return{HTMLAttributes:{},resizable:!1,renderWrapper:!1,handleWidth:5,cellMinWidth:25,View:Vx,lastColumnResizable:!0,allowTableNodeSelection:!1}},content:"tableRow+",tableRole:"table",isolating:!0,group:"block",parseHTML(){return[{tag:"table"}]},renderHTML({node:n,HTMLAttributes:e}){let{colgroup:t,tableWidth:r,tableMinWidth:i}=Wx(n,this.options.cellMinWidth),s=e.style;function o(){return s||(r?`width: ${r}`:`min-width: ${i}`)}let l=["table",I(this.options.HTMLAttributes,e,{style:o()}),t,["tbody",0]];return this.options.renderWrapper?["div",{class:"tableWrapper"},l]:l},parseMarkdown:(n,e)=>{let t=[],r=Array.isArray(n.align)?n.align:[];if(n.header){let i=[];n.header.forEach((s,o)=>{var l;let a=ws((l=r[o])!=null?l:s.align),c=a?{align:a}:{};i.push(e.createNode("tableHeader",c,[{type:"paragraph",content:e.parseInline(s.tokens)}]))}),t.push(e.createNode("tableRow",{},i))}return n.rows&&n.rows.forEach(i=>{let s=[];i.forEach((o,l)=>{var a;let c=ws((a=r[l])!=null?a:o.align),u=c?{align:c}:{};s.push(e.createNode("tableCell",u,[{type:"paragraph",content:e.parseInline(o.tokens)}]))}),t.push(e.createNode("tableRow",{},s))}),e.createNode("table",void 0,t)},renderMarkdown:(n,e)=>Xx(n,e),addCommands(){return{insertTable:({rows:n=3,cols:e=3,withHeaderRow:t=!0}={})=>({tr:r,dispatch:i,editor:s})=>{let o=Kx(s.schema,n,e,t);if(i){let l=r.selection.from+1;r.replaceSelectionWith(o).scrollIntoView().setSelection(T.near(r.doc.resolve(l)))}return!0},addColumnBefore:()=>({state:n,dispatch:e})=>_h(n,e),addColumnAfter:()=>({state:n,dispatch:e})=>Vh(n,e),deleteColumn:()=>({state:n,dispatch:e})=>Wh(n,e),addRowBefore:()=>({state:n,dispatch:e})=>Kh(n,e),addRowAfter:()=>({state:n,dispatch:e})=>Jh(n,e),deleteRow:()=>({state:n,dispatch:e})=>qh(n,e),deleteTable:()=>({state:n,dispatch:e})=>Xh(n,e),mergeCells:()=>({state:n,dispatch:e})=>pa(n,e),splitCell:()=>({state:n,dispatch:e})=>ma(n,e),toggleHeaderColumn:()=>({state:n,dispatch:e})=>Sn("column")(n,e),toggleHeaderRow:()=>({state:n,dispatch:e})=>Sn("row")(n,e),toggleHeaderCell:()=>({state:n,dispatch:e})=>Gh(n,e),mergeOrSplit:()=>({state:n,dispatch:e})=>pa(n,e)?!0:ma(n,e),setCellAttribute:(n,e)=>({state:t,dispatch:r})=>Uh(n,e)(t,r),goToNextCell:()=>({state:n,dispatch:e})=>ga(1)(n,e),goToPreviousCell:()=>({state:n,dispatch:e})=>ga(-1)(n,e),fixTables:()=>({state:n,dispatch:e})=>(e&&ha(n),!0),setCellSelection:n=>({tr:e,dispatch:t})=>{if(t){let r=K.create(e.doc,n.anchorCell,n.headCell);e.setSelection(r)}return!0}}},addKeyboardShortcuts(){return{Tab:()=>this.editor.commands.goToNextCell()?!0:this.editor.can().addRowAfter()?this.editor.chain().addRowAfter().goToNextCell().run():!1,"Shift-Tab":()=>this.editor.commands.goToPreviousCell(),Backspace:xs,"Mod-Backspace":xs,Delete:xs,"Mod-Delete":xs}},addProseMirrorPlugins(){return[...this.options.resizable&&this.editor.isEditable?[Qh({handleWidth:this.options.handleWidth,cellMinWidth:this.options.cellMinWidth,defaultCellMinWidth:this.options.cellMinWidth,View:this.options.View,lastColumnResizable:this.options.lastColumnResizable})]:[],ep({allowTableNodeSelection:this.options.allowTableNodeSelection})]},addNodeView(){let n=this.options.resizable&&this.editor.isEditable,e=this.options.View;return n||!e?null:({node:t,view:r,HTMLAttributes:i})=>{let s=I(this.options.HTMLAttributes,i);return new e(t,this.options.cellMinWidth,r,s)}},extendNodeSchema(n){let e={name:n.name,options:n.options,storage:n.storage};return{tableRole:V(E(n,"tableRole",e))}}}),iM=$.create({name:"tableKit",addExtensions(){let n=[];return this.options.table!==!1&&n.push(ba.configure(this.options.table)),this.options.tableCell!==!1&&n.push(Ss.configure(this.options.tableCell)),this.options.tableHeader!==!1&&n.push(vs.configure(this.options.tableHeader)),this.options.tableRow!==!1&&n.push(Cs.configure(this.options.tableRow)),n}});var ip=Cs;var sp=Ss;var op=vs;var Yx=$.create({name:"textAlign",addOptions(){return{types:[],alignments:["left","center","right","justify"],defaultAlignment:null}},addGlobalAttributes(){return[{types:this.options.types,attributes:{textAlign:{default:this.options.defaultAlignment,parseHTML:n=>{let e=n.style.textAlign;return this.options.alignments.includes(e)?e:this.options.defaultAlignment},renderHTML:n=>n.textAlign?{style:`text-align: ${n.textAlign}`}:{}}}}]},addCommands(){return{setTextAlign:n=>({commands:e})=>this.options.alignments.includes(n)?this.options.types.map(t=>e.updateAttributes(t,{textAlign:n})).some(t=>t):!1,unsetTextAlign:()=>({commands:n})=>this.options.types.map(e=>n.resetAttributes(e,"textAlign")).some(e=>e),toggleTextAlign:n=>({editor:e,commands:t})=>this.options.alignments.includes(n)?e.isActive({textAlign:n})?t.unsetTextAlign():t.setTextAlign(n):!1}},addKeyboardShortcuts(){return{"Mod-Shift-l":()=>this.editor.commands.setTextAlign("left"),"Mod-Shift-e":()=>this.editor.commands.setTextAlign("center"),"Mod-Shift-r":()=>this.editor.commands.setTextAlign("right"),"Mod-Shift-j":()=>this.editor.commands.setTextAlign("justify")}}}),lp=Yx;var Qx=Th.extend({addAttributes:function(){return Object.assign({},this.parent?this.parent():{},{class:{default:null},webp:{default:null}})},parseHTML:function(){return[{tag:"picture",getAttrs:function(n){var e=n.querySelector("img[src]"),t=n.querySelector('source[type="image/webp"], source[srcset$=".webp"]');return e?{src:e.getAttribute("src"),alt:e.getAttribute("alt"),title:e.getAttribute("title"),class:e.getAttribute("class"),width:e.getAttribute("width"),height:e.getAttribute("height"),webp:t?t.getAttribute("srcset"):null}:!1}},{tag:'img[src]:not([src^="data:"])'}]},renderHTML:function(n){var e=Object.assign({},n.HTMLAttributes||{}),t=e.webp;delete e.webp;var r=["img",I(this.options.HTMLAttributes,e)];return t?["picture",{},["source",{srcset:t,type:"image/webp"}],r]:r}});(function(n,e){"use strict";var t=n.Adminx||(n.Adminx={});function r(o,l,a){var c=e.createElement(o);return Object.keys(l||{}).forEach(function(u){u==="class"?c.className=l[u]:u==="dataset"?Object.keys(l[u]).forEach(function(d){c.dataset[d]=l[u][d]}):c.setAttribute(u,l[u])}),a!=null&&(c.innerHTML=a),c}function i(o){return' '}function s(o,l,a){t.RichEditor.modalInput({title:o,value:l||""},a)}t.RichEditor={instances:[],init:function(o){o=o||e;var l=o.querySelectorAll("textarea[data-rich-editor]:not([data-rich-editor-ready])");Array.prototype.forEach.call(l,this.create.bind(this))},extensions:function(){return[Mh.configure({link:!1}),jf.configure({openOnClick:!1,autolink:!0,protocols:["http","https","mailto","tel"],HTMLAttributes:{rel:"noopener noreferrer nofollow"}}),Qx.configure({allowBase64:!1}),ba.configure({resizable:!0}),ip,op,sp,lp.configure({types:["heading","paragraph"]})]},create:function(o){var l=o.getAttribute("data-rich-preset")||"full",a=r("div",{class:"rich-editor rich-editor-"+l}),c=r("div",{class:"rich-editor-toolbar",role:"toolbar"}),u=r("div",{class:"rich-editor-surface"}),d=r("div",{class:"rich-editor-content"}),f=parseInt(o.getAttribute("data-editor-height"),10)||0;f>0&&(f=Math.max(140,Math.min(900,f)),a.classList.add("rich-editor-fixed-height"),a.style.setProperty("--rich-editor-height",f+"px")),o.style.display="none",o.parentNode.insertBefore(a,o),a.appendChild(c),a.appendChild(u),u.appendChild(d),a.appendChild(o);var h=new zd({element:d,extensions:this.extensions(),content:o.value||"",editorProps:{attributes:{class:"rich-editor-prose",spellcheck:o.getAttribute("spellcheck")||"true"},handleDoubleClickOn:function(p,m,g,y){return!g||g.type.name!=="image"?!1:(h.chain().setNodeSelection(y).run(),t.RichEditor.openImageEditor(h,g.attrs,y),!0)}},onUpdate:function(){o.value=h.getHTML(),o.setAttribute("data-rich-editor-dirty","1"),o.dispatchEvent(new Event("input",{bubbles:!0}))},onSelectionUpdate:function(){t.RichEditor.syncToolbar(a,h)},onTransaction:function(){t.RichEditor.syncToolbar(a,h)}});this.buildToolbar(c,h),o.setAttribute("data-rich-editor-ready","1"),o._adminxTiptap=h,this.instances.push(h),this.bindForm(o,h),this.syncToolbar(a,h)},button:function(o,l,a,c){var u=r("button",{class:"btn btn-ghost btn-icon btn-sm rich-editor-btn",type:"button","data-rich-command":o,"data-tooltip":l,"aria-label":l},i(a));return u.addEventListener("click",function(){c()}),u},buildToolbar:function(o,l){var a,c=this;function u(){a=r("div",{class:"rich-editor-group"}),o.appendChild(a)}function d(h,p,m,g){a.appendChild(c.button(h,p,m,g))}function f(){var h=r("select",{class:"select rich-editor-block-select","data-rich-block-select":"","aria-label":"\u0424\u043E\u0440\u043C\u0430\u0442 \u0431\u043B\u043E\u043A\u0430","data-tooltip":"\u0424\u043E\u0440\u043C\u0430\u0442 \u0431\u043B\u043E\u043A\u0430"});[["paragraph","\u0410\u0431\u0437\u0430\u0446"],["heading-1","\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A H1"],["heading-2","\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A H2"],["heading-3","\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A H3"],["heading-4","\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A H4"],["heading-5","\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A H5"],["heading-6","\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A H6"]].forEach(function(p){var m=r("option",{value:p[0]});m.textContent=p[1],h.appendChild(m)}),h.addEventListener("change",function(){if(h.value==="paragraph"){l.chain().focus().setParagraph().run();return}l.chain().focus().setHeading({level:parseInt(h.value.replace("heading-",""),10)}).run()}),a.appendChild(h)}u(),d("undo","\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C","arrow-back-up",function(){l.chain().focus().undo().run()}),d("redo","\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C","arrow-forward-up",function(){l.chain().focus().redo().run()}),u(),d("bold","\u0416\u0438\u0440\u043D\u044B\u0439","bold",function(){l.chain().focus().toggleBold().run()}),d("italic","\u041A\u0443\u0440\u0441\u0438\u0432","italic",function(){l.chain().focus().toggleItalic().run()}),d("strike","\u0417\u0430\u0447\u0435\u0440\u043A\u043D\u0443\u0442\u044C","strikethrough",function(){l.chain().focus().toggleStrike().run()}),d("code","\u0411\u043B\u043E\u043A \u043A\u043E\u0434\u0430","code",function(){l.chain().focus().toggleCodeBlock().run()}),u(),f(),d("bulletList","\u041C\u0430\u0440\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0441\u043F\u0438\u0441\u043E\u043A","list",function(){l.chain().focus().toggleBulletList().run()}),d("orderedList","\u041D\u0443\u043C\u0435\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0441\u043F\u0438\u0441\u043E\u043A","list-numbers",function(){l.chain().focus().toggleOrderedList().run()}),d("blockquote","\u0426\u0438\u0442\u0430\u0442\u0430","blockquote",function(){l.chain().focus().toggleBlockquote().run()}),u(),d("alignLeft","\u041F\u043E \u043B\u0435\u0432\u043E\u043C\u0443 \u043A\u0440\u0430\u044E","align-left",function(){l.chain().focus().setTextAlign("left").run()}),d("alignCenter","\u041F\u043E \u0446\u0435\u043D\u0442\u0440\u0443","align-center",function(){l.chain().focus().setTextAlign("center").run()}),d("alignRight","\u041F\u043E \u043F\u0440\u0430\u0432\u043E\u043C\u0443 \u043A\u0440\u0430\u044E","align-right",function(){l.chain().focus().setTextAlign("right").run()}),u(),d("link","\u0421\u0441\u044B\u043B\u043A\u0430","link",function(){c.openLink(l)}),d("image","\u0418\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435","photo-plus",function(){c.openMediaPicker(l)}),d("table","\u0422\u0430\u0431\u043B\u0438\u0446\u0430","table-plus",function(){l.chain().focus().insertTable({rows:3,cols:3,withHeaderRow:!0}).run()}),d("showBlocks","\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0431\u043B\u043E\u043A\u0438","box-model-2",function(){var h=o.closest(".rich-editor");h&&(h.classList.toggle("rich-editor-show-blocks"),c.syncToolbar(h,l))}),u(),n.CodeMirror&&d("source","\u0418\u0441\u0445\u043E\u0434\u043D\u0438\u043A","code-dots",function(){c.openSource(l)}),d("clear","\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0444\u043E\u0440\u043C\u0430\u0442","eraser",function(){l.chain().focus().unsetAllMarks().clearNodes().run()})},syncToolbar:function(o,l){var a={bold:l.isActive("bold"),italic:l.isActive("italic"),strike:l.isActive("strike"),code:l.isActive("codeBlock"),bulletList:l.isActive("bulletList"),orderedList:l.isActive("orderedList"),blockquote:l.isActive("blockquote"),alignLeft:l.isActive({textAlign:"left"}),alignCenter:l.isActive({textAlign:"center"}),alignRight:l.isActive({textAlign:"right"}),link:l.isActive("link"),showBlocks:o.classList.contains("rich-editor-show-blocks"),source:o.classList.contains("rich-editor-source-open")};Object.keys(a).forEach(function(f){var h=o.querySelector('[data-rich-command="'+f+'"]');h&&h.setAttribute("aria-pressed",a[f]?"true":"false")});var c=o.querySelector("[data-rich-block-select]");if(c){for(var u="paragraph",d=1;d<=6;d++)if(l.isActive("heading",{level:d})){u="heading-"+d;break}c.value=u}},bindForm:function(o,l){var a=o.closest("form"),c=this;a&&a.addEventListener("submit",function(){c.syncTextarea(o,l)})},syncTextarea:function(o,l){var a;return o?(a=o.closest(".rich-editor"),a&&a.classList.contains("rich-editor-source-open")&&a._richSourceEditor?(a._richSourceEditor.save(),o.value=a._richSourceEditor.getValue(),o.setAttribute("data-rich-editor-dirty","1"),o.value):(l&&o.getAttribute("data-rich-editor-dirty")==="1"&&(o.value=l.getHTML()),o.value)):""},openSource:function(o){var l=o.view.dom.closest(".rich-editor");if(!(!l||!n.CodeMirror)){var a=l.querySelector(".rich-editor-surface"),c=l.querySelector("textarea[data-rich-editor]"),u=l.querySelector("[data-rich-inline-source]"),d=!l.classList.contains("rich-editor-source-open");if(!d){var f=l._richSourceEditor?l._richSourceEditor.getValue():c?c.value:"";f!==o.getHTML()&&o.commands.setContent(f||"
"),c&&(c.value=o.getHTML()),l.classList.remove("rich-editor-source-open"),this.setSourceControls(l,!1),this.syncToolbar(l,o),o.commands.focus();return}if(!u){u=r("div",{class:"rich-editor-inline-source","data-rich-inline-source":""});var h=r("textarea",{"data-rich-source-textarea":""});u.appendChild(h),a.parentNode.insertBefore(u,a.nextSibling),l._richSourceEditor=n.CodeMirror.fromTextArea(h,{mode:"htmlmixed",theme:e.documentElement.getAttribute("data-theme")==="dark"?"material-ocean":"default",lineNumbers:!0,lineWrapping:!0,matchBrackets:!0,autoCloseTags:!0,styleActiveLine:!0,indentUnit:2}),l._richSourceEditor.on("change",function(m){!c||l._richSourceSyncing||(c.value=m.getValue(),c.setAttribute("data-rich-editor-dirty","1"),c.dispatchEvent(new Event("input",{bubbles:!0})))})}var p=Math.max(260,Math.round(a.getBoundingClientRect().height||0));l._richSourceSyncing=!0,l._richSourceEditor.setValue(o.getHTML()),l._richSourceSyncing=!1,l._richSourceEditor.setSize("100%",p),l.classList.add("rich-editor-source-open"),this.setSourceControls(l,!0),this.syncToolbar(l,o),setTimeout(function(){l._richSourceEditor.refresh(),l._richSourceEditor.focus()},0)}},setSourceControls:function(o,l){o.querySelectorAll(".rich-editor-toolbar [data-rich-command], .rich-editor-toolbar [data-rich-block-select]").forEach(function(a){a.getAttribute("data-rich-command")!=="source"&&(a.disabled=!!l)})},openLink:function(o){var l=this,a=o.getAttributes("link")||{},c=r("div",{class:"overlay rich-editor-link-overlay"}),u=r("div",{class:"modal rich-editor-link-modal",role:"dialog","aria-modal":"true","aria-labelledby":"richEditorLinkTitle"});u.innerHTML='',c.appendChild(u),e.body.appendChild(c),requestAnimationFrame(function(){c.classList.add("show")});var d=c.querySelector("[data-link-url]"),f=c.querySelector("[data-link-class]"),h=c.querySelector("[data-link-selection]"),p=function(){c.classList.remove("show"),setTimeout(function(){c.remove(),e.removeEventListener("keydown",y)},180)},m=function(){var b=String(d.value||"").trim(),k=String(f.value||"").trim().replace(/\s+/g," ");if(!b){o.chain().focus().unsetLink().run(),p();return}o.chain().focus().extendMarkRange("link").setLink({href:b,class:k||null}).run(),p()},g=function(b,k){d.value=b||"",h.textContent=k||"\u0410\u0434\u0440\u0435\u0441 \u0432\u044B\u0431\u0440\u0430\u043D",d.dispatchEvent(new Event("input",{bubbles:!0}))},y=function(b){if(b.key==="Escape"){if(e.querySelector(".rich-editor-document-picker-overlay.show, .media-picker-overlay.show"))return;p()}b.key==="Enter"&&b.target!==f&&(b.preventDefault(),m())};c.addEventListener("click",function(b){if(b.target===c||b.target.closest("[data-link-cancel]")){p();return}if(b.target.closest("[data-link-apply]")){m();return}if(b.target.closest("[data-link-unset]")){o.chain().focus().unsetLink().run(),p();return}if(b.target.closest("[data-link-document]")){l.openDocumentLinkPicker(function(k){var w=String(k.alias||"").trim(),C=w?/^(?:[a-z]+:|\/|#)/i.test(w)?w:"/"+w:"/index.php?id="+k.id;g(C,"\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442 #"+k.id+" \xB7 "+(k.title||"\u0411\u0435\u0437 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u044F"))});return}b.target.closest("[data-link-media]")&&t.MediaPicker&&t.MediaPicker.open({type:"all",title:"\u0424\u0430\u0439\u043B \u0434\u043B\u044F \u0441\u0441\u044B\u043B\u043A\u0438",description:"\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u0438\u043B\u0438 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0438\u0437 \u043C\u0435\u0434\u0438\u0430\u0442\u0435\u043A\u0438.",onPick:function(k){k&&k.url&&g(k.url,"\u041C\u0435\u0434\u0438\u0430 \xB7 "+(k.name||k.url))},onUrl:function(k){k&&g(k,"\u0412\u043D\u0435\u0448\u043D\u0438\u0439 \u0430\u0434\u0440\u0435\u0441 \u043C\u0435\u0434\u0438\u0430")}})}),e.addEventListener("keydown",y),d.focus(),d.select()},openDocumentLinkPicker:function(o){var l=this,a=r("div",{class:"overlay rich-editor-document-picker-overlay"}),c=r("div",{class:"modal picker-modal rich-editor-document-picker",role:"dialog","aria-modal":"true","aria-labelledby":"richEditorDocumentPickerTitle"});c.innerHTML='
\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430...
',a.appendChild(c),e.body.appendChild(a),requestAnimationFrame(function(){a.classList.add("show")});var u=a.querySelector("[data-document-link-search]"),d=a.querySelector("[data-document-link-list]"),f=a.querySelector("[data-document-link-status]"),h=a.querySelector("[data-document-link-count]"),p=[],m=null,g=0,y=function(){a.classList.remove("show"),setTimeout(function(){a.remove(),e.removeEventListener("keydown",b)},160)},b=function(w){w.key==="Escape"&&(w.stopImmediatePropagation(),y())},k=function(){var w=++g,C=new URLSearchParams;C.set("q",u.value.trim()),C.set("limit",40),f.hidden=!1,f.textContent="\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430...",fetch((n.ADMINX_BASE||(t.base?t.base():""))+"/documents/picker?"+C.toString(),{headers:{Accept:"application/json"},credentials:"same-origin"}).then(function(O){return O.json()}).then(function(O){w===g&&(p=(O.data||{}).items||[],d.innerHTML=p.map(function(M,D){return'#'+l.escape(M.id)+" "+l.escape(M.title||"\u0411\u0435\u0437 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u044F")+" /"+l.escape(String(M.alias||"").replace(/^\/+/,"")||"\u0431\u0435\u0437 alias")+" "+l.escape(M.rubric_title||"\u0420\u0443\u0431\u0440\u0438\u043A\u0430 #"+M.rubric_id)+" "}).join(""),f.hidden=p.length>0,f.textContent=u.value.trim()?"\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E":"\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u044B",h.textContent=p.length?"\u041F\u043E\u043A\u0430\u0437\u0430\u043D\u043E: "+p.length:"")}).catch(function(){p=[],d.innerHTML="",f.hidden=!1,f.textContent="\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B"})};a.addEventListener("click",function(w){if(w.target===a||w.target.closest("[data-document-link-close]")){y();return}var C=w.target.closest("[data-document-link-index]");if(C){var O=p[parseInt(C.getAttribute("data-document-link-index"),10)];O&&(o(O),y())}}),u.addEventListener("input",function(){clearTimeout(m),m=setTimeout(k,220)}),e.addEventListener("keydown",b),k(),u.focus()},openMediaPicker:function(o){this.openImageEditor(o,{},null)},openImageEditor:function(o,l,a){var c=this;l=l||{};var u=typeof a=="number",d=r("div",{class:"overlay rich-editor-image-overlay"}),f=r("div",{class:"modal rich-editor-image-modal",role:"dialog","aria-modal":"true","aria-labelledby":"richEditorImageTitle"});f.innerHTML='\u0418\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u043E
",d.appendChild(f),e.body.appendChild(d),requestAnimationFrame(function(){d.classList.add("show")});var h=d.querySelector("[data-image-src]"),p=d.querySelector("[data-image-webp]"),m=d.querySelector("[data-image-picture]"),g=d.querySelector("[data-image-webp-field]"),y=d.querySelector("[data-image-alt]"),b=d.querySelector("[data-image-class]"),k=d.querySelector("[data-image-width]"),w=d.querySelector("[data-image-height]"),C=d.querySelector("[data-image-scale]"),O=d.querySelector("[data-image-scale-value]"),M=d.querySelector("[data-image-preview-img]"),D=d.querySelector("[data-image-empty]"),B=d.querySelector("[data-image-preview-size]"),P={naturalWidth:parseInt(l.width,10)||0,naturalHeight:parseInt(l.height,10)||0,ratio:l.width&&l.height?parseInt(l.width,10)/parseInt(l.height,10):0,loadingSrc:""},Se=function(){var L=parseInt(k.value,10)||0,F=P.naturalWidth&&L?Math.round(L/P.naturalWidth*100):100;F=Math.max(10,Math.min(200,F)),C.value=F,O.textContent=F+"%",B.textContent=L&&w.value?L+" x "+w.value+" px":""},Te=function(L,F,Ct){L=Math.max(1,Math.round(Number(L)||0)),F=Math.max(1,Math.round(Number(F)||0)),!(!L||!F)&&(Ct?(P.naturalWidth=L,P.naturalHeight=F,P.ratio=L/F):P.ratio||(P.ratio=L/F),k.value=L,w.value=F,Se())},Q=function(L,F){if(L=String(L||"").trim(),P.loadingSrc=L,!L){M.hidden=!0,D.hidden=!1,B.textContent="";return}var Ct=new n.Image;Ct.onload=function(){if(P.loadingSrc===L){M.src=L,M.hidden=!1,D.hidden=!0;var xa=F&&F.width?F.width:Ct.naturalWidth,wa=F&&F.height?F.height:Ct.naturalHeight;!k.value||!w.value||L!==l.src?Te(xa,wa,!0):(P.naturalWidth=xa||parseInt(k.value,10),P.naturalHeight=wa||parseInt(w.value,10),P.ratio=P.naturalWidth/P.naturalHeight,Se())}},Ct.onerror=function(){P.loadingSrc===L&&(M.hidden=!0,D.hidden=!1,D.querySelector("span").textContent="\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435")},Ct.src=L},Z=function(L){t.MediaPicker&&t.MediaPicker.open({type:"image",title:L==="webp"?"\u0412\u044B\u0431\u0440\u0430\u0442\u044C WebP":"\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435",description:L==="webp"?"\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0444\u0430\u0439\u043B WebP \u0434\u043B\u044F \u0442\u0435\u0433\u0430 source.":"\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0435 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u0438\u0437 \u043C\u0435\u0434\u0438\u0430\u0442\u0435\u043A\u0438.",onPick:function(F){if(!(!F||!F.url)){if(L==="webp"){p.value=F.url,m.checked=!0,g.hidden=!1;return}h.value=F.url,!y.value&&F.name&&(y.value=String(F.name).replace(/\.[^.]+$/,"").replace(/[-_]+/g," ")),k.value="",w.value="",Q(F.url,F)}},onUrl:function(F){F&&(L==="webp"?(p.value=F,m.checked=!0,g.hidden=!1):(h.value=F,k.value="",w.value="",Q(F)))}})},J=function(){d.classList.remove("show"),setTimeout(function(){d.remove(),e.removeEventListener("keydown",ka)},180)},dt=function(){var L=String(h.value||"").trim();if(!L){h.focus();return}var F={src:L,webp:m.checked&&String(p.value||"").trim()||null,alt:String(y.value||"").trim()||null,class:String(b.value||"").trim().replace(/\s+/g," ")||null,width:parseInt(k.value,10)||null,height:parseInt(w.value,10)||null};u?o.chain().focus().setNodeSelection(a).updateAttributes("image",F).run():o.chain().focus().setImage(F).run(),J()},ka=function(L){if(L.key==="Escape"){if(e.querySelector(".media-picker-overlay.show"))return;J()}};m.addEventListener("change",function(){g.hidden=!m.checked,m.checked&&p.focus()}),h.addEventListener("change",function(){k.value="",w.value="",Q(h.value)}),k.addEventListener("input",function(){var L=parseInt(k.value,10)||0;L&&P.ratio&&(w.value=Math.max(1,Math.round(L/P.ratio))),Se()}),w.addEventListener("input",function(){var L=parseInt(w.value,10)||0;L&&P.ratio&&(k.value=Math.max(1,Math.round(L*P.ratio))),Se()}),C.addEventListener("input",function(){var L=parseInt(C.value,10)||100;O.textContent=L+"%",P.naturalWidth&&P.naturalHeight&&(k.value=Math.max(1,Math.round(P.naturalWidth*L/100)),w.value=Math.max(1,Math.round(P.naturalHeight*L/100)),B.textContent=k.value+" x "+w.value+" px")}),d.addEventListener("click",function(L){if(L.target===d||L.target.closest("[data-image-cancel]")){J();return}var F=L.target.closest("[data-image-pick]");if(F){Z(F.getAttribute("data-image-pick"));return}if(L.target.closest("[data-image-original]")&&P.naturalWidth&&P.naturalHeight){Te(P.naturalWidth,P.naturalHeight,!1);return}if(L.target.closest("[data-image-apply]")){dt();return}L.target.closest("[data-image-remove]")&&u&&(o.chain().focus().setNodeSelection(a).deleteSelection().run(),J())}),e.addEventListener("keydown",ka),l.src?Q(l.src):h.focus()},modalInput:function(o,l){var a=r("div",{class:"overlay"}),c=r("div",{class:"modal rich-editor-prompt",role:"dialog","aria-modal":"true"});c.innerHTML='
',a.appendChild(c),e.body.appendChild(a),requestAnimationFrame(function(){a.classList.add("show")});var u=a.querySelector("[data-input]"),d=function(){a.classList.remove("show"),setTimeout(function(){a.remove(),e.removeEventListener("keydown",h)},180)},f=function(){var p=u.value||"";d(),l(p)},h=function(p){p.key==="Escape"&&d(),p.key==="Enter"&&f()};a.addEventListener("click",function(p){(p.target===a||p.target.closest("[data-cancel]"))&&d(),p.target.closest("[data-ok]")&&f()}),e.addEventListener("keydown",h),u.focus(),u.select()},escape:function(o){return String(o==null?"":o).replace(/[&<>"]/g,function(l){return{"&":"&","<":"<",">":">",'"':"""}[l]})}},e.addEventListener("adminx:content-ready",function(o){t.RichEditor.init(o.detail&&o.detail.root?o.detail.root:e)}),e.readyState==="loading"?e.addEventListener("DOMContentLoaded",function(){t.RichEditor.init()}):t.RichEditor.init()})(window,document);})();
diff --git a/adminx/assets/js/media-picker.js b/adminx/assets/js/media-picker.js
index 9340a11..66140d9 100644
--- a/adminx/assets/js/media-picker.js
+++ b/adminx/assets/js/media-picker.js
@@ -47,8 +47,14 @@
+ ''
+ + ' Создать
'
+ (onUrl ? ' Вставить
' : '')
+ '
'
+ 'Загрузка…
'
@@ -63,12 +69,16 @@
document.body.appendChild(overlay);
window.requestAnimationFrame(function () { overlay.classList.add('show'); });
- var state = { dir: options.dir || '/uploads', parent: '', page: 1, pages: 1, q: '', type: type };
+ var state = { dir: options.dir || '/uploads', parent: '', page: 1, pages: 1, q: '', type: type, canManage: false, busy: false };
var grid = overlay.querySelector('[data-mp-grid]');
var crumbs = overlay.querySelector('[data-mp-crumbs]');
var status = overlay.querySelector('[data-mp-status]');
var count = overlay.querySelector('[data-mp-count]');
var search = overlay.querySelector('[data-mp-search]');
+ var manage = overlay.querySelector('[data-mp-manage]');
+ var uploadInput = overlay.querySelector('[data-mp-upload-input]');
+ var folderForm = overlay.querySelector('[data-mp-folder-form]');
+ var folderName = overlay.querySelector('[data-mp-folder-name]');
var timer = null;
function close() {
@@ -89,6 +99,8 @@
var folders = data.folders || [];
var files = data.files || [];
renderCrumbs(data.breadcrumbs || []);
+ state.canManage = !!data.can_manage;
+ manage.hidden = !state.canManage;
grid.innerHTML = '';
folders.forEach(function (folder) {
var folderMeta = state.q
@@ -111,6 +123,74 @@
overlay.querySelector('[data-mp-up]').disabled = !state.parent;
}
+ function responseData(payload) {
+ var response = payload && payload.data ? payload.data : {};
+ if (!payload || !payload.ok || response.success === false) {
+ throw new Error(response.message || (response.error && response.error.message) || 'Не удалось выполнить действие');
+ }
+ return response;
+ }
+
+ function setBusy(value, message) {
+ state.busy = !!value;
+ manage.querySelectorAll('button').forEach(function (button) { button.disabled = state.busy; });
+ status.hidden = !message;
+ if (message) { status.textContent = message; }
+ }
+
+ function clearSearch() {
+ state.q = '';
+ search.value = '';
+ }
+
+ function createFolder() {
+ var name = folderName.value.trim();
+ if (!name || state.busy) { folderName.focus(); return; }
+ var data = new FormData();
+ data.set('_csrf', Adminx.csrf());
+ data.set('dir', state.dir);
+ data.set('name', name);
+ setBusy(true, 'Создаём папку…');
+ Adminx.Ajax.post(base + '/media/folders', data).then(responseData).then(function (response) {
+ var result = response.data || {};
+ folderForm.hidden = true;
+ folderName.value = '';
+ clearSearch();
+ Adminx.Toast.show(response.message || 'Папка создана', 'success');
+ load(result.path || state.dir, 1);
+ }).catch(function (error) {
+ setBusy(false, '');
+ Adminx.Toast.show(error.message || 'Не удалось создать папку', 'error');
+ folderName.focus();
+ });
+ }
+
+ function upload(files) {
+ if (!files || !files.length || state.busy) { return; }
+ setBusy(true, 'Подготовка к загрузке: ' + files.length + '…');
+ Adminx.Upload.files(base + '/media/upload', files, function (chunk) {
+ var data = new FormData();
+ data.set('_csrf', Adminx.csrf());
+ data.set('dir', state.dir);
+ chunk.forEach(function (file) { data.append('files[]', file); });
+ return data;
+ }, function (processed, total) {
+ status.textContent = 'Загружено: ' + processed + ' из ' + total + '…';
+ }).then(function (result) {
+ uploadInput.value = '';
+ clearSearch();
+ Adminx.Toast.show('Загружено файлов: ' + result.files.length, 'success');
+ load(state.dir, 1);
+ }).catch(function (error) {
+ uploadInput.value = '';
+ setBusy(false, '');
+ var uploaded = error && error.uploaded ? error.uploaded.length : 0;
+ var message = error.message || 'Не удалось загрузить файлы';
+ if (uploaded) { message += '. До ошибки загружено: ' + uploaded; }
+ Adminx.Toast.show(message, 'error');
+ });
+ }
+
function load(dir, page) {
state.dir = dir || state.dir;
state.page = page || 1;
@@ -131,9 +211,10 @@
state.parent = data.parent_dir || '';
state.page = data.page || 1;
state.pages = data.pages || 1;
+ setBusy(false, '');
render(data);
})
- .catch(function () { status.textContent = 'Не удалось загрузить медиа'; status.hidden = false; });
+ .catch(function () { setBusy(false, ''); status.textContent = 'Не удалось загрузить медиа'; status.hidden = false; });
}
overlay.addEventListener('click', function (e) {
@@ -146,6 +227,10 @@
if (e.target.closest('[data-mp-prev]') && state.page > 1) { load(state.dir, state.page - 1); }
if (e.target.closest('[data-mp-next]') && state.page < state.pages) { load(state.dir, state.page + 1); }
if (e.target.closest('[data-mp-up]') && state.parent) { load(state.parent, 1); }
+ if (e.target.closest('[data-mp-folder-toggle]')) { folderForm.hidden = !folderForm.hidden; if (!folderForm.hidden) { folderName.focus(); } return; }
+ if (e.target.closest('[data-mp-folder-cancel]')) { folderForm.hidden = true; folderName.value = ''; return; }
+ if (e.target.closest('[data-mp-folder-create]')) { createFolder(); return; }
+ if (e.target.closest('[data-mp-upload]')) { uploadInput.click(); return; }
if (onUrl) {
if (e.target.closest('[data-mp-url-toggle]')) {
var box = overlay.querySelector('[data-mp-url]');
@@ -163,6 +248,10 @@
window.clearTimeout(timer);
timer = window.setTimeout(function () { state.q = search.value.trim(); load(state.dir, 1); }, 220);
});
+ folderName.addEventListener('keydown', function (e) {
+ if (e.key === 'Enter') { e.preventDefault(); createFolder(); }
+ });
+ uploadInput.addEventListener('change', function () { upload(uploadInput.files); });
document.addEventListener('keydown', onKey);
load(state.dir, 1);
search.focus();
diff --git a/adminx/assets/src/editor-tiptap.js b/adminx/assets/src/editor-tiptap.js
index d30aeda..c3e3cb6 100644
--- a/adminx/assets/src/editor-tiptap.js
+++ b/adminx/assets/src/editor-tiptap.js
@@ -290,20 +290,29 @@ var EnhancedImage = Image.extend({
bindForm: function (textarea, editor) {
var form = textarea.closest('form');
+ var self = this;
if (!form) { return; }
form.addEventListener('submit', function () {
- var root = textarea.closest('.rich-editor');
- if (root && root.classList.contains('rich-editor-source-open') && root._richSourceEditor) {
- root._richSourceEditor.save();
- textarea.value = root._richSourceEditor.getValue();
- return;
- }
- if (textarea.getAttribute('data-rich-editor-dirty') === '1') {
- textarea.value = editor.getHTML();
- }
+ self.syncTextarea(textarea, editor);
});
},
+ syncTextarea: function (textarea, editor) {
+ var root;
+ if (!textarea) { return ''; }
+ root = textarea.closest('.rich-editor');
+ if (root && root.classList.contains('rich-editor-source-open') && root._richSourceEditor) {
+ root._richSourceEditor.save();
+ textarea.value = root._richSourceEditor.getValue();
+ textarea.setAttribute('data-rich-editor-dirty', '1');
+ return textarea.value;
+ }
+ if (editor && textarea.getAttribute('data-rich-editor-dirty') === '1') {
+ textarea.value = editor.getHTML();
+ }
+ return textarea.value;
+ },
+
openSource: function (editor) {
var root = editor.view.dom.closest('.rich-editor');
if (!root || !window.CodeMirror) { return; }
diff --git a/adminx/modules/Catalog/Controller.php b/adminx/modules/Catalog/Controller.php
index 85c7819..5872e77 100644
--- a/adminx/modules/Catalog/Controller.php
+++ b/adminx/modules/Catalog/Controller.php
@@ -90,12 +90,15 @@
'state' => Request::getStr('state', ''),
'issue' => Request::getStr('issue', ''),
'payment' => Request::getStr('payment', ''),
+ 'view' => Request::getStr('view', 'table') === 'cards' ? 'cards' : 'table',
'page' => Request::getInt('page', 1),
'per_page' => Request::getInt('per_page', 25),
);
+ $statsSnapshot = Model::productStatsSnapshot();
return $this->render('@products/products.twig', array(
'result' => Model::products($filters),
- 'stats' => Model::productStats(),
+ 'stats' => $statsSnapshot['stats'],
+ 'stats_loaded' => $statsSnapshot['loaded'],
'filters' => $filters,
'saved_views' => SavedViews::all('catalog_products', Auth::id(), $this->savedViewFields('products')),
'can_manage' => Permission::check('manage_products'),
@@ -104,26 +107,32 @@
));
}
+ public function productStats(array $params = array())
+ {
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); }
+ $stats = Model::productStats();
+ $stats['indexed_label'] = !empty($stats['indexed_at']) ? date('d.m H:i', (int) $stats['indexed_at']) : '—';
+ return $this->success('', array('data' => array('stats' => $stats)));
+ }
+
public function productQuality(array $params = array())
{
if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
if (!Model::hasCommerceCatalogs()) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Товарные каталоги не настроены'), 404); }
$this->assets();
- $stats = Model::productStats();
+ $stats = Model::productQualityStats();
$issues = Model::productQualityIssues($stats);
$allowed = array();
foreach ($issues as $item) { $allowed[(string) $item['code']] = true; }
$issue = Request::getStr('issue', '');
if ($issue !== '' && !isset($allowed[$issue])) { $issue = ''; }
- $state = Request::getStr('state', '');
- if (!in_array($state, array('', 'active', 'inactive'), true)) { $state = ''; }
$filters = array(
'q' => Request::getStr('q', ''),
'issue' => $issue,
- 'state' => $state,
'page' => Request::getInt('page', 1),
'per_page' => Request::getInt('per_page', 25),
'include_quality' => true,
+ 'quality_scope' => true,
);
return $this->render('@products/product-quality.twig', array(
'result' => Model::products($filters),
@@ -1050,6 +1059,7 @@
$search = Request::getStr('q', '');
return $this->render('@products/variant-group.twig', array(
'group' => $group, 'search' => $search,
+ 'focus_product_id' => Request::getInt('focus', 0),
'variant_fields' => VariantGroups::fields((int) $group['id']),
'variant_attributes' => VariantGroups::nativeAttributes(),
'package_templates' => ShippingPackageTemplates::all(),
@@ -1169,6 +1179,7 @@
public function copyVariantProduct(array $params = array())
{
if (($error = $this->productGuard()) !== null) { return $error; }
+ if (!Request::postBool('confirmed', false)) { return $this->error('Подтвердите создание варианта', array(), 409); }
$sourceId = (int) $params['product'];
$newId = 0;
try {
@@ -1181,12 +1192,15 @@
}
$this->audit('catalog.variant_copied', (int) $params['id'], array('source_id' => $sourceId, 'product_id' => $newId));
- return $this->success('Вариант создан копированием', array('redirect' => $this->base() . '/catalog/products/' . $newId . '/edit'));
+ return $this->success('Вариант создан копированием', array(
+ 'redirect' => $this->base() . '/catalog/products/' . $newId . '/edit?variant_group=' . (int) $params['id'],
+ ));
}
public function copyProduct(array $params = array())
{
if (($error = $this->guardPermission('manage_products', 'manage_documents')) !== null) { return $error; }
+ if (!Request::postBool('confirmed', false)) { return $this->error('Подтвердите создание копии товара', array(), 409); }
$sourceId = isset($params['id']) ? (int) $params['id'] : 0;
try {
$copy = ProductDuplicator::copy($sourceId, Auth::id());
@@ -1269,7 +1283,23 @@
VariantGroups::add($groupId, $productId);
} catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
$this->audit('catalog.variant_group_created_from_product', $groupId, array('product_id' => $productId));
- return $this->success('Группа вариантов создана', array('redirect' => $this->base() . '/catalog/variant-groups/' . $groupId));
+ return $this->success('Группа вариантов создана', array('redirect' => $this->variantGroupUrl($groupId, $productId)));
+ }
+
+ public function attachProductVariantGroup(array $params = array())
+ {
+ if (($error = $this->productGuard()) !== null) { return $error; }
+ $productId = isset($params['id']) ? (int) $params['id'] : 0;
+ $groupId = Request::postInt('group_id', 0);
+ if (!Model::product($productId)) { return $this->error('Товар не найден', array(), 404); }
+ if ($groupId <= 0) { return $this->error('Выберите группу вариантов', array(), 422); }
+ if (VariantGroups::membership($productId)) { return $this->error('Товар уже состоит в группе', array(), 422); }
+ try { VariantGroups::add($groupId, $productId); }
+ catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
+ $this->audit('catalog.variant_added_from_product', $groupId, array('product_id' => $productId));
+ return $this->success('Товар добавлен в группу вариантов', array(
+ 'redirect' => $this->variantGroupUrl($groupId, $productId),
+ ));
}
public function editProduct(array $params = array())
@@ -1287,8 +1317,17 @@
CodeEditor::useRichEditor();
$this->attributeAssets();
$variantGroup = VariantGroups::membership($id);
+ $returnUrl = $this->base() . '/catalog/products';
+ $returnLabel = 'Товары';
+ $returnGroupId = Request::getInt('variant_group', 0);
+ if ($variantGroup && $returnGroupId === (int) $variantGroup['id']) {
+ $returnUrl = $this->variantGroupUrl((int) $variantGroup['id'], $id);
+ $returnLabel = 'Группа вариантов';
+ }
+
$shippingProfile = ProductShipping::profile($id);
$nativeAttributes = Attributes::productEditor($id);
+ $searchAliasesAvailable = $this->productSearchAliasesAvailable();
$mediaDraftToken = DocumentMediaDraft::issue(Auth::id(), $id);
return $this->render('@documents/edit.twig', array(
'document' => $document,
@@ -1301,6 +1340,7 @@
'is_new' => false, 'can_manage' => true, 'quick_edit' => Request::getBool('quick_edit', false),
'actor_id' => Auth::id(),
'catalog_mode' => true, 'product' => $product, 'variant_group' => $variantGroup,
+ 'variant_groups' => $variantGroup ? array() : VariantGroups::all(),
'shipping_profile' => $shippingProfile,
'package_templates' => ShippingPackageTemplates::all(),
'native_attributes' => $nativeAttributes,
@@ -1308,7 +1348,12 @@
'product_promotions_available' => Promotions::available(),
'product_promotions' => Promotions::forProduct($id),
'can_manage_product_promotions' => Permission::check('manage_orders'),
- 'return_url' => $this->base() . '/catalog/products',
+ 'product_search_aliases_available' => $searchAliasesAvailable,
+ 'product_search_aliases' => $searchAliasesAvailable
+ ? \App\Modules\Search\DocumentAliases::forDocument($id)
+ : array(),
+ 'return_url' => $returnUrl,
+ 'return_label' => $returnLabel,
'product_copy_url' => $this->base() . '/catalog/products/' . $id . '/copy',
'submit_url' => $this->base() . '/documents/' . $id,
));
@@ -1419,6 +1464,47 @@
if(($error=$this->productGuard())!==null){return $error;}$productId=isset($params['id'])?(int)$params['id']:0;if(!Model::product($productId)){return $this->error('Товар не найден',array(),404);}try{$data=Attributes::saveProductValues($productId,Request::postAll(),Auth::id());}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}$this->audit('catalog.product_attributes_saved',$productId,$data);return $this->success('Характеристики товара сохранены',array('data'=>$data));
}
+ public function saveProductSearchAliases(array $params = array())
+ {
+ if (($error = $this->productGuard()) !== null) { return $error; }
+ $productId = isset($params['id']) ? (int) $params['id'] : 0;
+ if (!Model::product($productId)) { return $this->error('Товар не найден', array(), 404); }
+ if (!$this->productSearchAliasesAvailable()) {
+ return $this->error('Обновите и включите модуль «Поиск по сайту»', array(), 409);
+ }
+
+ try {
+ $aliases = \App\Modules\Search\DocumentAliases::saveDocument(
+ $productId,
+ (array) Request::post('aliases', array()),
+ Auth::id()
+ );
+ } catch (\Throwable $e) {
+ return $this->error($e->getMessage(), array(), 422);
+ }
+
+ $this->audit('catalog.product_search_aliases_saved', $productId, array('count' => count($aliases)));
+ return $this->success('Поисковые названия сохранены', array('data' => array('aliases' => $aliases)));
+ }
+
+ public function productSearchAliasSuggestions(array $params = array())
+ {
+ if (!Permission::check('manage_products')) {
+ return $this->error('Недостаточно прав', array(), 403);
+ }
+
+ if (!$this->productSearchAliasesAvailable()) {
+ return $this->error('Обновите и включите модуль «Поиск по сайту»', array(), 409);
+ }
+
+ $query = Request::getStr('q', '');
+ $documentId = Request::getInt('document_id', 0);
+ $items = mb_strlen(trim($query), 'UTF-8') >= 2
+ ? \App\Modules\Search\DocumentAliases::suggestions($query, $documentId, 12)
+ : array();
+ return $this->success('', array('data' => array('items' => $items)));
+ }
+
public function copyProductShippingToVariants(array $params = array())
{
if (($error = $this->productGuard()) !== null) { return $error; }
@@ -1521,14 +1607,24 @@
$allowed = Model::productRubrics();
$rubricId = Request::getInt('rubric_id', 0);
- if (!in_array($rubricId, $allowed, true)) { $rubricId = !empty($allowed) ? (int) reset($allowed) : 0; }
- if ($rubricId <= 0) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Товарная рубрика не настроена'), 404); }
+ if (!$allowed) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Товарная рубрика не настроена'), 404); }
+ if (!in_array($rubricId, $allowed, true)) {
+ $this->assets();
+ return $this->render('@products/product-create.twig', array(
+ 'rubrics' => Model::productRubricOptions(),
+ 'return_url' => $this->base() . '/catalog/products',
+ ));
+ }
+
$document = DocumentsModel::blank($rubricId);
+ $mediaDraftToken = DocumentMediaDraft::issue(Auth::id(), 0);
AdminAssets::addStyle($this->base() . '/modules/Documents/assets/documents.css', 50);
AdminAssets::addScript($this->base() . '/modules/Documents/assets/documents.js', 50);
CodeEditor::useRichEditor(); $this->assets();
return $this->render('@documents/edit.twig', array(
- 'document' => $document, 'field_groups' => DocumentsModel::fieldsForRubric($rubricId, 0),
+ 'document' => $document,
+ 'field_groups' => DocumentsModel::fieldsForRubric($rubricId, 0, $mediaDraftToken),
+ 'media_draft_token' => $mediaDraftToken,
'rubrics' => array_values(array_filter(DocumentsModel::rubrics(), function ($rubric) use ($allowed) { return in_array((int) $rubric['Id'], $allowed, true); })),
'templates' => DocumentsModel::rubricTemplates($rubricId), 'navigation_items' => DocumentsModel::navigationItems(),
'authors' => DocumentsModel::authors(), 'is_new' => true, 'can_manage' => true,
@@ -1774,8 +1870,8 @@
protected function savedViewFields($scope)
{
return $scope === 'quality'
- ? array('q', 'issue', 'state', 'per_page')
- : array('q', 'state', 'issue', 'payment', 'per_page');
+ ? array('q', 'issue', 'per_page')
+ : array('q', 'state', 'issue', 'payment', 'per_page', 'view');
}
protected function quickEditData()
@@ -1801,6 +1897,16 @@
'quick_profiles_available' => QuickEditorProfiles::available());
}
+ protected function variantGroupUrl($groupId, $productId = 0)
+ {
+ $url = $this->base() . '/catalog/variant-groups/' . (int) $groupId;
+ if ((int) $productId > 0) {
+ $url .= '?focus=' . (int) $productId . '#variant-' . (int) $productId;
+ }
+
+ return $url;
+ }
+
protected function assets()
{
AdminAssets::addStyle($this->base() . '/modules/Catalog/assets/catalog.css', 50);
@@ -1827,6 +1933,16 @@
&& is_file(BASEPATH . '/modules/products/admin/module.php');
}
+ protected function productSearchAliasesAvailable()
+ {
+ $module = ModuleManager::get('search');
+ return is_array($module)
+ && !empty($module['installed'])
+ && !empty($module['enabled'])
+ && class_exists('App\\Modules\\Search\\DocumentAliases')
+ && \App\Modules\Search\DocumentAliases::available();
+ }
+
protected function attributeAssets()
{
$this->assets();
diff --git a/adminx/modules/Catalog/Model.php b/adminx/modules/Catalog/Model.php
index 5826da4..795f55b 100644
--- a/adminx/modules/Catalog/Model.php
+++ b/adminx/modules/Catalog/Model.php
@@ -257,8 +257,8 @@
protected static function filterTemplateSettings(array $input, $submitted = false)
{
- $zero = isset($input['zero_behavior']) ? (string) $input['zero_behavior'] : 'disable';
- if (!in_array($zero, array('show', 'disable', 'hide'), true)) { $zero = 'disable'; }
+ $zero = isset($input['zero_behavior']) ? (string) $input['zero_behavior'] : 'hide';
+ if (!in_array($zero, array('show', 'disable', 'hide'), true)) { $zero = 'hide'; }
return array(
'show_counts' => $submitted ? !empty($input['show_counts']) : (!array_key_exists('show_counts', $input) || !empty($input['show_counts'])),
'zero_behavior' => $zero,
@@ -519,6 +519,47 @@
return $out;
}
+ /** Разделы каталога, доступные как родители в редакторе документа. */
+ public static function searchParentDocuments($query, $limit = 30)
+ {
+ $query = trim((string) $query);
+ $limit = max(1, min(50, (int) $limit));
+ $sql = 'SELECT i.id AS catalog_item_id,i.name,i.status AS catalog_status,i.document_id,d.rubric_id,'
+ . ' d.document_title,d.document_alias,r.rubric_title'
+ . ' FROM ' . self::itemsTable() . ' i'
+ . ' INNER JOIN ' . self::documentsTable() . ' d ON d.Id=i.document_id'
+ . ' LEFT JOIN ' . self::rubricsTable() . ' r ON r.Id=d.rubric_id'
+ . " WHERE i.document_id>0 AND d.document_deleted!='1'";
+ $args = array();
+ if ($query !== '') {
+ $sql .= ' AND (i.name LIKE %ss OR d.document_title LIKE %ss'
+ . ' OR d.document_alias LIKE %ss OR d.Id=%i)';
+ $args[] = $query;
+ $args[] = $query;
+ $args[] = $query;
+ $args[] = (int) $query;
+ }
+
+ $sql .= ' ORDER BY i.level ASC,i.position ASC,i.name ASC LIMIT ' . $limit;
+ $rows = call_user_func_array(array('DB', 'query'), array_merge(array($sql), $args))->getAll();
+ $out = array();
+ foreach ($rows ?: array() as $row) {
+ $out[] = array(
+ 'id' => (int) $row['document_id'],
+ 'rubric_id' => (int) $row['rubric_id'],
+ 'title' => self::decode($row['name']),
+ 'alias' => (string) $row['document_alias'],
+ 'status' => (int) $row['catalog_status'],
+ 'rubric_title' => 'Раздел каталога'
+ . ((int) $row['catalog_status'] === 1 ? '' : ' · выключен')
+ . ' · ' . self::decode($row['rubric_title']),
+ 'catalog_item_id' => (int) $row['catalog_item_id'],
+ );
+ }
+
+ return $out;
+ }
+
public static function parseValue($value)
{
$data = FieldValueCodec::decodeStructured($value, array());
@@ -543,7 +584,35 @@
{
return self::productsAvailable()
? ProductIndexer::stats()
- : array('total' => 0, 'active' => 0, 'inactive' => 0, 'categories' => 0);
+ : self::emptyProductStats();
+ }
+
+ public static function productQualityStats()
+ {
+ return self::productsAvailable()
+ ? ProductIndexer::qualityStats()
+ : self::emptyProductStats();
+ }
+
+ public static function productStatsSnapshot()
+ {
+ $stats = self::productsAvailable() ? ProductIndexer::cachedStats() : self::emptyProductStats();
+ return array(
+ 'loaded' => is_array($stats),
+ 'stats' => array_merge(self::emptyProductStats(), is_array($stats) ? $stats : array()),
+ );
+ }
+
+ protected static function emptyProductStats()
+ {
+ return array(
+ 'total'=>0,'quality_total'=>0,'active'=>0,'inactive'=>0,'without_category'=>0,
+ 'without_image'=>0,'without_article'=>0,'without_price'=>0,'without_stock'=>0,
+ 'without_excerpt'=>0,'without_seo'=>0,'stale'=>0,'shipping_incomplete'=>0,
+ 'shipping_disabled'=>0,'duplicate_articles'=>0,'legacy_attributes'=>0,
+ 'filter_index_errors'=>0,'registration_incomplete'=>0,'without_video'=>0,
+ 'variant_errors'=>0,'description_markup'=>0,'sfr'=>0,'issues'=>0,'recommendations'=>0,'indexed_at'=>0,
+ );
}
public static function productQualityIssues(array $stats)
@@ -561,6 +630,14 @@
return self::productsAvailable() ? ProductIndexer::productRubrics() : array();
}
+ public static function productRubricOptions()
+ {
+ $allowed = array_flip(self::productRubrics());
+ return array_values(array_filter(self::rubricOptions(), function ($rubric) use ($allowed) {
+ return isset($allowed[(int) $rubric['id']]);
+ }));
+ }
+
public static function reindexProducts($limit = 0)
{
return self::productsAvailable()
diff --git a/adminx/modules/Catalog/module.php b/adminx/modules/Catalog/module.php
index 9280b17..86a4106 100644
--- a/adminx/modules/Catalog/module.php
+++ b/adminx/modules/Catalog/module.php
@@ -14,7 +14,7 @@
defined('BASEPATH') || die('Direct access to this location is not allowed.');
return array(
- 'code' => 'catalog', 'name' => 'Каталог', 'version' => '0.3.2',
+ 'code' => 'catalog', 'name' => 'Каталог', 'version' => '0.3.5',
'permissions' => array('key' => 'catalog', 'items' => array(
array(
'code' => 'view_catalog',
diff --git a/adminx/modules/Catalog/view/edit.twig b/adminx/modules/Catalog/view/edit.twig
index 8087f74..812591a 100644
--- a/adminx/modules/Catalog/view/edit.twig
+++ b/adminx/modules/Catalog/view/edit.twig
@@ -47,7 +47,7 @@
Карточка товара Единый вид для главной страницы и разделов товарного каталога.
@@ -66,7 +66,7 @@
{% endif %}
diff --git a/adminx/modules/Database/Backup.php b/adminx/modules/Database/Backup.php
index e2a8b94..f1a75d2 100644
--- a/adminx/modules/Database/Backup.php
+++ b/adminx/modules/Database/Backup.php
@@ -337,9 +337,13 @@
/** Заменяет префикс только внутри SQL-идентификаторов, не затрагивая данные строк. */
protected static function portableDdl($ddl)
{
+ $ddl = preg_replace_callback('/\bCONSTRAINT\s+`([^`]+)`/i', function ($match) {
+ return 'CONSTRAINT `{{prefix}}_fk_' . substr(hash('sha256', (string) $match[1]), 0, 16) . '`';
+ }, (string) $ddl);
+
$ddl = preg_replace_callback('/`([^`]+)`/', function ($match) {
return '`' . Model::portableTableName($match[1]) . '`';
- }, (string) $ddl);
+ }, $ddl);
//-- MariaDB выводит DEFAULT NULL у TEXT/BLOB, тогда как MySQL 5.7
//-- запрещает default для этих типов. NULL и без clause остаётся default.
diff --git a/adminx/modules/Database/SqlDumpReader.php b/adminx/modules/Database/SqlDumpReader.php
index fe861ca..510fab6 100644
--- a/adminx/modules/Database/SqlDumpReader.php
+++ b/adminx/modules/Database/SqlDumpReader.php
@@ -96,7 +96,7 @@
throw new \RuntimeException('SQL-дамп оборван или содержит незавершённую команду');
}
- if ($meta['format'] !== 'adminx full dump') {
+ if ($meta['format'] !== 'ave.cms full dump') {
throw new \RuntimeException('Файл не является полным дампом AVE.cms');
}
@@ -309,8 +309,9 @@
protected static function readMetadata($line, array &$meta)
{
$value = trim(substr(ltrim((string) $line), 2));
- if (strcasecmp($value, 'adminx full dump') === 0) {
- $meta['format'] = 'adminx full dump';
+ if (strcasecmp($value, 'AVE.cms full dump') === 0
+ || strcasecmp($value, 'adminx full dump') === 0) {
+ $meta['format'] = 'ave.cms full dump';
return;
}
diff --git a/adminx/modules/Database/module.php b/adminx/modules/Database/module.php
index 256f011..c0f8b0c 100644
--- a/adminx/modules/Database/module.php
+++ b/adminx/modules/Database/module.php
@@ -21,7 +21,7 @@
return [
'code' => 'database',
'name' => 'База данных',
- 'version' => '0.2.0',
+ 'version' => '0.2.2',
'permissions' => [
'key' => 'database',
diff --git a/adminx/modules/Documents/Controller.php b/adminx/modules/Documents/Controller.php
index 15a8d62..3514a20 100644
--- a/adminx/modules/Documents/Controller.php
+++ b/adminx/modules/Documents/Controller.php
@@ -361,13 +361,22 @@
return $this->error('Недостаточно прав', array(), 403);
}
+ $scope = Request::getStr('scope', 'document');
+ $items = $scope === 'parent'
+ ? Model::parentDocumentPicker(
+ Request::getStr('q', ''),
+ Request::getInt('limit', 20),
+ Request::getInt('exclude_id', 0)
+ )
+ : Model::documentPicker(
+ Request::getStr('q', ''),
+ Request::getStr('rubric_id', ''),
+ Request::getInt('limit', 20)
+ );
+
return $this->success('', array(
'data' => array(
- 'items' => Model::documentPicker(
- Request::getStr('q', ''),
- Request::getStr('rubric_id', ''),
- Request::getInt('limit', 20)
- ),
+ 'items' => $items,
),
));
}
diff --git a/adminx/modules/Documents/Model.php b/adminx/modules/Documents/Model.php
index ce029ba..794618e 100644
--- a/adminx/modules/Documents/Model.php
+++ b/adminx/modules/Documents/Model.php
@@ -304,10 +304,21 @@
if (!empty($allowed)) { $catalogAllowed = array_values(array_unique(array_merge($catalogAllowed, $allowed))); }
}
+ $conditionReferences = $conditionsEnabled ? self::conditionReferences($rows) : array();
+ $conditionSources = array();
$groups = array();
foreach ($rows as $row) {
$settings = FieldSettings::effective($row);
- if (array_key_exists('placed', $settings) && empty($settings['placed'])) { continue; }
+ if (array_key_exists('placed', $settings) && empty($settings['placed'])) {
+ $idReference = 'id:' . (int) $row['Id'];
+ $aliasReference = 'alias:' . strtolower(trim((string) $row['rubric_field_alias']));
+ if (isset($conditionReferences[$idReference]) || isset($conditionReferences[$aliasReference])) {
+ $conditionSources[(int) $row['Id']] = self::conditionSourceRow($row);
+ }
+
+ continue;
+ }
+
$groupId = (int) $row['rubric_field_group'];
if (!isset($groups[$groupId])) {
$groups[$groupId] = array(
@@ -324,7 +335,12 @@
$groups[$groupId]['items'][] = $field;
}
- return array_values($groups);
+ $groups = array_values($groups);
+ if (!empty($groups) && !empty($conditionSources)) {
+ $groups[0]['condition_sources'] = array_values($conditionSources);
+ }
+
+ return $groups;
}
/**
@@ -338,13 +354,11 @@
return array();
}
- $fields = array();
+ $fieldGroups = self::resolveFieldGroups($rubricId, (int) $documentId, $fieldGroups);
+ $fields = self::conditionAwareFields($fieldGroups);
$effectiveValues = array();
- foreach (self::resolveFieldGroups($rubricId, (int) $documentId, $fieldGroups) as $group) {
- foreach ($group['items'] as $field) {
- $fields[] = $field;
- $effectiveValues[(int) $field['Id']] = isset($field['field_value']) ? $field['field_value'] : '';
- }
+ foreach ($fields as $field) {
+ $effectiveValues[(int) $field['Id']] = isset($field['field_value']) ? $field['field_value'] : '';
}
foreach ($values as $id => $value) {
@@ -356,14 +370,14 @@
public static function computedFieldValues($rubricId, array $values, array $document = array(), $documentId = 0, $fieldGroups = null)
{
+ $fieldGroups = self::resolveFieldGroups((int) $rubricId, (int) $documentId, $fieldGroups);
+ $fields = self::conditionAwareFields($fieldGroups);
$definitions = array();
$effectiveValues = array();
- foreach (self::resolveFieldGroups((int) $rubricId, (int) $documentId, $fieldGroups) as $group) {
- foreach ($group['items'] as $field) {
- $fieldId = (int) $field['Id'];
- $definitions[$fieldId] = $field;
- $effectiveValues[$fieldId] = isset($field['field_value']) ? $field['field_value'] : '';
- }
+ foreach ($fields as $field) {
+ $fieldId = (int) $field['Id'];
+ $definitions[$fieldId] = $field;
+ $effectiveValues[$fieldId] = isset($field['field_value']) ? $field['field_value'] : '';
}
foreach ($values as $fieldId => $value) {
@@ -387,18 +401,19 @@
return $values;
}
- $fields = array();
+ $fieldGroups = self::resolveFieldGroups((int) $rubricId, (int) $documentId, $fieldGroups);
+ $fields = self::conditionAwareFields($fieldGroups);
$fieldMap = array();
$storedValues = array();
$effectiveValues = array();
- foreach (self::resolveFieldGroups((int) $rubricId, (int) $documentId, $fieldGroups) as $group) {
- foreach ($group['items'] as $field) {
- $fields[] = $field;
+ foreach ($fields as $field) {
$fieldId = (int) $field['Id'];
+ if (empty($field['_condition_source'])) {
$fieldMap[$fieldId] = $field;
+ }
+
$storedValues[$fieldId] = (int) $documentId > 0 && isset($field['field_value']) ? $field['field_value'] : '';
$effectiveValues[$fieldId] = isset($field['field_value']) ? $field['field_value'] : '';
- }
}
foreach ($values as $id => $value) {
@@ -821,6 +836,29 @@
: self::fieldsForRubric((int) $rubricId, (int) $documentId);
}
+ protected static function conditionAwareFields(array $fieldGroups)
+ {
+ $fields = array();
+ $seen = array();
+ foreach ($fieldGroups as $group) {
+ foreach (isset($group['condition_sources']) && is_array($group['condition_sources']) ? $group['condition_sources'] : array() as $field) {
+ $fieldId = isset($field['Id']) ? (int) $field['Id'] : 0;
+ if ($fieldId <= 0 || isset($seen[$fieldId])) { continue; }
+ $seen[$fieldId] = true;
+ $fields[] = $field;
+ }
+
+ foreach (isset($group['items']) && is_array($group['items']) ? $group['items'] : array() as $field) {
+ $fieldId = isset($field['Id']) ? (int) $field['Id'] : 0;
+ if ($fieldId <= 0 || isset($seen[$fieldId])) { continue; }
+ $seen[$fieldId] = true;
+ $fields[] = $field;
+ }
+ }
+
+ return $fields;
+ }
+
public static function delete($id)
{
$id = (int) $id;
@@ -1331,6 +1369,33 @@
return (new \App\Content\Documents\DocumentPickerRepository())->search($q, $rubricId, $limit);
}
+ public static function parentDocumentPicker($q, $limit = 20, $excludeId = 0)
+ {
+ $limit = max(1, min(50, (int) $limit));
+ $excludeId = max(0, (int) $excludeId);
+ $items = CatalogModel::searchParentDocuments($q, $limit);
+ $seen = array();
+ $result = array();
+ foreach ($items as $item) {
+ $id = (int) $item['id'];
+ if ($id <= 0 || $id === $excludeId || isset($seen[$id])) { continue; }
+ $seen[$id] = true;
+ $result[] = $item;
+ if (count($result) >= $limit) { return $result; }
+ }
+
+ $documents = (new \App\Content\Documents\DocumentPickerRepository())->search($q, array(), $limit);
+ foreach ($documents as $item) {
+ $id = (int) $item['id'];
+ if ($id <= 0 || $id === $excludeId || isset($seen[$id])) { continue; }
+ $seen[$id] = true;
+ $result[] = $item;
+ if (count($result) >= $limit) { break; }
+ }
+
+ return $result;
+ }
+
/** Existing keywords/tags for the searchable tag inputs in document editor. */
public static function termSuggestions($kind, $q = '', $rubricId = 0, $limit = 12)
{
@@ -1673,11 +1738,7 @@
{
$type = (string) $row['rubric_field_type'];
$hasInitialValue = array_key_exists('__initial_value', $row);
- $raw = $hasInitialValue
- ? (string) $row['__initial_value']
- : ($row['document_field_id']
- ? (string) $row['field_value'] . (string) $row['field_value_more']
- : self::initialFieldValue($type, (string) $row['rubric_field_default']));
+ $raw = self::rawFieldValue($row);
$editor = FieldAdminEditors::describe($type);
$kind = self::documentEditorKind($editor);
$settings = FieldSettings::effective($row);
@@ -1777,6 +1838,60 @@
return $field;
}
+ protected static function rawFieldValue(array $row)
+ {
+ if (array_key_exists('__initial_value', $row)) { return (string) $row['__initial_value']; }
+ if (!empty($row['document_field_id'])) {
+ return (string) $row['field_value'] . (string) $row['field_value_more'];
+ }
+
+ return self::initialFieldValue((string) $row['rubric_field_type'], (string) $row['rubric_field_default']);
+ }
+
+ protected static function conditionSourceRow(array $row)
+ {
+ return array(
+ 'Id' => (int) $row['Id'],
+ 'rubric_id' => (int) $row['rubric_id'],
+ 'rubric_field_group' => (int) $row['rubric_field_group'],
+ 'rubric_field_title' => self::decode($row['rubric_field_title']),
+ 'rubric_field_alias' => (string) $row['rubric_field_alias'],
+ 'rubric_field_type' => (string) $row['rubric_field_type'],
+ 'rubric_field_settings' => isset($row['rubric_field_settings']) ? (string) $row['rubric_field_settings'] : '',
+ 'group_settings' => isset($row['group_settings']) ? (string) $row['group_settings'] : '',
+ 'field_value' => self::rawFieldValue($row),
+ '_condition_source' => true,
+ );
+ }
+
+ protected static function conditionReferences(array $rows)
+ {
+ $references = array();
+ foreach ($rows as $row) {
+ foreach (array(FieldConditionEvaluator::condition($row), FieldConditionEvaluator::groupCondition($row)) as $condition) {
+ if (!empty($condition['tree']) && is_array($condition['tree'])) {
+ self::collectConditionReferences($condition['tree'], $references);
+ }
+ }
+ }
+
+ return $references;
+ }
+
+ protected static function collectConditionReferences(array $node, array &$references)
+ {
+ if (isset($node['items']) && is_array($node['items'])) {
+ foreach ($node['items'] as $item) {
+ if (is_array($item)) { self::collectConditionReferences($item, $references); }
+ }
+
+ return;
+ }
+
+ $reference = isset($node['field']) ? strtolower(trim((string) $node['field'])) : '';
+ if ($reference !== '') { $references[$reference] = true; }
+ }
+
protected static function hasGroupSettingsColumn()
{
static $has = null;
@@ -1941,7 +2056,9 @@
}
if ($kind === 'relation') {
- return isset($parsed['document_id']) ? (string) $parsed['document_id'] : (string) $raw;
+ return array(
+ 'document_id' => isset($parsed['document_id']) ? (string) $parsed['document_id'] : (string) $raw,
+ );
}
if (in_array($kind, array('lines', 'relation_list'), true)) {
diff --git a/adminx/modules/Documents/assets/documents.css b/adminx/modules/Documents/assets/documents.css
index c0a4abf..787a282 100644
--- a/adminx/modules/Documents/assets/documents.css
+++ b/adminx/modules/Documents/assets/documents.css
@@ -571,6 +571,40 @@
text-overflow: ellipsis;
white-space: nowrap;
}
+.documents-public-link {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ min-width: 0;
+ max-width: 100%;
+ width: fit-content;
+ color: var(--blue-600);
+ font-size: 11.5px;
+ line-height: 1.35;
+ text-decoration: none;
+}
+.documents-public-link i {
+ flex: 0 0 auto;
+ color: var(--blue-600);
+ font-size: 13px;
+}
+.documents-public-link span {
+ overflow: hidden;
+ min-width: 0;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.documents-public-link:hover,
+.documents-public-link:focus-visible {
+ color: var(--blue-700);
+}
+.documents-public-alias-empty {
+ color: var(--text-muted);
+ font-size: 11.5px;
+}
+.documents-admin-card-title .documents-public-link {
+ margin-top: 3px;
+}
.documents-search-flag {
display: inline-flex;
align-items: center;
@@ -1462,13 +1496,15 @@
.documents-editor-workspace[data-active-panel="attributes"] #documentForm,
.documents-editor-workspace[data-active-panel="promotions"] #documentForm,
.documents-editor-workspace[data-active-panel="variants"] #documentForm,
-.documents-editor-workspace[data-active-panel="shipping"] #documentForm {
+.documents-editor-workspace[data-active-panel="shipping"] #documentForm,
+.documents-editor-workspace[data-active-panel="search"] #documentForm {
display: none;
}
.documents-editor-workspace[data-active-panel="attributes"] [data-document-workspace-panel="attributes"],
.documents-editor-workspace[data-active-panel="promotions"] [data-document-workspace-panel="promotions"],
.documents-editor-workspace[data-active-panel="variants"] [data-document-workspace-panel="variants"],
-.documents-editor-workspace[data-active-panel="shipping"] [data-document-workspace-panel="shipping"] {
+.documents-editor-workspace[data-active-panel="shipping"] [data-document-workspace-panel="shipping"],
+.documents-editor-workspace[data-active-panel="search"] [data-document-workspace-panel="search"] {
display: block;
}
.documents-editor-workspace [data-document-workspace-panel] {
diff --git a/adminx/modules/Documents/assets/documents.js b/adminx/modules/Documents/assets/documents.js
index b2c6d6a..2eb4160 100644
--- a/adminx/modules/Documents/assets/documents.js
+++ b/adminx/modules/Documents/assets/documents.js
@@ -1124,6 +1124,13 @@
documentFieldConditionContext: function () {
var context = {};
+ this.form.querySelectorAll('[data-document-condition-source]').forEach(function (source) {
+ var id = String(parseInt(source.getAttribute('data-field-id'), 10) || 0);
+ var alias = String(source.getAttribute('data-field-alias') || '').trim().toLowerCase();
+ var values = [String(source.value == null ? '' : source.value)];
+ context['id:' + id] = values;
+ if (alias) { context['alias:' + alias] = values; }
+ });
this.form.querySelectorAll('.ax-document-field[data-rubric-field-id]').forEach(function (field) {
var values = [];
var controls = Array.prototype.slice.call(field.querySelectorAll('input[name], select[name], textarea[name]'));
@@ -1349,9 +1356,24 @@
searchCatalog: function (catalog, value) {
if (!catalog) { return; }
- value = String(value || '').trim().toLowerCase();
- catalog.querySelectorAll('[data-document-catalog-node]').forEach(function (node) {
- node.classList.toggle('is-filtered', value && String(node.getAttribute('data-search') || '').toLowerCase().indexOf(value) < 0);
+ var normalize = function (input) {
+ return String(input || '').toLowerCase().replace(/ё/g, 'е').replace(/[^0-9a-zа-я]+/gi, ' ').trim();
+ };
+ var query = normalize(value);
+ var tokens = query ? query.split(/\s+/) : [];
+ var nodes = Array.prototype.slice.call(catalog.querySelectorAll('[data-document-catalog-node]'));
+ nodes.forEach(function (node) {
+ var haystack = normalize(node.getAttribute('data-search'));
+ var matches = !tokens.length || tokens.every(function (token) { return haystack.indexOf(token) >= 0; });
+ node.classList.toggle('is-filtered', !matches);
+ });
+ nodes.forEach(function (node) {
+ if (node.classList.contains('is-filtered')) { return; }
+ var parent = node.parentElement ? node.parentElement.closest('[data-document-catalog-node]') : null;
+ while (parent) {
+ parent.classList.remove('is-filtered');
+ parent = parent.parentElement ? parent.parentElement.closest('[data-document-catalog-node]') : null;
+ }
});
},
@@ -1496,7 +1518,7 @@
saveActiveWorkspace: function () {
var workspace = document.querySelector('[data-document-workspace]');
var panel = workspace ? workspace.getAttribute('data-active-panel') || 'main' : 'main';
- if (panel === 'attributes' || panel === 'shipping' || panel === 'promotions') {
+ if (panel === 'attributes' || panel === 'shipping' || panel === 'promotions' || panel === 'search') {
var form = workspace.querySelector('[data-document-workspace-panel="' + panel + '"] form');
if (form && typeof form.requestSubmit === 'function') { form.requestSubmit(); }
else if (form) { form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true })); }
@@ -1772,6 +1794,16 @@
if (textarea._adminxCodeMirror) { textarea._adminxCodeMirror.save(); }
});
this.form.querySelectorAll('textarea[data-rich-editor]').forEach(function (textarea) {
+ if (window.Adminx && Adminx.RichEditor && typeof Adminx.RichEditor.syncTextarea === 'function') {
+ Adminx.RichEditor.syncTextarea(textarea, textarea._adminxTiptap || null);
+ return;
+ }
+ var root = textarea.closest('.rich-editor');
+ if (root && root.classList.contains('rich-editor-source-open') && root._richSourceEditor) {
+ root._richSourceEditor.save();
+ textarea.value = root._richSourceEditor.getValue();
+ return;
+ }
if (textarea._adminxTiptap && textarea.getAttribute('data-rich-editor-dirty') === '1') {
textarea.value = textarea._adminxTiptap.getHTML();
}
@@ -2467,7 +2499,11 @@
applyPickedMedia: function (target, file) {
var input = target ? target.querySelector('[data-document-media-url]') : null;
- if (input && file && file.url) { input.value = file.url; input.dispatchEvent(new Event('input', { bubbles: true })); }
+ if (input && file && file.url) {
+ input.value = file.url;
+ input.dispatchEvent(new Event('input', { bubbles: true }));
+ this.sortMediaRowsByName(target.closest('[data-document-media-list]'));
+ }
},
mediaPickerType: function (target) {
@@ -2507,25 +2543,40 @@
uploadMediaFiles: function (container, files, replace) {
if (!container || !files || !files.length) { return; }
- var data = new FormData();
- data.append('_csrf', this.csrf());
- data.append('dir', container.getAttribute('data-upload-dir') || '/uploads');
- data.append('field_id', container.getAttribute('data-field-id') || '0');
- data.append('document_id', (this.field('id') || {}).value || '0');
- data.append('media_draft_token', (this.field('media_draft_token') || {}).value || '');
- Array.prototype.forEach.call(files, function (file) { data.append('files[]', file); });
var self = this;
- this.ajax(container.getAttribute('data-upload-url') || (this.base() + '/media/upload'), data, function (payload) {
- var uploaded = ((payload.data || {}).files) || [];
- if (replace && uploaded.length) {
- self.prepareMediaReplacement(container);
+ var buttons = container.querySelectorAll('[data-document-media-upload]');
+ buttons.forEach(function (button) { button.disabled = true; });
+ Adminx.Loader.show();
+ Adminx.Upload.files(
+ container.getAttribute('data-upload-url') || (this.base() + '/media/upload'),
+ files,
+ function (chunk) {
+ var data = new FormData();
+ data.append('_csrf', self.csrf());
+ data.append('dir', container.getAttribute('data-upload-dir') || '/uploads');
+ data.append('field_id', container.getAttribute('data-field-id') || '0');
+ data.append('document_id', (self.field('id') || {}).value || '0');
+ data.append('media_draft_token', (self.field('media_draft_token') || {}).value || '');
+ chunk.forEach(function (file) { data.append('files[]', file); });
+ return data;
}
+ ).then(function (result) {
+ var uploaded = result.files || [];
+ if (replace && uploaded.length) { self.prepareMediaReplacement(container); }
if (container.matches('[data-document-media-single]')) {
self.applyUploadedSingle(container, uploaded[0] || null);
} else {
self.appendMediaFiles(container, uploaded);
}
- Adminx.Toast.show(payload.message || 'Файлы загружены', 'success');
+ Adminx.Toast.show('Загружено файлов: ' + uploaded.length, 'success');
+ }).catch(function (error) {
+ var uploaded = error && error.uploaded ? error.uploaded.length : 0;
+ var message = (error && error.message) || 'Не удалось загрузить файлы';
+ if (uploaded) { message += '. До ошибки загружено: ' + uploaded; }
+ Adminx.Toast.show(message, 'error');
+ }).finally(function () {
+ Adminx.Loader.hide();
+ buttons.forEach(function (button) { button.disabled = false; });
});
},
@@ -2606,10 +2657,43 @@
seen[url] = true;
added++;
});
+ if (added) { this.sortMediaRowsByName(list); }
this.updateMediaListEmpty(list);
if (!added && files.length) { Adminx.Toast.show('Эти файлы уже есть в поле', 'info'); }
},
+ sortMediaRowsByName: function (list) {
+ if (!list || list.getAttribute('data-media-accept') !== 'image') { return; }
+ var box = list.querySelector('[data-document-media-items]');
+ if (!box) { return; }
+ var rows = Array.prototype.slice.call(box.querySelectorAll('[data-document-media-row]'));
+ if (rows.length < 2) { return; }
+ var self = this;
+ var collator = window.Intl && window.Intl.Collator
+ ? new window.Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })
+ : null;
+ rows.sort(function (left, right) {
+ var leftName = self.mediaRowFileName(left);
+ var rightName = self.mediaRowFileName(right);
+ if (!leftName && !rightName) { return 0; }
+ if (!leftName) { return 1; }
+ if (!rightName) { return -1; }
+ return collator ? collator.compare(leftName, rightName) : leftName.localeCompare(rightName);
+ });
+ rows.forEach(function (row) { box.appendChild(row); });
+ this.reindexMediaList(list);
+ },
+
+ mediaRowFileName: function (row) {
+ var input = row ? row.querySelector('[data-media-key="url"]') : null;
+ var value = input ? String(input.value || '').trim() : '';
+ if (!value) { return ''; }
+ value = value.split(/[?#]/, 1)[0].replace(/\\/g, '/');
+ var name = value.slice(value.lastIndexOf('/') + 1);
+ try { name = decodeURIComponent(name); } catch (e) {}
+ return name;
+ },
+
createMediaRow: function (list) {
this.addMediaRow(list);
var rows = list.querySelectorAll('[data-document-media-row]');
@@ -3016,11 +3100,15 @@
var rubricEl = target ? (target.querySelector && target.querySelector('[data-document-picker-rubric]')) : null;
if (!rubricEl && target && target.matches && target.matches('[data-document-picker-rubric]')) { rubricEl = target; }
var rubric = rubricEl ? (rubricEl.getAttribute('data-document-picker-rubric') || '') : '';
+ var scope = target && target.getAttribute ? (target.getAttribute('data-document-picker-scope') || '') : '';
+ var excludeId = target && target.getAttribute ? (target.getAttribute('data-document-picker-exclude') || '') : '';
var load = function () {
var params = new URLSearchParams();
params.set('q', search.value.trim());
params.set('limit', 20);
if (rubric) { params.set('rubric_id', rubric); }
+ if (scope) { params.set('scope', scope); }
+ if (excludeId) { params.set('exclude_id', excludeId); }
status.textContent = 'Загрузка...';
status.hidden = false;
fetch(self.base() + '/documents/picker?' + params.toString(), { headers: { 'Accept': 'application/json' }, credentials: 'same-origin' })
diff --git a/adminx/modules/Documents/language/en/interface.xml b/adminx/modules/Documents/language/en/interface.xml
index f055a43..e3350ea 100644
--- a/adminx/modules/Documents/language/en/interface.xml
+++ b/adminx/modules/Documents/language/en/interface.xml
@@ -493,6 +493,8 @@
Refresh page
Published
Published
+
Open on site
+
Open page in a new tab
Open the desired folder and confirm your selection. All suitable files from it will be added to the field.
Cancel
Cancel change
diff --git a/adminx/modules/Documents/language/ru/interface.xml b/adminx/modules/Documents/language/ru/interface.xml
index 003f43d..248bdc7 100644
--- a/adminx/modules/Documents/language/ru/interface.xml
+++ b/adminx/modules/Documents/language/ru/interface.xml
@@ -493,6 +493,8 @@
Обновить страницу
Опубликован
Опубликованные
+
Открыть на сайте
+
Открыть страницу в новой вкладке
Откройте нужную папку и подтвердите выбор. В поле добавятся все подходящие файлы из неё.
Отменить
Отменить изменение
diff --git a/adminx/modules/Documents/module.php b/adminx/modules/Documents/module.php
index 45ba32d..89191c7 100644
--- a/adminx/modules/Documents/module.php
+++ b/adminx/modules/Documents/module.php
@@ -17,7 +17,7 @@
return array(
'code' => 'documents',
'name' => 'Документы',
- 'version' => '0.3.3',
+ 'version' => '0.3.9',
'permissions' => array(
'key' => 'documents',
diff --git a/adminx/modules/Documents/view/_product_panels.twig b/adminx/modules/Documents/view/_product_panels.twig
index 08368c2..7e7d5a9 100644
--- a/adminx/modules/Documents/view/_product_panels.twig
+++ b/adminx/modules/Documents/view/_product_panels.twig
@@ -1,3 +1,26 @@
+{% if catalog_mode and not is_new and product_search_aliases_available %}
+
+{% endif %}
+
{% if catalog_mode and not is_new %}
@@ -30,6 +30,7 @@
Характеристики
Варианты
Доставка
+ {% if product_search_aliases_available %} Поиск {% endif %}
{% endif %}
Дополнительно
@@ -41,8 +42,9 @@
Акции{{ is_new ? ' · после сохранения' : '' }}
Характеристики{{ is_new ? ' · после сохранения' : '' }}
Варианты{{ is_new ? ' · после сохранения' : '' }}
- Доставка{{ is_new ? ' · после сохранения' : '' }}
- {% endif %}
+ Доставка{{ is_new ? ' · после сохранения' : '' }}
+ {% if product_search_aliases_available %}Поиск {% endif %}
+ {% endif %}
Дополнительно
@@ -71,6 +73,11 @@
+ {% if field_groups %}
+ {% for source in field_groups[0].condition_sources|default([]) %}
+
+ {% endfor %}
+ {% endif %}
@@ -298,7 +305,7 @@
Автор {% for author in authors %}{{ author.name }}{% if not author.active %} · отключён{% endif %} {% endfor %}
Родительский документ
-
{% endmacro %}
+{% macro public_link(item) %}
+ {% if item.document_alias %}
+ {% set public_url = item.document_alias == '/' ? '/' : '/' ~ item.document_alias|trim('/') %}
+
{{ item.document_alias }}
+ {% else %}
+
без alias
+ {% endif %}
+{% endmacro %}
{% import _self as document_ui %}
{% block title %}Документы{% endblock %}
{% block content %}
@@ -136,7 +144,7 @@
@@ -172,7 +180,7 @@
{% if can_manage %} {% endif %}
{{ item.Id }}
- {{ item.document_title }} {{ item.document_alias ? item.document_alias : 'без alias' }}
+ {{ item.document_title }} {{ document_ui.public_link(item) }}
{% for display in item.admin_view_values %}
{% if display.format == 'image' %}{% if display.value.image %} {% else %}— {% endif %}
@@ -218,7 +226,7 @@
{{ item.document_title }}
- {{ item.document_count_view|number_format(0, '.', ' ') }} просмотров {{ item.document_alias ? item.document_alias : 'без alias' }}
+ {{ item.document_count_view|number_format(0, '.', ' ') }} просмотров{{ document_ui.public_link(item) }}
diff --git a/adminx/modules/Media/Controller.php b/adminx/modules/Media/Controller.php
index 00064d5..1ea3176 100644
--- a/adminx/modules/Media/Controller.php
+++ b/adminx/modules/Media/Controller.php
@@ -197,6 +197,7 @@
'pages' => $listing['pages'],
'total' => $listing['total'],
'per_page' => $perPage,
+ 'can_manage' => Permission::check('manage_media'),
),
));
}
@@ -278,6 +279,7 @@
}
return $this->success('Папка создана', array(
+ 'data' => array('path' => $path),
'redirect' => $this->base() . '/media?dir=' . rawurlencode($path),
));
}
diff --git a/adminx/modules/Media/assets/media.js b/adminx/modules/Media/assets/media.js
index ac8c891..a4bc58f 100644
--- a/adminx/modules/Media/assets/media.js
+++ b/adminx/modules/Media/assets/media.js
@@ -446,32 +446,32 @@
}
function upload(files) {
- var fd;
- var i;
if (!files || !files.length) {
setStatus('Файлы не выбраны', 'error');
return;
}
- fd = new FormData(form);
- fd.delete('files[]');
- for (i = 0; i < files.length; i++) {
- fd.append('files[]', files[i]);
- }
+ var total = files.length;
form.classList.add('is-loading');
- setStatus('Загрузка: ' + files.length, 'progress');
- Adminx.Ajax.post(self.base() + '/media/upload', fd).then(function (payload) {
+ setStatus('Подготовка к загрузке: ' + total, 'progress');
+ Adminx.Upload.files(self.base() + '/media/upload', files, function (chunk) {
+ var fd = new FormData(form);
+ fd.delete('files[]');
+ chunk.forEach(function (file) { fd.append('files[]', file); });
+ return fd;
+ }, function (processed, count) {
+ setStatus('Загружено: ' + processed + ' из ' + count, 'progress');
+ }).then(function (result) {
form.classList.remove('is-loading');
- var d = payload.data || {};
- if (d.success) {
- setStatus(d.message || 'Готово', 'success');
- Adminx.Ajax.handle(payload);
- } else {
- setStatus(d.message || 'Ошибка загрузки', 'error');
- Adminx.Toast.show(d.message || 'Ошибка загрузки', 'error');
- }
- }).catch(function () {
+ setStatus('Загружено файлов: ' + result.files.length, 'success');
+ Adminx.Toast.show('Загружено файлов: ' + result.files.length, 'success');
+ window.location.href = self.base() + '/media?dir=' + encodeURIComponent(form.getAttribute('data-dir') || '/uploads');
+ }).catch(function (error) {
form.classList.remove('is-loading');
- setStatus('Ошибка сети', 'error');
+ var uploaded = error && error.uploaded ? error.uploaded.length : 0;
+ var message = (error && error.message) || 'Ошибка сети';
+ if (uploaded) { message += '. До ошибки загружено: ' + uploaded; }
+ setStatus(message, 'error');
+ Adminx.Toast.show(message, 'error');
});
}
diff --git a/adminx/modules/Media/module.php b/adminx/modules/Media/module.php
index dcea04f..ae64a44 100644
--- a/adminx/modules/Media/module.php
+++ b/adminx/modules/Media/module.php
@@ -19,7 +19,7 @@
return array(
'code' => 'media',
'name' => 'Медиа',
- 'version' => '0.6.2',
+ 'version' => '0.6.4',
'permissions' => array(
'key' => 'media',
diff --git a/adminx/modules/Navigation/Controller.php b/adminx/modules/Navigation/Controller.php
index 3644cbb..f45cef3 100644
--- a/adminx/modules/Navigation/Controller.php
+++ b/adminx/modules/Navigation/Controller.php
@@ -17,10 +17,12 @@
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Adminx\Support\CodeEditor;
+ use App\Adminx\Support\AdminLocale;
use App\Common\AdminAssets;
use App\Common\Auth;
use App\Common\Controller as BaseController;
use App\Common\Permission;
+ use App\Helpers\Hooks;
use App\Helpers\Request;
class Controller extends BaseController
@@ -32,12 +34,22 @@
CodeEditor::useCodeMirror('htmlmixed');
$q = Request::getStr('q', '');
+ $panelSources = Hooks::filter('navigation.item.panel_sources', array());
+ if (!is_array($panelSources)) { $panelSources = array(); }
+ $panelSources = array_values(array_filter(array_map(function ($source) {
+ if (!is_array($source)) { return null; }
+ $code = strtolower(trim(isset($source['code']) ? (string) $source['code'] : ''));
+ $title = trim(isset($source['title']) ? (string) $source['title'] : '');
+ if (!preg_match('/^[a-z][a-z0-9_-]{1,31}$/', $code) || $title === '') { return null; }
+ return array('code' => $code, 'title' => AdminLocale::translateMarkup($title));
+ }, $panelSources)));
return $this->render('@navigation/index.twig', array(
'navigation_items' => Model::all($q),
'stats' => Model::stats(),
'filters' => array('q' => $q),
'access_groups' => Model::accessGroups(),
+ 'navigation_panel_sources' => $panelSources,
'can_manage' => Permission::check('manage_navigation'),
));
}
diff --git a/adminx/modules/Navigation/Model.php b/adminx/modules/Navigation/Model.php
index 751d2f2..92f27ba 100644
--- a/adminx/modules/Navigation/Model.php
+++ b/adminx/modules/Navigation/Model.php
@@ -16,6 +16,7 @@
defined('BASEPATH') || die('Direct access to this location is not allowed.');
+ use App\Common\FileCacheInvalidator;
use App\Content\ContentTables;
use App\Content\PublicUserTables;
use App\Helpers\Dir;
@@ -234,6 +235,10 @@
}
$navigationId = (int) $current['navigation_id'];
+ if (!array_key_exists('status', $input)) {
+ $data['status'] = (string) $current['status'];
+ }
+
self::assertItemParent($navigationId, (int) $id, (int) $data['parent_id']);
$data['navigation_id'] = $navigationId;
DB::startTransaction();
@@ -406,6 +411,7 @@
public static function clearCache($id, $alias = '')
{
+ $id = (int) $id;
$keys = array((string) (int) $id);
$alias = trim((string) $alias);
if ($alias !== '') {
@@ -417,6 +423,8 @@
self::removeFile(self::cacheDir($key) . '/items.cache');
self::removeDir(self::cacheDir($key));
}
+
+ FileCacheInvalidator::navigation($id, $alias);
}
protected static function input(array $input)
@@ -485,6 +493,11 @@
}
$parentId = isset($input['parent_id']) ? (int) $input['parent_id'] : 0;
+ $cssClass = isset($input['css_class']) ? (string) $input['css_class'] : '';
+ if (array_key_exists('panel_source', $input)) {
+ $cssClass = self::applyPanelSource($cssClass, (string) $input['panel_source']);
+ }
+
return array(
'document_id' => isset($input['document_id']) && (int) $input['document_id'] > 0 ? (int) $input['document_id'] : null,
'alias' => $alias,
@@ -494,7 +507,7 @@
'image' => isset($input['image']) ? (string) $input['image'] : '',
'css_style' => isset($input['css_style']) ? (string) $input['css_style'] : '',
'css_id' => isset($input['css_id']) ? (string) $input['css_id'] : '',
- 'css_class' => isset($input['css_class']) ? (string) $input['css_class'] : '',
+ 'css_class' => $cssClass,
'parent_id' => $parentId,
'level' => '1',
'position' => isset($input['position']) ? (int) $input['position'] : 1,
@@ -502,6 +515,25 @@
);
}
+ protected static function applyPanelSource($cssClass, $source)
+ {
+ $classes = preg_split('/\s+/', trim((string) $cssClass)) ?: array();
+ $classes = array_values(array_filter($classes, function ($class) {
+ return $class !== 'catalog-menu-link'
+ && $class !== 'catalog-menu-panel'
+ && strpos($class, 'catalog-menu-source-') !== 0;
+ }));
+
+ $source = strtolower(trim((string) $source));
+ if ($source === 'children') {
+ $classes[] = 'catalog-menu-panel';
+ } elseif (preg_match('/^[a-z][a-z0-9_-]{1,31}$/', $source)) {
+ $classes[] = 'catalog-menu-source-' . $source;
+ }
+
+ return implode(' ', array_values(array_unique(array_filter($classes))));
+ }
+
protected static function row(array $row, $withCode = false)
{
$id = (int) $row['navigation_id'];
@@ -530,6 +562,7 @@
$row['id'] = $row['navigation_item_id'];
$row['navigation_id'] = isset($row['navigation_id']) ? (int) $row['navigation_id'] : 0;
$row['document_id'] = isset($row['document_id']) ? (int) $row['document_id'] : 0;
+ $row['panel_source'] = self::panelSource(isset($row['css_class']) ? $row['css_class'] : '');
$row['parent_id'] = isset($row['parent_id']) ? (int) $row['parent_id'] : 0;
$row['level'] = isset($row['level']) ? (string) $row['level'] : '1';
$row['position'] = isset($row['position']) ? (int) $row['position'] : 1;
@@ -541,6 +574,16 @@
return $row;
}
+ protected static function panelSource($cssClass)
+ {
+ $cssClass = trim((string) $cssClass);
+ if (preg_match('/(?:^|\s)catalog-menu-source-([a-z][a-z0-9_-]{1,31})(?:\s|$)/', $cssClass, $match)) {
+ return (string) $match[1];
+ }
+
+ return preg_match('/(?:^|\s)catalog-menu-panel(?:\s|$)/', $cssClass) ? 'children' : 'link';
+ }
+
protected static function decode($value)
{
return html_entity_decode((string) $value, ENT_QUOTES, 'UTF-8');
diff --git a/adminx/modules/Navigation/language/en/interface.xml b/adminx/modules/Navigation/language/en/interface.xml
index 25af45b..d3a1c9c 100644
--- a/adminx/modules/Navigation/language/en/interface.xml
+++ b/adminx/modules/Navigation/language/en/interface.xml
@@ -1,5 +1,9 @@
+ Catalog menu behavior
+ Regular link
+ Show child items on the right
+ Used only by navigation connected to the product catalog.
[tag:if_every:2]even[tag:if_else]odd[tag:/if]
, and normal and active items are elements
, start and end of level - list
diff --git a/adminx/modules/Navigation/language/ru/interface.xml b/adminx/modules/Navigation/language/ru/interface.xml
index d8447f7..80c4a88 100644
--- a/adminx/modules/Navigation/language/ru/interface.xml
+++ b/adminx/modules/Navigation/language/ru/interface.xml
@@ -1,5 +1,9 @@
+ Поведение в меню каталога
+ Обычная ссылка
+ Показать справа дочерние пункты
+ Используется только навигацией, подключённой к товарному каталогу.
[tag:if_every:2]чётный[tag:if_else]нечётный[tag:/if]
, а обычный и активный пункты — элементы
, начало и конец уровня — список
diff --git a/adminx/modules/Navigation/module.php b/adminx/modules/Navigation/module.php
index fda014b..089fbe3 100644
--- a/adminx/modules/Navigation/module.php
+++ b/adminx/modules/Navigation/module.php
@@ -17,7 +17,14 @@
return array(
'code' => 'navigation',
'name' => 'Навигация',
- 'version' => '0.2.0',
+ 'version' => '0.2.2',
+
+ 'hook_definitions' => array(
+ array(
+ 'name' => 'navigation.item.panel_sources',
+ 'description' => 'Дополнительные источники правой панели для пункта навигации товарного каталога.',
+ ),
+ ),
'permissions' => array(
'key' => 'navigation',
diff --git a/adminx/modules/Navigation/view/index.twig b/adminx/modules/Navigation/view/index.twig
index 16a2175..a048100 100644
--- a/adminx/modules/Navigation/view/index.twig
+++ b/adminx/modules/Navigation/view/index.twig
@@ -391,6 +391,17 @@
CSS style
CSS class
CSS ID
+ {% if navigation_panel_sources %}
+
+ Поведение в меню каталога
+
+ Обычная ссылка
+ Показать справа дочерние пункты
+ {% for source in navigation_panel_sources %}{{ source.title }} {% endfor %}
+
+ Используется только навигацией, подключённой к товарному каталогу.
+
+ {% endif %}
diff --git a/adminx/modules/PublicSite/Controller.php b/adminx/modules/PublicSite/Controller.php
index 0f715bf..0975d29 100644
--- a/adminx/modules/PublicSite/Controller.php
+++ b/adminx/modules/PublicSite/Controller.php
@@ -46,6 +46,18 @@
));
}
+ public function templateMap(array $params = array())
+ {
+ if (!Permission::check('view_public_site')) {
+ return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403);
+ }
+
+ AdminAssets::addStyle($this->base() . '/modules/PublicSite/assets/public-site.css', 50);
+ return $this->render('@public_site/template-map.twig', array(
+ 'template_map' => TemplateMap::build(),
+ ));
+ }
+
public function placements(array $params = array())
{
if (!Permission::check('view_public_site')) {
@@ -217,6 +229,7 @@
protected function publicTemplateAssets()
{
CodeEditor::useCodeMirror('htmlmixed');
+ AdminAssets::addStyle($this->base() . '/modules/PublicSite/assets/public-site.css', 50);
AdminAssets::addStyle(ADMINX_BASE . '/modules/Themes/assets/view-overrides.css', 56);
AdminAssets::addScript(ADMINX_BASE . '/modules/Themes/assets/view-overrides.js', 56);
}
diff --git a/adminx/modules/PublicSite/TemplateMap.php b/adminx/modules/PublicSite/TemplateMap.php
new file mode 100644
index 0000000..0e579bf
--- /dev/null
+++ b/adminx/modules/PublicSite/TemplateMap.php
@@ -0,0 +1,317 @@
+
+ | @copyright 2007-2026 (c) AVE.cms
+ | @link https://ave-cms.ru
+ | @version 3.3
+ */
+
+ namespace App\Adminx\PublicSite;
+
+ defined('BASEPATH') || die('Direct access to this location is not allowed.');
+
+ use App\Common\DatabaseSchema;
+ use App\Content\ContentTables;
+ use App\Frontend\ThemeAssets;
+ use DB;
+
+ /** Read-only description of the active public template chain. */
+ class TemplateMap
+ {
+ public static function build()
+ {
+ $theme = self::theme();
+ $siteTemplates = self::siteTemplates();
+ $rubrics = self::rubrics();
+ $components = self::components();
+ $product = self::productLayer();
+
+ return array(
+ 'theme' => $theme,
+ 'summary' => array(
+ 'site_templates' => count($siteTemplates),
+ 'rubrics' => count($rubrics),
+ 'components' => $components['count'],
+ 'active_overrides' => $components['active_count'],
+ ),
+ 'rules' => self::rules($theme),
+ 'scenarios' => self::scenarios($theme, $components, $product),
+ 'site_templates' => $siteTemplates,
+ 'rubrics' => $rubrics,
+ 'component_groups' => $components['groups'],
+ 'product' => $product,
+ );
+ }
+
+ protected static function theme()
+ {
+ $code = ThemeAssets::currentTheme();
+ $manifest = ThemeAssets::manifest($code);
+ $mode = isset($manifest['presentation_mode']) && $manifest['presentation_mode'] === 'theme'
+ ? 'theme' : 'native';
+ $pageShell = isset($manifest['page_shell']) ? trim((string) $manifest['page_shell']) : '';
+
+ return array(
+ 'code' => $code,
+ 'name' => isset($manifest['name']) && trim((string) $manifest['name']) !== ''
+ ? (string) $manifest['name'] : $code,
+ 'version' => isset($manifest['version']) ? (string) $manifest['version'] : '',
+ 'mode' => $mode,
+ 'mode_label' => $mode === 'theme' ? 'Оболочка темы' : 'Штатные шаблоны AVE.cms',
+ 'mode_note' => $mode === 'theme'
+ ? 'Тема формирует внешний каркас страницы; наследование шаблонов рубрики зависит от настроек темы.'
+ : 'Общий шаблон сайта и шаблон рубрики собирают страницу. Тема подключает стили и переоформляет отдельные компоненты.',
+ 'page_shell' => $pageShell,
+ 'page_shell_active' => $mode === 'theme' && $pageShell !== '',
+ 'inherit_rubric_templates' => !empty($manifest['inherit_rubric_templates']),
+ 'overrides' => isset($manifest['view_overrides']) && is_array($manifest['view_overrides'])
+ ? array_values($manifest['view_overrides']) : array(),
+ 'url' => '/themes?theme=' . rawurlencode($code),
+ );
+ }
+
+ protected static function siteTemplates()
+ {
+ $table = ContentTables::table('templates');
+ $rubrics = ContentTables::table('rubrics');
+ if (!self::tableExists($table) || !self::tableExists($rubrics)) { return array(); }
+
+ $rows = DB::query(
+ 'SELECT t.Id id,t.template_title title,COUNT(r.Id) usage_count'
+ . ' FROM ' . $table . ' t LEFT JOIN ' . $rubrics . ' r ON r.rubric_template_id=t.Id'
+ . ' GROUP BY t.Id,t.template_title ORDER BY usage_count DESC,t.template_title,t.Id'
+ )->getAll() ?: array();
+ $out = array();
+ foreach ($rows as $row) {
+ $out[] = array(
+ 'id' => (int) $row['id'],
+ 'title' => self::decode($row['title']),
+ 'usage_count' => (int) $row['usage_count'],
+ 'url' => '/templates/' . (int) $row['id'],
+ );
+ }
+
+ return $out;
+ }
+
+ protected static function rubrics()
+ {
+ $out = array();
+ foreach (Model::structure() as $row) {
+ $purpose = 'Обычные документы';
+ if (!empty($row['catalogs_count'])) {
+ $purpose = 'Каталог';
+ } elseif (!empty($row['requests_count'])) {
+ $purpose = 'Документы и подборки';
+ }
+
+ $out[] = array(
+ 'id' => (int) $row['id'],
+ 'title' => (string) $row['title'],
+ 'alias' => (string) $row['alias'],
+ 'purpose' => $purpose,
+ 'template_id' => (int) $row['template_id'],
+ 'template_title' => (string) $row['template_title'],
+ 'has_main_template' => (int) $row['template_length'] > 0,
+ 'extra_templates_count' => (int) $row['rubric_templates_count'],
+ 'documents_count' => (int) $row['documents_count'],
+ 'url' => '/rubrics/' . (int) $row['id'] . '/templates',
+ );
+ }
+
+ return $out;
+ }
+
+ protected static function components()
+ {
+ $items = array();
+ try {
+ foreach (PublicViewTemplates::all() as $item) {
+ $items[] = self::component($item, 'site', '/public-site/templates');
+ }
+ } catch (\Throwable $e) {
+ // A broken optional theme must not make the read-only map unavailable.
+ }
+
+ $productClass = '\\App\\Adminx\\Packages\\Products\\PublicViewTemplates';
+ if (class_exists($productClass) && method_exists($productClass, 'all')) {
+ try {
+ foreach ($productClass::all() as $item) {
+ $items[] = self::component($item, 'products', '/catalog/public-templates');
+ }
+ } catch (\Throwable $e) {
+ // The products module can be installed without its latest schema during an update.
+ }
+ }
+
+ $groups = array();
+ $active = 0;
+ foreach ($items as $item) {
+ $group = $item['group'] !== '' ? $item['group'] : 'Другие компоненты';
+ if (!isset($groups[$group])) {
+ $groups[$group] = array('title' => $group, 'items' => array());
+ }
+
+ $groups[$group]['items'][] = $item;
+ $active += $item['active'] ? 1 : 0;
+ }
+
+ return array('count' => count($items), 'active_count' => $active, 'items' => $items, 'groups' => array_values($groups));
+ }
+
+ protected static function component(array $item, $owner, $route)
+ {
+ $source = isset($item['source']) ? (string) $item['source'] : 'missing';
+ return array(
+ 'code' => isset($item['code']) ? (string) $item['code'] : '',
+ 'namespace' => isset($item['namespace']) ? (string) $item['namespace'] : '',
+ 'file' => isset($item['file']) ? (string) $item['file'] : '',
+ 'path' => isset($item['theme_path']) ? (string) $item['theme_path'] : '',
+ 'group' => isset($item['group']) ? (string) $item['group'] : '',
+ 'title' => isset($item['title']) ? (string) $item['title'] : '',
+ 'description' => isset($item['description']) ? (string) $item['description'] : '',
+ 'icon' => isset($item['icon']) ? (string) $item['icon'] : 'ti ti-template',
+ 'owner' => $owner,
+ 'active' => !empty($item['active_override']) || $source === 'fallback',
+ 'source' => $source,
+ 'source_label' => isset($item['source_label']) ? (string) $item['source_label'] : 'Не определён',
+ 'state_class' => $source === 'theme' ? 'badge-green' : ($source === 'fallback' ? 'badge-blue' : 'badge-amber'),
+ 'url' => $route . '?template=' . rawurlencode(isset($item['code']) ? (string) $item['code'] : ''),
+ );
+ }
+
+ protected static function productLayer()
+ {
+ $result = array(
+ 'available' => false,
+ 'cards' => array('total' => 0, 'published' => 0, 'used' => 0, 'url' => '/catalog/card-templates'),
+ 'filters' => array('total' => 0, 'published' => 0, 'used' => 0, 'url' => '/catalog/filter-templates'),
+ );
+ $classes = array(
+ 'cards' => '\\App\\Adminx\\Packages\\Products\\CardTemplates',
+ 'filters' => '\\App\\Adminx\\Packages\\Products\\FilterTemplates',
+ );
+ foreach ($classes as $key => $class) {
+ if (!class_exists($class) || !method_exists($class, 'stats')) { continue; }
+ try {
+ $stats = $class::stats();
+ $result[$key] = array_merge($result[$key], is_array($stats) ? $stats : array());
+ $result['available'] = true;
+ } catch (\Throwable $e) {
+ // Keep the map usable while an optional module waits for migrations.
+ }
+ }
+
+ return $result;
+ }
+
+ protected static function rules(array $theme)
+ {
+ return array(
+ array('icon' => 'ti ti-database', 'title' => 'Данные остаются в AVE.cms', 'text' => 'Документы, поля, рубрики и условия запросов не переносятся в файлы темы.'),
+ array('icon' => 'ti ti-template', 'title' => 'Шаблон задаёт место', 'text' => 'Общий шаблон создаёт каркас, а шаблон рубрики размещает содержимое конкретного типа документа.'),
+ array('icon' => 'ti ti-palette', 'title' => 'Тема оформляет результат', 'text' => $theme['mode'] === 'native'
+ ? 'Сейчас тема подключает стили и заменяет только перечисленные публичные компоненты.'
+ : 'Сейчас активна оболочка темы; её файл формирует внешний каркас страницы.'),
+ );
+ }
+
+ protected static function scenarios(array $theme, array $components, array $product)
+ {
+ $componentItems = isset($components['items']) ? $components['items'] : array();
+ $productUrl = $product['available'] ? '/catalog/products' : '/modules';
+ $productCardsUrl = $product['available'] ? '/catalog/card-templates' : '/modules';
+ return array(
+ array(
+ 'icon' => 'ti ti-browser', 'title' => 'Общий каркас сайта',
+ 'description' => 'Шапка, подвал и место, куда AVE.cms вставляет содержимое текущего документа.',
+ 'steps' => array(
+ self::step('Шаблон сайта', 'Хранит общий HTML и тег [tag:maincontent].', '/templates', 'Основной слой', 'blue'),
+ self::step('Назначение рубрике', 'Каждая рубрика выбирает нужный общий шаблон.', '/rubrics', 'Настройка', 'gray'),
+ self::step('Активная тема', $theme['mode_note'], $theme['url'], $theme['mode_label'], $theme['mode'] === 'native' ? 'green' : 'violet'),
+ ),
+ ),
+ array(
+ 'icon' => 'ti ti-file-text', 'title' => 'Обычная текстовая страница',
+ 'description' => 'Например: «О компании», «Доставка», статья или новость.',
+ 'steps' => array(
+ self::step('Документ', 'Заголовок, текст, изображения и остальные значения полей.', '/documents', 'Данные', 'gray'),
+ self::step('Шаблон рубрики', 'Решает, какие поля и блоки вывести и в каком порядке.', '/rubrics', 'Сборка содержимого', 'blue'),
+ self::componentStep($componentItems, 'storefront', 'info.twig', 'Компонент темы', 'Дополнительное оформление, только если его вызывает шаблон.'),
+ self::step('Шаблон сайта', 'Оборачивает готовое содержимое общей шапкой и подвалом.', '/templates', 'Каркас', 'green'),
+ ),
+ ),
+ array(
+ 'icon' => 'ti ti-list-search', 'title' => 'Список новостей или материалов',
+ 'description' => 'Запрос выбирает документы, а его шаблоны или публичное представление оформляют список.',
+ 'steps' => array(
+ self::step('Запрос', 'Условия, сортировка, количество и пагинация.', '/requests', 'Выбор данных', 'gray'),
+ self::step('Шаблоны запроса', 'Шаблон элемента и общая обёртка результата.', '/requests', 'Основной вывод', 'blue'),
+ self::componentStep($componentItems, 'content_public', 'list.twig', 'Резервный список', 'Используется для нативных коллекций без отдельного представления.'),
+ self::step('Рубрика и сайт', 'Определяют место списка на странице и общий каркас.', '/rubrics', 'Размещение', 'green'),
+ ),
+ ),
+ array(
+ 'icon' => 'ti ti-category-2', 'title' => 'Раздел товарного каталога',
+ 'description' => 'Категория остаётся документом, а каталог добавляет подразделы, фильтры, сортировку и выдачу.',
+ 'steps' => array(
+ self::step('Раздел каталога', 'Связь с документом, полями, условиями и фильтрами.', '/catalog', 'Данные и правила', 'gray'),
+ self::step('Шаблон рубрики', 'Размещает теги подразделов, фильтров и товарной выдачи.', '/rubrics', 'Сборка страницы', 'blue'),
+ self::componentStep($componentItems, 'products_public', 'filter.twig', 'Фильтры', 'Оформляет готовые параметры фильтрации.'),
+ self::componentStep($componentItems, 'products_public', 'listing.twig', 'Товарная выдача', 'Оформляет список и пагинацию.'),
+ ),
+ ),
+ array(
+ 'icon' => 'ti ti-package', 'title' => 'Карточка и страница товара',
+ 'description' => 'Один товар может выглядеть по-разному в списке и на собственной странице.',
+ 'steps' => array(
+ self::step('Товар', 'Документ, цены, изображения и нативные характеристики.', $productUrl, 'Данные', 'gray'),
+ self::step('Карточки товара', $product['available']
+ ? 'Опубликованные карточки назначаются разделам и контекстам.'
+ : 'Слой появляется после установки модуля «Товары».', $productCardsUrl, 'Списки', 'blue'),
+ self::componentStep($componentItems, 'products_public', 'product-card.twig', 'Резервная карточка', 'Работает, если отдельная опубликованная карточка не назначена.'),
+ self::componentStep($componentItems, 'products_public', 'product-detail.twig', 'Страница товара', 'Галерея, цена, покупка, описание и характеристики.'),
+ ),
+ ),
+ );
+ }
+
+ protected static function componentStep(array $items, $namespace, $file, $title, $description)
+ {
+ foreach ($items as $item) {
+ if ($item['namespace'] !== $namespace || $item['file'] !== $file) { continue; }
+ return self::step($title, $description, $item['url'], $item['source_label'],
+ $item['source'] === 'theme' ? 'green' : ($item['source'] === 'fallback' ? 'blue' : 'amber'));
+ }
+
+ return self::step($title, $description, '', 'Не найден', 'amber');
+ }
+
+ protected static function step($title, $text, $url, $state, $tone)
+ {
+ return array(
+ 'title' => (string) $title,
+ 'text' => (string) $text,
+ 'url' => (string) $url,
+ 'state' => (string) $state,
+ 'tone' => in_array($tone, array('gray', 'blue', 'green', 'violet', 'amber'), true) ? $tone : 'gray',
+ );
+ }
+
+ protected static function tableExists($table)
+ {
+ try { return DatabaseSchema::tableExists($table); }
+ catch (\Throwable $e) { return false; }
+ }
+
+ protected static function decode($value)
+ {
+ return html_entity_decode((string) $value, ENT_QUOTES, 'UTF-8');
+ }
+ }
diff --git a/adminx/modules/PublicSite/assets/public-site.css b/adminx/modules/PublicSite/assets/public-site.css
index 8e4b8ae..5d7e015 100644
--- a/adminx/modules/PublicSite/assets/public-site.css
+++ b/adminx/modules/PublicSite/assets/public-site.css
@@ -837,3 +837,508 @@
margin-left: 0;
}
}
+.public-template-map-notice {
+ margin-bottom: var(--space-4);
+}
+.public-template-map-summary {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+}
+.public-template-map-mode {
+ margin-top: var(--space-5);
+}
+.public-template-mode-row {
+ position: relative;
+ display: grid;
+ grid-template-columns: minmax(320px, 1.4fr) minmax(180px, 0.7fr) minmax(220px, 0.9fr) 40px;
+ align-items: center;
+ gap: var(--space-4);
+ min-height: 88px;
+ padding: var(--space-4) var(--space-5);
+ border-radius: var(--radius-sm);
+ background: var(--surface-muted);
+}
+.public-template-mode-main {
+ display: grid;
+ grid-template-columns: 42px minmax(0, 1fr);
+ align-items: center;
+ gap: var(--space-3);
+}
+.public-template-mode-main h3,
+.public-template-mode-main p {
+ margin: 0;
+}
+.public-template-mode-main h3 {
+ margin-top: 2px;
+ font-size: 17px;
+ text-wrap: balance;
+}
+.public-template-mode-main p {
+ margin-top: 4px;
+ color: var(--text-secondary);
+ font-size: 12px;
+ line-height: 1.45;
+ text-wrap: pretty;
+}
+.public-template-mode-theme,
+.public-template-mode-shell {
+ display: grid;
+ gap: 4px;
+ min-width: 0;
+}
+.public-template-mode-theme > span,
+.public-template-mode-shell > span {
+ font-size: 12px;
+}
+.public-template-mode-theme > b,
+.public-template-mode-shell > b,
+.public-template-mode-theme > code,
+.public-template-mode-shell > code {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.public-template-mode-theme > small,
+.public-template-mode-shell > small {
+ color: var(--text-secondary);
+}
+.public-template-mode-shell > small {
+ color: var(--amber-700);
+}
+.public-template-mode-shell > small.is-active {
+ color: var(--green-700);
+}
+.public-template-rules {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 1px;
+ margin-top: var(--space-3);
+ overflow: hidden;
+ border-radius: var(--radius-sm);
+ background: var(--border-default);
+}
+.public-template-rule {
+ display: grid;
+ grid-template-columns: 28px minmax(0, 1fr);
+ align-items: start;
+ gap: var(--space-3);
+ min-height: 72px;
+ padding: var(--space-4);
+ background: var(--surface);
+}
+.public-template-rule > i {
+ color: var(--blue-600);
+ font-size: 22px;
+}
+.public-template-rule > div {
+ display: grid;
+ gap: 4px;
+}
+.public-template-rule span {
+ color: var(--text-secondary);
+ font-size: 12px;
+ line-height: 1.45;
+ text-wrap: pretty;
+}
+.public-template-scenarios,
+.public-template-inventory,
+.public-template-components {
+ margin-top: var(--space-6);
+}
+.public-template-flow-list {
+ display: grid;
+ gap: var(--space-3);
+}
+.public-template-flow {
+ overflow: hidden;
+ border-radius: var(--radius-sm);
+ background: var(--surface);
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
+}
+.public-template-flow > header {
+ display: flex;
+ align-items: center;
+ gap: var(--space-3);
+ padding: var(--space-4) var(--space-5);
+ border-bottom: 1px solid var(--border-default);
+}
+.public-template-flow > header h3,
+.public-template-flow > header p {
+ margin: 0;
+}
+.public-template-flow > header h3 {
+ font-size: 15px;
+ text-wrap: balance;
+}
+.public-template-flow > header p {
+ margin-top: 3px;
+ color: var(--text-secondary);
+ font-size: 12px;
+ text-wrap: pretty;
+}
+[data-theme="dark"] .public-template-flow {
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.09);
+}
+.public-template-flow-steps {
+ display: flex;
+ align-items: stretch;
+ gap: 0;
+ padding: var(--space-3);
+}
+.public-template-flow-step {
+ position: relative;
+ display: grid;
+ grid-template-columns: 26px minmax(0, 1fr) 32px;
+ align-items: start;
+ gap: var(--space-2);
+ flex: 1 1 0;
+ min-width: 0;
+ padding: var(--space-3);
+ border-radius: var(--radius-sm);
+ background: var(--surface-muted);
+}
+.public-template-flow-step.is-blue .public-template-flow-number {
+ background: var(--blue-100);
+ color: var(--blue-700);
+}
+.public-template-flow-step.is-green .public-template-flow-number {
+ background: var(--green-100);
+ color: var(--green-700);
+}
+.public-template-flow-step.is-violet .public-template-flow-number {
+ background: var(--violet-100);
+ color: var(--violet-700);
+}
+.public-template-flow-step.is-amber .public-template-flow-number {
+ background: var(--amber-100);
+ color: var(--amber-700);
+}
+.public-template-flow-number {
+ display: grid;
+ place-items: center;
+ width: 26px;
+ height: 26px;
+ border-radius: 50%;
+ background: var(--gray-200);
+ color: var(--text-secondary);
+ font-size: 12px;
+ font-weight: 700;
+}
+.public-template-flow-copy {
+ display: grid;
+ gap: 6px;
+ min-width: 0;
+}
+.public-template-flow-copy p {
+ margin: 0;
+ color: var(--text-secondary);
+ font-size: 12px;
+ line-height: 1.4;
+ text-wrap: pretty;
+}
+.public-template-flow-title {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: var(--space-2);
+}
+.public-template-flow-title b {
+ font-size: 13px;
+ line-height: 1.35;
+}
+.public-template-flow-title .badge {
+ flex: 0 0 auto;
+ max-width: 48%;
+ white-space: normal;
+}
+.public-template-flow-step > .btn {
+ width: 32px;
+ height: 32px;
+ min-height: 32px;
+}
+.public-template-flow-arrow {
+ align-self: center;
+ flex: 0 0 24px;
+ color: var(--text-secondary);
+ text-align: center;
+}
+.public-template-inventory-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
+ gap: var(--space-4);
+}
+.public-template-site-templates table th:nth-child(2),
+.public-template-site-templates table td:nth-child(2) {
+ width: 120px;
+}
+.public-template-site-templates table th:last-child,
+.public-template-site-templates table td:last-child {
+ width: 52px;
+}
+.public-template-product-links {
+ display: grid;
+}
+.public-template-product-links > a {
+ display: grid;
+ grid-template-columns: 40px minmax(0, 1fr) 20px;
+ align-items: center;
+ gap: var(--space-3);
+ min-height: 66px;
+ padding: var(--space-3) var(--space-4);
+ color: inherit;
+ text-decoration: none;
+}
+.public-template-product-links > a + a {
+ border-top: 1px solid var(--border-default);
+}
+.public-template-product-links > a:hover {
+ background: var(--surface-muted);
+}
+.public-template-product-links > a > span:nth-child(2) {
+ display: grid;
+ gap: 3px;
+ min-width: 0;
+}
+.public-template-product-links > a small {
+ color: var(--text-secondary);
+ font-size: 12px;
+}
+.public-template-product-links > a > i {
+ color: var(--text-secondary);
+}
+.public-template-product-empty {
+ min-height: 198px;
+ padding: var(--space-5);
+}
+.public-template-rubrics-card {
+ margin-top: var(--space-4);
+}
+.public-template-rubrics-table th:nth-child(1) {
+ width: 22%;
+}
+.public-template-rubrics-table th:nth-child(2) {
+ width: 15%;
+}
+.public-template-rubrics-table th:nth-child(3) {
+ width: 20%;
+}
+.public-template-rubrics-table th:nth-child(4) {
+ width: 23%;
+}
+.public-template-rubrics-table th:nth-child(5) {
+ width: 90px;
+}
+.public-template-rubrics-table th:last-child {
+ width: 52px;
+}
+.public-template-rubric-state {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 5px 8px;
+}
+.public-template-rubric-state small {
+ color: var(--text-secondary);
+}
+.public-template-component-groups {
+ column-count: 2;
+ column-gap: var(--space-4);
+}
+.public-template-component-group {
+ display: inline-block;
+ width: 100%;
+ margin-bottom: var(--space-4);
+ overflow: hidden;
+ padding: 0;
+ break-inside: avoid;
+}
+.public-template-component-group > summary {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-3);
+ min-height: 58px;
+ padding: var(--space-3) var(--space-4);
+ cursor: pointer;
+ list-style: none;
+}
+.public-template-component-group > summary::-webkit-details-marker {
+ display: none;
+}
+.public-template-component-group > summary > span {
+ display: grid;
+ grid-template-columns: 24px minmax(0, 1fr) auto;
+ align-items: center;
+ gap: var(--space-2);
+ min-width: 0;
+}
+.public-template-component-group > summary > span > i {
+ color: var(--blue-600);
+ font-size: 20px;
+}
+.public-template-component-group > summary small {
+ color: var(--text-secondary);
+ font-size: 12px;
+}
+.public-template-component-group > summary > i {
+ transition: transform 0.18s cubic-bezier(0.2, 0, 0, 1);
+}
+.public-template-component-group[open] > summary {
+ border-bottom: 1px solid var(--border-default);
+}
+.public-template-component-group[open] > summary > i {
+ transform: rotate(180deg);
+}
+.public-template-component-list {
+ display: grid;
+}
+.public-template-component-row {
+ display: grid;
+ grid-template-columns: 40px minmax(0, 1fr) 40px;
+ align-items: center;
+ gap: var(--space-3);
+ min-height: 82px;
+ padding: var(--space-3) var(--space-4);
+}
+.public-template-component-row + .public-template-component-row {
+ border-top: 1px solid var(--border-default);
+}
+.public-template-component-copy {
+ display: grid;
+ gap: 4px;
+ min-width: 0;
+}
+.public-template-component-copy > div {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-2);
+}
+.public-template-component-copy p {
+ margin: 0;
+ color: var(--text-secondary);
+ font-size: 12px;
+ line-height: 1.4;
+ text-wrap: pretty;
+}
+.public-template-component-copy code {
+ overflow: hidden;
+ color: var(--text-secondary);
+ font-size: 11px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+@media (max-width: 1280px) {
+ .public-template-mode-row {
+ grid-template-columns: minmax(280px, 1.2fr) minmax(160px, 0.7fr) minmax(190px, 0.8fr) 40px;
+ }
+ .public-template-flow-steps {
+ align-items: stretch;
+ flex-wrap: wrap;
+ gap: var(--space-2);
+ }
+ .public-template-flow-step {
+ flex-basis: calc(50% - var(--space-2));
+ }
+ .public-template-flow-arrow {
+ display: none;
+ }
+}
+@media (max-width: 980px) {
+ .public-template-map-summary,
+ .public-template-rules,
+ .public-template-inventory-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+ .public-template-mode-row {
+ grid-template-columns: minmax(0, 1fr) minmax(160px, 0.6fr) 40px;
+ }
+ .public-template-mode-shell {
+ grid-column: 1 / -1;
+ grid-row: 2;
+ }
+}
+@media (max-width: 720px) {
+ .public-template-map-summary,
+ .public-template-rules,
+ .public-template-inventory-grid,
+ .public-template-mode-row {
+ grid-template-columns: 1fr;
+ }
+ .public-template-component-groups {
+ column-count: 1;
+ }
+ .public-template-mode-row {
+ gap: var(--space-3);
+ padding: var(--space-4);
+ }
+ .public-template-mode-shell {
+ grid-column: auto;
+ grid-row: auto;
+ }
+ .public-template-mode-row > .btn {
+ position: absolute;
+ right: var(--space-4);
+ }
+ .public-template-flow > header {
+ align-items: flex-start;
+ padding: var(--space-3) var(--space-4);
+ }
+ .public-template-flow-steps {
+ display: grid;
+ grid-template-columns: 1fr;
+ padding: var(--space-2);
+ }
+ .public-template-flow-step {
+ width: 100%;
+ }
+ .public-template-rubrics-card .table-scroll {
+ overflow: visible;
+ }
+ .public-template-rubrics-table {
+ display: block;
+ min-width: 0;
+ }
+ .public-template-rubrics-table thead,
+ .public-template-rubrics-table colgroup {
+ display: none;
+ }
+ .public-template-rubrics-table tbody,
+ .public-template-rubrics-table tr,
+ .public-template-rubrics-table td {
+ display: block;
+ width: 100%;
+ }
+ .public-template-rubrics-table tr {
+ box-sizing: border-box;
+ padding: var(--space-3) var(--space-4);
+ border-bottom: 1px solid var(--border-default);
+ }
+ .public-template-rubrics-table td {
+ box-sizing: border-box;
+ display: grid;
+ grid-template-columns: minmax(100px, 34%) minmax(0, 1fr);
+ gap: var(--space-3);
+ padding: 6px 0;
+ border: 0;
+ }
+ .public-template-rubrics-table td::before {
+ color: var(--text-secondary);
+ content: attr(data-label);
+ font-size: 12px;
+ }
+ .public-template-component-group > summary > span {
+ grid-template-columns: 24px minmax(0, 1fr);
+ }
+ .public-template-component-group > summary > span small {
+ grid-column: 2;
+ }
+ .public-template-component-row {
+ grid-template-columns: 36px minmax(0, 1fr) 40px;
+ align-items: start;
+ padding: var(--space-3);
+ }
+ .public-template-component-copy > div {
+ align-items: flex-start;
+ flex-direction: column;
+ }
+}
diff --git a/adminx/modules/PublicSite/module.php b/adminx/modules/PublicSite/module.php
index ce57408..f3bac08 100644
--- a/adminx/modules/PublicSite/module.php
+++ b/adminx/modules/PublicSite/module.php
@@ -17,7 +17,7 @@
return array(
'code' => 'public_site',
'name' => 'Публичный сайт',
- 'version' => '0.5.0',
+ 'version' => '0.5.2',
'permissions' => array(
'key' => 'public_site',
'items' => array(
@@ -56,6 +56,7 @@
),
'routes' => array(
array('GET', '/public-site', array(\App\Adminx\PublicSite\Controller::class, 'index')),
+ array('GET', '/public-site/template-map', array(\App\Adminx\PublicSite\Controller::class, 'templateMap')),
array('GET', '/public-site/map', array(\App\Adminx\PublicSite\Controller::class, 'siteMap')),
array('GET', '/public-site/placements', array(\App\Adminx\PublicSite\Controller::class, 'placements')),
array('GET', '/public-site/diagnostics', array(\App\Adminx\PublicSite\Controller::class, 'diagnostics')),
diff --git a/adminx/modules/PublicSite/view/_tabs.twig b/adminx/modules/PublicSite/view/_tabs.twig
index f939f9a..7fb1ffa 100644
--- a/adminx/modules/PublicSite/view/_tabs.twig
+++ b/adminx/modules/PublicSite/view/_tabs.twig
@@ -1,5 +1,6 @@
Структура
+ Карта шаблонов
Карта сайта
Представления
Шаблоны страницы
diff --git a/adminx/modules/PublicSite/view/template-map.twig b/adminx/modules/PublicSite/view/template-map.twig
new file mode 100644
index 0000000..3531059
--- /dev/null
+++ b/adminx/modules/PublicSite/view/template-map.twig
@@ -0,0 +1,262 @@
+{% extends '@adminx/main.twig' %}
+{% block title %}Карта шаблонов{% endblock %}
+{% block content %}
+
+
+ Главная
+ Публичный сайт
+ Карта шаблонов
+
+
+
+
+{% set public_site_tab = 'template_map' %}
+{% include '@public_site/_tabs.twig' %}
+
+
+
+
+ Это навигатор, а не новый конструктор
+ Карта ничего не переключает и не сохраняет. Она показывает действующую цепочку AVE.cms и ведёт в штатный редактор нужного шаблона.
+
+
+
+
+
+
+
{{ template_map.summary.site_templates }} Общих шаблонов
+
+
+
+
{{ template_map.summary.rubrics }} Рубрик
+
+
+
+
{{ template_map.summary.components }} Публичных компонентов
+
+
+
+
{{ template_map.summary.active_overrides }} Действующих источников
+
+
+
+
+
+
+
+
+
+
+
Способ сборки
+
{{ template_map.theme.mode_label }}
+
{{ template_map.theme.mode_note }}
+
+
+
+ Активная тема
+ {{ template_map.theme.name }}
+ {{ template_map.theme.code }}{% if template_map.theme.version %} · версия {{ template_map.theme.version }}{% endif %}
+
+
+ Файловая оболочка
+ {% if template_map.theme.page_shell %}{{ template_map.theme.page_shell }}{% else %}Не задана {% endif %}
+ {{ template_map.theme.page_shell_active ? 'Используется' : 'Не заменяет общий шаблон' }}
+
+
+
+
+
+ {% for rule in template_map.rules %}
+
+
+
{{ rule.title }} {{ rule.text }}
+
+ {% endfor %}
+
+
+
+
+
+
+
+ {% for scenario in template_map.scenarios %}
+
+
+
+ {% for step in scenario.steps %}
+
+
{{ loop.index }}
+
+
{{ step.title }} {{ step.state }}
+
{{ step.text }}
+
+ {% if step.url %}
{% endif %}
+
+ {% if not loop.last %}
{% endif %}
+ {% endfor %}
+
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
Общие шаблоны сайта Каркас страницы и место [tag:maincontent]
+
+
+
+
+
+
+
+
Товарное оформление Отдельные представления карточек и фильтров
+ {% if template_map.product.available %}
Подключено {% else %}
Не установлено {% endif %}
+
+ {% if template_map.product.available %}
+
+ {% else %}
+
+
Товарный слой не установлен
+
После установки модуля «Товары» здесь появятся карточки, фильтры и шаблоны страницы товара.
+
Открыть модули
+
+ {% endif %}
+
+
+
+
+
+
Рубрики и их шаблоны {{ template_map.rubrics|length }} типов контента в текущей установке
+
+
+
+
+
+
+
+
+
+
+ {% for group in template_map.component_groups %}
+
+
+ {{ group.title }} {{ group.items|length }} компонентов
+
+
+
+ {% for item in group.items %}
+
+
+
+
{{ item.title }} {{ item.source_label }}
+
{{ item.description }}
+
{{ item.path ?: item.namespace ~ '/' ~ item.file }}
+
+
+
+ {% endfor %}
+
+
+ {% else %}
+
Компоненты темы не найдены Проверьте реестр активной темы.
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/adminx/modules/Rubrics/Controller.php b/adminx/modules/Rubrics/Controller.php
index 55a5593..b321ced 100644
--- a/adminx/modules/Rubrics/Controller.php
+++ b/adminx/modules/Rubrics/Controller.php
@@ -665,6 +665,12 @@
return $this->error('Шаблон рубрики не найден', array(), 404);
}
+ if (!$this->wantsJson()) {
+ $this->redirect($this->base() . '/rubrics?templates=' . (int) $item['rubric_id']
+ . '&template=' . (int) $item['id']);
+ return null;
+ }
+
return $this->success('', array('data' => $item));
}
diff --git a/adminx/modules/Rubrics/Model.php b/adminx/modules/Rubrics/Model.php
index c089c89..ae0c8ec 100644
--- a/adminx/modules/Rubrics/Model.php
+++ b/adminx/modules/Rubrics/Model.php
@@ -840,7 +840,10 @@
}
$rows = DB::query(
- 'SELECT t.*, (SELECT COUNT(*) FROM ' . self::docsTable() . ' d WHERE d.rubric_id = t.rubric_id AND d.rubric_tmpl_id = t.id) AS docs_count'
+ 'SELECT t.*,'
+ . ' (SELECT COUNT(*) FROM ' . self::docsTable() . ' d WHERE d.rubric_id = t.rubric_id AND d.rubric_tmpl_id = t.id) AS docs_count,'
+ . ' (SELECT d.Id FROM ' . self::docsTable() . ' d WHERE d.rubric_id = t.rubric_id AND d.rubric_tmpl_id = t.id ORDER BY d.Id ASC LIMIT 1) AS first_document_id,'
+ . ' (SELECT d.document_title FROM ' . self::docsTable() . ' d WHERE d.rubric_id = t.rubric_id AND d.rubric_tmpl_id = t.id ORDER BY d.Id ASC LIMIT 1) AS first_document_title'
. ' FROM ' . self::templatesTable() . ' t WHERE t.rubric_id = %i ORDER BY t.id ASC',
(int) $rubricId
)->getAll();
@@ -882,7 +885,10 @@
public static function extraTemplate($id)
{
$row = DB::query(
- 'SELECT t.*, (SELECT COUNT(*) FROM ' . self::docsTable() . ' d WHERE d.rubric_id = t.rubric_id AND d.rubric_tmpl_id = t.id) AS docs_count'
+ 'SELECT t.*,'
+ . ' (SELECT COUNT(*) FROM ' . self::docsTable() . ' d WHERE d.rubric_id = t.rubric_id AND d.rubric_tmpl_id = t.id) AS docs_count,'
+ . ' (SELECT d.Id FROM ' . self::docsTable() . ' d WHERE d.rubric_id = t.rubric_id AND d.rubric_tmpl_id = t.id ORDER BY d.Id ASC LIMIT 1) AS first_document_id,'
+ . ' (SELECT d.document_title FROM ' . self::docsTable() . ' d WHERE d.rubric_id = t.rubric_id AND d.rubric_tmpl_id = t.id ORDER BY d.Id ASC LIMIT 1) AS first_document_title'
. ' FROM ' . self::templatesTable() . ' t WHERE t.id = %i LIMIT 1',
(int) $id
)->getAssoc();
@@ -1457,6 +1463,8 @@
$row['author_id'] = (int) $row['author_id'];
$row['created'] = (int) $row['created'];
$row['docs_count'] = isset($row['docs_count']) ? (int) $row['docs_count'] : 0;
+ $row['first_document_id'] = isset($row['first_document_id']) ? (int) $row['first_document_id'] : 0;
+ $row['first_document_title'] = isset($row['first_document_title']) ? (string) $row['first_document_title'] : '';
$row['created_label'] = $row['created'] > 0 ? date('d.m.Y H:i', $row['created']) : 'не задано';
$row['size_label'] = self::formatBytes(strlen((string) $row['template']));
$row['can_delete'] = $row['docs_count'] === 0;
diff --git a/adminx/modules/Rubrics/assets/rubrics.css b/adminx/modules/Rubrics/assets/rubrics.css
index eb4f67e..4abb738 100644
--- a/adminx/modules/Rubrics/assets/rubrics.css
+++ b/adminx/modules/Rubrics/assets/rubrics.css
@@ -1129,7 +1129,8 @@
overflow-x: hidden;
}
.rubrics-extra-template-panel {
- width: min(66vw, 980px);
+ width: 66.6667vw;
+ max-width: 66.6667vw;
overflow-x: hidden;
}
.rubrics-drawer {
@@ -1141,6 +1142,63 @@
.rubrics-drawer .drawer-body {
overflow-x: hidden;
}
+.rubrics-extra-template-body {
+ display: flex;
+ min-height: 0;
+ flex-direction: column;
+ gap: 16px;
+}
+.rubrics-extra-template-help {
+ flex: 0 0 auto;
+ align-items: center;
+ margin: 0;
+}
+.rubrics-extra-template-help > i {
+ flex: 0 0 auto;
+ color: var(--blue-600);
+ font-size: 20px;
+}
+.rubrics-extra-template-help > div {
+ display: grid;
+ flex: 1 1 auto;
+ gap: 3px;
+ min-width: 0;
+}
+.rubrics-extra-template-help b,
+.rubrics-extra-template-help span {
+ display: block;
+}
+.rubrics-extra-template-help span {
+ color: var(--text-secondary);
+ font-size: 12px;
+ line-height: 1.45;
+}
+.rubrics-extra-template-help .btn {
+ flex: 0 0 auto;
+ max-width: 42%;
+}
+.rubrics-extra-template-help .btn span {
+ overflow: hidden;
+ color: inherit;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.rubrics-extra-template-fields {
+ display: flex;
+ flex: 1 1 auto;
+ flex-direction: column;
+ gap: 14px;
+ min-height: 0;
+}
+.rubrics-extra-template-fields > .rubrics-code-field {
+ flex: 1 1 auto;
+ min-height: 360px;
+}
+.rubrics-extra-template-fields > .rubrics-code-field > .CodeMirror {
+ flex: 1 1 auto;
+ min-height: 320px;
+ height: auto !important;
+}
/* Секции формы настроек поля: цветные плашки-заголовки и визуальное разделение */
.rubrics-field-form {
display: flex;
@@ -3859,12 +3917,24 @@
.rubrics-group-panel {
width: min(96vw, 980px);
}
+ .rubrics-extra-template-panel {
+ max-width: min(96vw, 980px);
+ }
#rubricDrawer {
width: 96vw;
max-width: 96vw;
}
}
@media (max-width: 760px) {
+ .rubrics-extra-template-help {
+ align-items: stretch;
+ flex-direction: column;
+ }
+ .rubrics-extra-template-help .btn {
+ justify-content: center;
+ max-width: none;
+ width: 100%;
+ }
.rubrics-builder-runtime {
grid-template-columns: 40px minmax(0, 1fr) auto;
}
diff --git a/adminx/modules/Rubrics/assets/rubrics.js b/adminx/modules/Rubrics/assets/rubrics.js
index de9fbf1..3496b85 100644
--- a/adminx/modules/Rubrics/assets/rubrics.js
+++ b/adminx/modules/Rubrics/assets/rubrics.js
@@ -1339,6 +1339,7 @@
this.extraTemplateForm.elements.rubric_id.value = this.currentRubricId;
this.setCodeValue(this.extraTemplateForm.elements.template, '');
document.getElementById('rubricExtraTemplateTitle').textContent = 'Новый дополнительный шаблон';
+ this.updateExtraTemplateDocumentLink({ rubric_id: this.currentRubricId, docs_count: 0 });
this.activeTemplateTextarea = this.extraTemplateForm.elements.template;
if (Adminx.Drawer) { Adminx.Drawer.open('rubricExtraTemplateDrawer'); }
this.refreshEditors();
@@ -1357,11 +1358,29 @@
self.extraTemplateForm.elements.title.value = item.title || '';
self.setCodeValue(self.extraTemplateForm.elements.template, item.template || '');
document.getElementById('rubricExtraTemplateTitle').textContent = 'Редактирование шаблона #' + item.id;
+ self.updateExtraTemplateDocumentLink(item);
self.activeTemplateTextarea = self.extraTemplateForm.elements.template;
self.refreshEditors();
});
},
+ updateExtraTemplateDocumentLink: function (item) {
+ var link = document.querySelector('[data-extra-template-document-link]');
+ var label;
+ if (!link) { return; }
+ item = item || {};
+ if ((parseInt(item.docs_count, 10) || 0) === 1 && (parseInt(item.first_document_id, 10) || 0) > 0) {
+ link.href = this.base() + '/documents/' + parseInt(item.first_document_id, 10) + '/edit';
+ label = item.first_document_title
+ ? (link.getAttribute('data-edit-prefix') || '') + item.first_document_title
+ : link.getAttribute('data-edit-content');
+ } else {
+ link.href = this.base() + '/documents?rubric_id=' + (parseInt(item.rubric_id, 10) || this.currentRubricId || 0);
+ label = link.getAttribute('data-documents-label');
+ }
+ link.querySelector('span').textContent = label;
+ },
+
submitExtraTemplate: function () {
var self = this;
var id = this.extraTemplateForm.elements.id.value;
diff --git a/adminx/modules/Rubrics/language/en/interface.xml b/adminx/modules/Rubrics/language/en/interface.xml
index 573e7ff..d9bff8d 100644
--- a/adminx/modules/Rubrics/language/en/interface.xml
+++ b/adminx/modules/Rubrics/language/en/interface.xml
@@ -1140,4 +1140,8 @@
Replace all
The old file will be moved to the trash after saving.
The current set will be moved to the trash after saving.
+ The template controls presentation
+ Headings, text, and other data are edited in the document that uses this template.
+ Edit content
+ Edit:
diff --git a/adminx/modules/Rubrics/language/ru/interface.xml b/adminx/modules/Rubrics/language/ru/interface.xml
index 58195e0..a1e1884 100644
--- a/adminx/modules/Rubrics/language/ru/interface.xml
+++ b/adminx/modules/Rubrics/language/ru/interface.xml
@@ -1140,4 +1140,8 @@
Заменить все
Старый файл будет перемещён в корзину после сохранения.
Текущий комплект будет перемещён в корзину после сохранения.
+ Шаблон отвечает за оформление
+ Заголовки, тексты и другие данные меняются в документе, который использует этот шаблон.
+ Редактировать содержимое
+ Редактировать:
diff --git a/adminx/modules/Rubrics/module.php b/adminx/modules/Rubrics/module.php
index 23d5f33..9785bc9 100644
--- a/adminx/modules/Rubrics/module.php
+++ b/adminx/modules/Rubrics/module.php
@@ -17,7 +17,7 @@
return array(
'code' => 'rubrics',
'name' => 'Рубрики и поля',
- 'version' => '0.3.4',
+ 'version' => '0.3.6',
'field_sets' => \App\Adminx\Rubrics\RubricFieldPresets::definitions(),
'hook_definitions' => array(
array(
diff --git a/adminx/modules/Rubrics/view/index.twig b/adminx/modules/Rubrics/view/index.twig
index 43129ae..954b3c2 100644
--- a/adminx/modules/Rubrics/view/index.twig
+++ b/adminx/modules/Rubrics/view/index.twig
@@ -783,12 +783,17 @@
-