From 97df5b4c0aa1d5fbf19c2132436045af0846b5f1 Mon Sep 17 00:00:00 2001 From: Nikita Kiselev Date: Mon, 15 Dec 2025 19:38:57 +0300 Subject: [PATCH] feat: add html editor for telegram messages --- frontend/admin/package-lock.json | 79 +++++++++++ frontend/admin/package.json | 1 + .../admin/src/components/RichTextEditor.vue | 133 ++++++++++++++++++ .../Settings/ItemTgMessageTemplate.vue | 32 ++--- .../controller/extension/module/tgshop.php | 9 +- .../template/extension/module/tgshop.twig | 2 + .../bastion/Handlers/SendMessageHandler.php | 3 - .../bastion/Handlers/TelegramHandler.php | 1 + .../upload/oc_telegram_shop/configs/app.php | 43 +++--- .../Telegram/Commands/StartCommand.php | 2 +- .../framework/Telegram/TelegramService.php | 16 +-- 11 files changed, 263 insertions(+), 58 deletions(-) create mode 100644 frontend/admin/src/components/RichTextEditor.vue diff --git a/frontend/admin/package-lock.json b/frontend/admin/package-lock.json index 1ac8c09..d50e17c 100644 --- a/frontend/admin/package-lock.json +++ b/frontend/admin/package-lock.json @@ -8,6 +8,7 @@ "name": "admin", "version": "0.0.0", "dependencies": { + "@codemirror/lang-html": "^6.4.11", "@codemirror/lang-json": "^6.0.2", "@codemirror/theme-one-dark": "^6.1.3", "@formkit/drag-and-drop": "^0.5.3", @@ -572,6 +573,51 @@ "@lezer/common": "^1.1.0" } }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", + "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, "node_modules/@codemirror/lang-json": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", @@ -1547,6 +1593,17 @@ "integrity": "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==", "license": "MIT" }, + "node_modules/@lezer/css": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz", + "integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, "node_modules/@lezer/highlight": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", @@ -1556,6 +1613,28 @@ "@lezer/common": "^1.3.0" } }, + "node_modules/@lezer/html": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz", + "integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, "node_modules/@lezer/json": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", diff --git a/frontend/admin/package.json b/frontend/admin/package.json index 71f5684..1dca9c6 100644 --- a/frontend/admin/package.json +++ b/frontend/admin/package.json @@ -16,6 +16,7 @@ "format": "prettier --write src/" }, "dependencies": { + "@codemirror/lang-html": "^6.4.11", "@codemirror/lang-json": "^6.0.2", "@codemirror/theme-one-dark": "^6.1.3", "@formkit/drag-and-drop": "^0.5.3", diff --git a/frontend/admin/src/components/RichTextEditor.vue b/frontend/admin/src/components/RichTextEditor.vue new file mode 100644 index 0000000..0b70291 --- /dev/null +++ b/frontend/admin/src/components/RichTextEditor.vue @@ -0,0 +1,133 @@ + + + + diff --git a/frontend/admin/src/components/Settings/ItemTgMessageTemplate.vue b/frontend/admin/src/components/Settings/ItemTgMessageTemplate.vue index a22c8a5..1abfbeb 100644 --- a/frontend/admin/src/components/Settings/ItemTgMessageTemplate.vue +++ b/frontend/admin/src/components/Settings/ItemTgMessageTemplate.vue @@ -2,12 +2,11 @@ @@ -78,11 +70,15 @@ import {useSettingsStore} from "@/stores/settings.js"; import {ref, toRaw, useId} from "vue"; import SettingsItem from "@/components/SettingsItem.vue"; import {apiPost} from "@/utils/http.js"; +import {Codemirror} from "vue-codemirror"; +import { html } from '@codemirror/lang-html'; +import { oneDark } from '@codemirror/theme-one-dark'; const model = defineModel(); const settings = useSettingsStore(); const isSending = ref(false); const collapseId = useId(); +const extensions = [html(), oneDark]; const props = defineProps({ label: { diff --git a/module/oc_telegram_shop/upload/admin/controller/extension/module/tgshop.php b/module/oc_telegram_shop/upload/admin/controller/extension/module/tgshop.php index 0929dab..fbde5d9 100755 --- a/module/oc_telegram_shop/upload/admin/controller/extension/module/tgshop.php +++ b/module/oc_telegram_shop/upload/admin/controller/extension/module/tgshop.php @@ -96,6 +96,7 @@ class ControllerExtensionModuleTgshop extends Controller public function index(): void { $this->runMaintenanceTasks(); + $this->injectAssets(); $this->injectVueJs(); $this->showConfigPage(); } @@ -112,7 +113,7 @@ class ControllerExtensionModuleTgshop extends Controller $data['themes'] = self::$themes; $data['telecart_module_version'] = module_version(); $data['shop_base_url'] = HTTPS_CATALOG; - + $data['action'] = $this->url->link( 'extension/module/tgshop', 'user_token=' . $this->session->data['user_token'], @@ -272,4 +273,10 @@ class ControllerExtensionModuleTgshop extends Controller return $log; } + + private function injectAssets(): void + { + $this->document->addScript('view/javascript/summernote/summernote.js'); + $this->document->addStyle('view/javascript/summernote/summernote.css'); + } } diff --git a/module/oc_telegram_shop/upload/admin/view/template/extension/module/tgshop.twig b/module/oc_telegram_shop/upload/admin/view/template/extension/module/tgshop.twig index 34fbefd..faacd2c 100755 --- a/module/oc_telegram_shop/upload/admin/view/template/extension/module/tgshop.twig +++ b/module/oc_telegram_shop/upload/admin/view/template/extension/module/tgshop.twig @@ -31,4 +31,6 @@ + + {{ footer }} diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/SendMessageHandler.php b/module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/SendMessageHandler.php index cff5289..e2b04b9 100755 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/SendMessageHandler.php +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/SendMessageHandler.php @@ -69,9 +69,6 @@ class SendMessageHandler $this->telegramService->sendMessage( $telegramUserId, $message, - [], - \Openguru\OpenCartFramework\Telegram\Enums\ChatAction::TYPING, - '' // Обычный текст без форматирования ); $this->logger->info('Message sent to Telegram user', [ diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/TelegramHandler.php b/module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/TelegramHandler.php index 28fe526..6dc2386 100755 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/TelegramHandler.php +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/TelegramHandler.php @@ -7,6 +7,7 @@ use Exception; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\GuzzleException; use Openguru\OpenCartFramework\Cache\CacheInterface; +use Openguru\OpenCartFramework\Telegram\Enums\ChatAction; use Symfony\Component\HttpFoundation\JsonResponse; use Openguru\OpenCartFramework\Http\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/configs/app.php b/module/oc_telegram_shop/upload/oc_telegram_shop/configs/app.php index b132baf..588d292 100755 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/configs/app.php +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/configs/app.php @@ -15,33 +15,34 @@ return [ 'telegram' => [ "bot_token" => "", "chat_id" => null, - "owner_notification_template" => << <<Новый заказ №{order_id} +Магазин: {store_name} -*Покупатель:* {customer} -*Email:* {email} -*Телефон:* {phone} -*IP:* {ip} +Покупатель +Имя: {customer} +Email: {email} +Телефон: {phone} +IP: {ip} -*Адрес доставки:* -{address} - -*Комментарий:* +Комментарий к заказу {comment} -*Сумма заказа:* {total} -*Дата оформления:* {created_at} -TEXT, - "customer_notification_template" => <<Сумма заказа: {total} +Дата оформления: {created_at} +HTML, + "customer_notification_template" => <<Заказ оформлен -*Номер заказа* \#{order_id} -*Сумма заказа:* {total} -*Дата оформления:* {created_at} +Спасибо за ваш заказ в магазине {store_name}. -Мы свяжемся с вами при необходимости\. -Хорошего дня\! -TEXT, +Номер заказа: №{order_id} +Сумма заказа: {total}р. +Дата оформления: {created_at} + +Информация о заказе сохранена. +При необходимости с вами свяжутся представители магазина. +HTML, "mini_app_url" => "", ], diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/Commands/StartCommand.php b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/Commands/StartCommand.php index 75af2a8..acef691 100755 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/Commands/StartCommand.php +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/Commands/StartCommand.php @@ -33,6 +33,6 @@ class StartCommand extends TelegramCommand ], ]; - $this->telegram->sendMessage($chatId, $message, $buttons, ChatAction::TYPING, 'html'); + $this->telegram->sendMessage($chatId, $message, $buttons); } } diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/TelegramService.php b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/TelegramService.php index 27055ed..1225bca 100755 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/TelegramService.php +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/TelegramService.php @@ -27,21 +27,9 @@ class TelegramService $this->logger = $logger; } - public function escapeTelegramMarkdownV2(string $text): string - { - $specials = ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']; - foreach ($specials as $char) { - $text = str_replace($char, '\\' . $char, $text); - } - - return $text; - } - public function prepareMessage(string $template, array $variables = []): string { - $values = array_map([$this, 'escapeTelegramMarkdownV2'], array_values($variables)); - - return str_replace(array_keys($variables), $values, $template); + return str_replace(array_keys($variables), array_values($variables), $template); } /** @@ -54,7 +42,7 @@ class TelegramService string $text, array $replyMarkup = [], string $chatAction = ChatAction::TYPING, - string $parseMode = 'MarkdownV2' + string $parseMode = 'html' ): void { if (! $this->botToken) { return;