From c53f174959977a5e35f73f42f154993d920e06cd Mon Sep 17 00:00:00 2001 From: Dmitriy Gorshenin Date: Mon, 27 Oct 2025 23:39:55 +0300 Subject: [PATCH] test --- lzipus/README.md | 166 ++++++++++++++++++ lzipus/manifest.json | 2 +- lzipus/src/popup.css | 163 ++++++++++++++++-- lzipus/src/popup.html | 28 ++- lzipus/src/popup.js | 392 +++++++++++++++++++++++++++++++++++++++--- 5 files changed, 703 insertions(+), 48 deletions(-) create mode 100644 lzipus/README.md diff --git a/lzipus/README.md b/lzipus/README.md new file mode 100644 index 0000000..b32aeb7 --- /dev/null +++ b/lzipus/README.md @@ -0,0 +1,166 @@ +# Izipus - SMS Support Templates Manager + +**Version:** 0.4.0 +**Type:** Browser Extension (Firefox/Chrome) + +## 🎯 ОписаниС + +Izipus - Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π½ΠΎΠ΅ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ для спСциалистов тСхничСской ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠΈ SMS/Ρ‚Π΅Π»Π΅ΠΊΠΎΠΌ-ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ΠΎΠ². Π£ΠΏΡ€ΠΎΡ‰Π°Π΅Ρ‚ Ρ€Π°Π±ΠΎΡ‚Ρƒ с шаблонами ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ² Π½Π° Ρ‚ΠΈΠΏΠΎΠ²Ρ‹Π΅ запросы ΠΊΠ»ΠΈΠ΅Π½Ρ‚ΠΎΠ² ΠΈ ΠΏΠ°Ρ€Ρ‚Π½Ρ‘Ρ€ΠΎΠ². + +## ✨ ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ возмоТности + +### Π€Π°Π·Π° 1 (v0.4.0) - Π“ΠžΠ’ΠžΠ’Πž βœ… + +#### πŸ“ Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ шаблонами +- **Π‘ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° Π³ΠΎΡ‚ΠΎΠ²Ρ‹Ρ… шаблонов** Π½Π° русском ΠΈ английском языках +- **Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΡ… шаблонов** с RU/EN вСрсиями +- **Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ встроСнных шаблонов** с сохранСниСм ΠΏΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΠΉ +- **Π˜ΠΌΠΏΠΎΡ€Ρ‚/экспорт шаблонов** Π² JSON для ΠΎΠ±ΠΌΠ΅Π½Π° с ΠΊΠΎΠ»Π»Π΅Π³Π°ΠΌΠΈ +- **Поиск ΠΏΠΎ шаблонам** ΠΏΠΎ названию ΠΈ содСрТимому + +#### πŸ’Ύ Бинхронизация +- **Chrome Storage Sync API** - автоматичСская синхронизация ΠΌΠ΅ΠΆΠ΄Ρƒ устройствами +- **АвтоматичСская миграция** Π΄Π°Π½Π½Ρ‹Ρ… ΠΈΠ· localStorage +- **Fallback Π½Π° localStorage** для локального тСстирования + +#### 🎨 ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠΉ интСрфСйс +- **Toast увСдомлСния** ΠΏΡ€ΠΈ ΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠΈ Π² Π±ΡƒΡ„Π΅Ρ€ ΠΎΠ±ΠΌΠ΅Π½Π° +- **Вёмная/свСтлая Ρ‚Π΅ΠΌΠ°** с сохранСниСм Π²Ρ‹Π±ΠΎΡ€Π° +- **ΠžΡ€Π³Π°Π½ΠΈΠ·Π°Ρ†ΠΈΡ ΠΏΠΎ катСгориям** с Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒΡŽ сворачивания +- **Π‘Ρ‡Ρ‘Ρ‚Ρ‡ΠΈΠΊ использования** для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ шаблона +- **Адаптивный Π΄ΠΈΠ·Π°ΠΉΠ½** + +## πŸš€ Установка + +### Firefox +1. Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ +2. ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ `about:debugging#/runtime/this-firefox` +3. НаТмитС "Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠ΅ Π΄ΠΎΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅" +4. Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ Ρ„Π°ΠΉΠ» `manifest.json` + +### Chrome/Edge +1. Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ +2. ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ `chrome://extensions/` +3. Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚Π΅ "Π Π΅ΠΆΠΈΠΌ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ°" +4. НаТмитС "Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ распакованноС Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅" +5. Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ ΠΏΠ°ΠΏΠΊΡƒ с Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ΠΌ + +## πŸ“– ИспользованиС + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ дСйствия + +1. **ΠšΠΎΠΏΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ шаблона**: НаТмитС ΠΊΠ½ΠΎΠΏΠΊΡƒ RU ΠΈΠ»ΠΈ ENG рядом с Π½ΡƒΠΆΠ½Ρ‹ΠΌ шаблоном +2. **Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ шаблона**: НаТмитС ΠΊΠ½ΠΎΠΏΠΊΡƒ "+" Π² Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ΅ +3. **Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅**: НаТмитС ΠΈΠΊΠΎΠ½ΠΊΡƒ ΠΊΠ°Ρ€Π°Π½Π΄Π°ΡˆΠ° рядом с шаблоном +4. **Π£Π΄Π°Π»Π΅Π½ΠΈΠ΅**: НаТмитС ΠΈΠΊΠΎΠ½ΠΊΡƒ ΠΊΠΎΡ€Π·ΠΈΠ½Ρ‹ +5. **Поиск**: Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ строку поиска Π² Π²Π΅Ρ€Ρ…Π½Π΅ΠΉ части ΠΎΠΊΠ½Π° + +### Π˜ΠΌΠΏΠΎΡ€Ρ‚/Экспорт + +**Экспорт:** +- НаТмитС ΠΊΠ½ΠΎΠΏΠΊΡƒ экспорта (πŸ“€) +- Π€Π°ΠΉΠ» сохранится ΠΊΠ°ΠΊ `izipus-templates-YYYY-MM-DD.json` + +**Π˜ΠΌΠΏΠΎΡ€Ρ‚:** +- НаТмитС ΠΊΠ½ΠΎΠΏΠΊΡƒ ΠΈΠΌΠΏΠΎΡ€Ρ‚Π° (πŸ“₯) +- Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ JSON Ρ„Π°ΠΉΠ» с шаблонами +- Π”ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚Ρ‹ (ΠΏΠΎ названию) Π±ΡƒΠ΄ΡƒΡ‚ автоматичСски ΠΏΡ€ΠΎΠΏΡƒΡ‰Π΅Π½Ρ‹ + +### Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ встроСнных шаблонов + +1. НаТмитС ΠΊΠ½ΠΎΠΏΠΊΡƒ рСдактирования (πŸ–ŠοΈ) Π½Π° встроСнном шаблонС +2. Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ язык (OK - RU, ΠžΡ‚ΠΌΠ΅Π½Π° - EN) +3. Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Π½ΠΎΠ²Ρ‹ΠΉ тСкст +4. ИзмСнСния ΡΠΎΡ…Ρ€Π°Π½ΡΡŽΡ‚ΡΡ автоматичСски + +## πŸ“‚ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +``` +lzipus/ +β”œβ”€β”€ manifest.json # ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ +β”œβ”€β”€ icons/ # Иконки Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ +β”‚ β”œβ”€β”€ icon16.png +β”‚ β”œβ”€β”€ icon48.png +β”‚ └── icon128.png +└── src/ + β”œβ”€β”€ popup.html # Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ + β”œβ”€β”€ popup.css # Π‘Ρ‚ΠΈΠ»ΠΈ + └── popup.js # Π›ΠΎΠ³ΠΈΠΊΠ° прилоТСния +``` + +## πŸ”§ Π’Π΅Ρ…Π½ΠΎΠ»ΠΎΠ³ΠΈΠΈ + +- **Manifest V3** - соврСмСнная вСрсия API Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠΉ +- **Chrome Storage Sync API** - синхронизация Π΄Π°Π½Π½Ρ‹Ρ… +- **Bootstrap 5.3.1** - UI ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ +- **Font Awesome 6.1.2** - ΠΈΠΊΠΎΠ½ΠΊΠΈ +- **Vanilla JavaScript** - Π±Π΅Π· Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊΠΎΠ² + +## πŸ“Š Π₯Ρ€Π°Π½Π΅Π½ΠΈΠ΅ Π΄Π°Π½Π½Ρ‹Ρ… + +ВсС Π΄Π°Π½Π½Ρ‹Π΅ хранятся Π² `chrome.storage.sync`: + +- `templates` - ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠ΅ ΡˆΠ°Π±Π»ΠΎΠ½Ρ‹ +- `builtinOverrides` - пСрСопрСдСлСния встроСнных шаблонов +- `usageStats` - статистика использования +- `theme` - выбранная Ρ‚Π΅ΠΌΠ° +- `categoryStates` - состояния ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΉ (ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΠΎ/Π·Π°ΠΊΡ€Ρ‹Ρ‚ΠΎ) + +## πŸ—ΊοΈ ДороТная ΠΊΠ°Ρ€Ρ‚Π° + +### Π€Π°Π·Π° 2: Новая Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ +- [ ] БистСма ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… Π² ΡˆΠ°Π±Π»ΠΎΠ½Π°Ρ… (`{{client_name}}`, `{{date}}`) +- [ ] ΠšΠΎΠ½Ρ‚Π΅ΠΊΡΡ‚Π½ΠΎΠ΅ мСню для быстрого доступа +- [ ] Π˜ΡΡ‚ΠΎΡ€ΠΈΡ использования ΠΈ статистика +- [ ] ΠœΡƒΠ»ΡŒΡ‚ΠΈΡΠ·Ρ‹Ρ‡Π½ΠΎΡΡ‚ΡŒ (ES, DE, FR) + +### Π€Π°Π·Π° 3: Π˜Π½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΡ +- [ ] Π˜Π½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΡ с CRM/Ρ‚ΠΈΠΊΠ΅Ρ‚-систСмами (Zendesk, Jira) +- [ ] AI-ассистСнт для прСдлоТСния шаблонов +- [ ] ΠšΠΎΠΌΠ°Π½Π΄Π½Ρ‹Π΅ возмоТности (ΠΎΠ±Π»Π°Ρ‡Π½ΠΎΠ΅ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅) + +### Π€Π°Π·Π° 4: ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ +- [ ] Π Π΅Ρ„Π°ΠΊΡ‚ΠΎΡ€ΠΈΠ½Π³ (ΠΌΠΎΠ΄ΡƒΠ»ΠΈ, TypeScript) +- [ ] Unit ΠΈ E2E тСсты +- [ ] CI/CD pipeline +- [ ] Полная докумСнтация + +## 🀝 Π’ΠΊΠ»Π°Π΄ Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ + +ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ для ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½ΠΈΠΉ! Если Ρƒ вас Π΅ΡΡ‚ΡŒ ΠΈΠ΄Π΅ΠΈ ΠΈΠ»ΠΈ Π²Ρ‹ нашли Π±Π°Π³: + +1. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ issue Π½Π° GitLab +2. ΠŸΡ€Π΅Π΄Π»ΠΎΠΆΠΈΡ‚Π΅ pull request +3. ΠžΠΏΠΈΡˆΠΈΡ‚Π΅ измСнСния ΠΈ ΠΈΡ… Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎΡΡ‚ΡŒ + +## πŸ“ Changelog + +### v0.4.0 (2025-10-26) - Π€Π°Π·Π° 1 +- βœ… Toast увСдомлСния ΠΏΡ€ΠΈ ΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠΈ +- βœ… Поиск ΠΏΠΎ шаблонам +- βœ… Π˜ΠΌΠΏΠΎΡ€Ρ‚/экспорт Π² JSON +- βœ… Π‘Ρ‡Ρ‘Ρ‚Ρ‡ΠΈΠΊ использования шаблонов +- βœ… ΠœΠΈΠ³Ρ€Π°Ρ†ΠΈΡ Π½Π° chrome.storage.sync +- βœ… Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ встроСнных шаблонов +- πŸ› Π˜ΡΠΏΡ€Π°Π²Π»Π΅Π½Π° ΠΎΠΏΠ΅Ρ‡Π°Ρ‚ΠΊΠ° Π² HTML + +### v0.3.1 +- Базовая Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ +- ВстроСнныС ΡˆΠ°Π±Π»ΠΎΠ½Ρ‹ +- ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠ΅ ΡˆΠ°Π±Π»ΠΎΠ½Ρ‹ +- Вёмная Ρ‚Π΅ΠΌΠ° + +## πŸ“„ ЛицСнзия + +ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ распространяСтся "ΠΊΠ°ΠΊ Π΅ΡΡ‚ΡŒ" для Π²Π½ΡƒΡ‚Ρ€Π΅Π½Π½Π΅Π³ΠΎ использования. + +## πŸ‘€ Автор + +**Dmitriy Gorshenin** +Email: dmitriy.gorshenin1@gmail.com +GitLab: https://git.gorshenin.info/Dgors03/Answer_Templates + +--- + +Made with ❀️ for SMS Support Teams + diff --git a/lzipus/manifest.json b/lzipus/manifest.json index 6c993f4..bb12e50 100644 --- a/lzipus/manifest.json +++ b/lzipus/manifest.json @@ -1,6 +1,6 @@ { "name": "Izipus", - "version": "0.3.1", + "version": "0.4.0", "manifest_version": 3, "description": "Lzipus - assists your workflow by simplifying interactions.", "homepage_url": "https://git.gorshenin.info/Dgors03/Answer_Templates", diff --git a/lzipus/src/popup.css b/lzipus/src/popup.css index 2cdb32b..aabb60a 100644 --- a/lzipus/src/popup.css +++ b/lzipus/src/popup.css @@ -8,6 +8,28 @@ body { box-sizing: border-box; } +/* ПлавноС появлСниС элСмСнтов */ +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +/* Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ */ +h1, h3 { + padding: 6px; +} + +h3 { + font-size: 16px; /* УмСньшСн Ρ€Π°Π·ΠΌΠ΅Ρ€ ΡˆΡ€ΠΈΡ„Ρ‚Π° */ + font-weight: normal; /* УмСньшСн вСс ΡˆΡ€ΠΈΡ„Ρ‚Π° для минималистичного Π²ΠΈΠ΄Π° */ + color: #333; /* Π¦Π²Π΅Ρ‚ ΡˆΡ€ΠΈΡ„Ρ‚Π° для ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΉ */ +} + +/* ΠšΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€Ρ‹ */ div.container { display: flex; flex-direction: column; @@ -18,26 +40,17 @@ div.container { div.header { display: flex; flex-direction: row; - justify-content: center; + justify-content: space-between; + align-items: center; padding: 6px; } -h1 { - padding: 6px; -} - -h3 { - padding: 6px; - font-family: Helvetica, Arial, sans-serif; /* Π‘ΠΎΠ»Π΅Π΅ минималистичный ΡˆΡ€ΠΈΡ„Ρ‚ для ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΉ */ - font-size: 16px; /* УмСньшСн Ρ€Π°Π·ΠΌΠ΅Ρ€ ΡˆΡ€ΠΈΡ„Ρ‚Π° */ - font-weight: normal; /* УмСньшСн вСс ΡˆΡ€ΠΈΡ„Ρ‚Π° для минималистичного Π²ΠΈΠ΄Π° */ - color: #333; /* Π¦Π²Π΅Ρ‚ ΡˆΡ€ΠΈΡ„Ρ‚Π° для ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΉ */ -} - -i { - width: 20px; +.header-buttons { + display: flex; + gap: 5px; } +/* Кнопки */ button.template-add, button.template-edit, button.template-delete { @@ -49,19 +62,19 @@ button.template-delete { cursor: pointer; font-size: 14px; /* МСньший Ρ€Π°Π·ΠΌΠ΅Ρ€ тСкста */ border-radius: 5px; /* Π‘ΠΊΡ€ΡƒΠ³Π»Π΅Π½Π½Ρ‹Π΅ ΡƒΠ³Π»Ρ‹ */ - transition: background-color 0.3s, color 0.3s, border-color 0.3s; /* ΠŸΠ»Π°Π²Π½Ρ‹Π΅ ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄Ρ‹ */ + transition: background-color 0.3s, color 0.3s, border-color 0.3s, transform 0.3s; /* ΠŸΠ»Π°Π²Π½Ρ‹Π΅ ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄Ρ‹ */ } -/* Π­Ρ„Ρ„Π΅ΠΊΡ‚ ΠΏΡ€ΠΈ Π½Π°Π²Π΅Π΄Π΅Π½ΠΈΠΈ */ +/* Π­Ρ„Ρ„Π΅ΠΊΡ‚Ρ‹ для ΠΊΠ½ΠΎΠΏΠΎΠΊ */ button.template-add:hover, button.template-edit:hover, button.template-delete:hover { background-color: #abcef5; /* ΠŸΠΎΠ΄ΡΠ²Π΅Ρ‚ΠΊΠ° Ρ„ΠΎΠ½ΠΎΠΌ */ color: #fff; /* Π‘Π΅Π»Ρ‹ΠΉ Ρ†Π²Π΅Ρ‚ тСкста */ border-color: #88aee5; /* НСмного Ρ‚Π΅ΠΌΠ½Π΅Π΅ Ρ€Π°ΠΌΠΊΠ° */ + transform: scale(1.05); /* Π£Π²Π΅Π»ΠΈΡ‡Π΅Π½ΠΈΠ΅ ΠΊΠ½ΠΎΠΏΠΊΠΈ ΠΏΡ€ΠΈ Π½Π°Π²Π΅Π΄Π΅Π½ΠΈΠΈ */ } -/* Π­Ρ„Ρ„Π΅ΠΊΡ‚ ΠΏΡ€ΠΈ фокусС */ button.template-add:focus, button.template-edit:focus, button.template-delete:focus { @@ -69,6 +82,7 @@ button.template-delete:focus { box-shadow: 0 0 3px rgba(0, 0, 0, 0.2); /* ЛСгкая Ρ‚Π΅Π½ΡŒ для фокуса */ } +/* Π¨Π°Π±Π»ΠΎΠ½Ρ‹ */ div.templates { display: flex; flex-direction: column; @@ -83,12 +97,16 @@ span.template { justify-content: space-between; height: fit-content; padding: 8px 7px; + justify-content: space-between; + height: fit-content; + padding: 8px 7px; border: 1px solid #8888c6; /* Π¦Π²Π΅Ρ‚ Ρ€Π°ΠΌΠΊΠΈ совпадаСт с Ρ€Π°ΠΌΠΊΠΎΠΉ ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΈ */ border-radius: 8px; /* Π‘ΠΊΡ€ΡƒΠ³Π»Π΅Π½Π½Ρ‹Π΅ ΡƒΠ³Π»Ρ‹ */ overflow: hidden; width: 100%; /* Π¨Π°Π±Π»ΠΎΠ½Ρ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Ρ€Π°ΡΡ‚ΡΠ³ΠΈΠ²Π°Ρ‚ΡŒΡΡ Π½Π° всю ΡˆΠΈΡ€ΠΈΠ½Ρƒ */ box-sizing: border-box; /* Π§Ρ‚ΠΎΠ±Ρ‹ ΠΏΠ°Π΄Π΄ΠΈΠ½Π³ΠΈ Π½Π΅ влияли Π½Π° Ρ€Π°Π·ΠΌΠ΅Ρ€ */ transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; /* ΠŸΠ»Π°Π²Π½Ρ‹ΠΉ ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄ ΠΏΡ€ΠΈ Π½Π°Π²Π΅Π΄Π΅Π½ΠΈΠΈ */ + animation: fadeIn 0.5s ease-in; /* ΠŸΡ€ΠΈΠΌΠ΅Π½ΡΠ΅ΠΌ Π°Π½ΠΈΠΌΠ°Ρ†ΠΈΡŽ ΠΊ элСмСнтам шаблона */ } /* ΠŸΠΎΠ΄ΡΠ²Π΅Ρ‚ΠΊΠ° шаблонов ΠΏΡ€ΠΈ Π½Π°Π²Π΅Π΄Π΅Π½ΠΈΠΈ */ @@ -265,3 +283,112 @@ textarea { resize: none; } +/* Toast увСдомлСния */ +.toast-container { + position: fixed; + top: 20px; + right: 20px; + z-index: 9999; + display: flex; + flex-direction: column; + gap: 10px; +} + +.toast { + background: #4CAF50; + color: white; + padding: 12px 20px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + display: flex; + align-items: center; + gap: 10px; + min-width: 250px; + animation: slideIn 0.3s ease-out, fadeOut 0.3s ease-in 2.7s; + opacity: 0; + animation-fill-mode: forwards; +} + +.toast.show { + opacity: 1; +} + +.toast i { + font-size: 18px; +} + +.toast-message { + flex: 1; + font-size: 14px; +} + +@keyframes slideIn { + from { + transform: translateX(400px); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes fadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +.dark-theme .toast { + background: #388E3C; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); +} + +/* Поиск */ +.search-wrapper { + position: relative; + margin: 10px 0; + width: 100%; +} + +.search-wrapper input { + width: 100%; + padding: 10px 40px 10px 15px; + border: 1px solid #8888c6; + border-radius: 8px; + font-size: 14px; + transition: border-color 0.3s; +} + +.search-wrapper input:focus { + outline: none; + border-color: #a56fbf; + box-shadow: 0 0 5px rgba(165, 111, 191, 0.3); +} + +.search-icon { + position: absolute; + right: 15px; + top: 50%; + transform: translateY(-50%); + color: #8888c6; + pointer-events: none; +} + +.dark-theme .search-wrapper input { + background: #444; + color: #fff; + border-color: #555; +} + +.dark-theme .search-icon { + color: #aaa; +} + +/* Π‘ΠΊΡ€Ρ‹Ρ‚ΠΈΠ΅ элСмСнтов ΠΏΡ€ΠΈ поискС */ +.hidden-by-search { + display: none !important; +} diff --git a/lzipus/src/popup.html b/lzipus/src/popup.html index b593ba2..a2a958e 100644 --- a/lzipus/src/popup.html +++ b/lzipus/src/popup.html @@ -11,16 +11,34 @@ +

Π’Π²ΠΎΠΈ ΡˆΠ°Π±Π»ΠΎΠ½Ρ‹

- +
+ + + +
-
+ + +
+ + +
+ +

ΠŸΡ€ΠΎΠΌΠ΅ΠΆΡƒΡ‚ΠΎΡ‡Π½Ρ‹Π΅ ΠΎΡ‚Π²Π΅Ρ‚Ρ‹ @@ -28,12 +46,12 @@

- + ΠŸΡ€ΠΈΠ½ΡΠ»ΠΈ Π² Ρ€Π°Π±ΠΎΡ‚Ρƒ RU ENG - + @@ -277,7 +295,7 @@
- +
diff --git a/lzipus/src/popup.js b/lzipus/src/popup.js index 9921715..3f577b5 100644 --- a/lzipus/src/popup.js +++ b/lzipus/src/popup.js @@ -1,6 +1,124 @@ -document.addEventListener("DOMContentLoaded", function () { +document.addEventListener("DOMContentLoaded", async () => { + // Storage Helper - ΠΎΠ±Ρ‘Ρ€Ρ‚ΠΊΠ° для chrome.storage.sync с fallback Π½Π° localStorage + const StorageHelper = { + async get(key) { + if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.sync) { + return new Promise((resolve) => { + chrome.storage.sync.get([key], (result) => { + resolve(result[key]); + }); + }); + } else { + // Fallback для локального тСстирования + const value = localStorage.getItem(key); + return value ? JSON.parse(value) : null; + } + }, + + async set(key, value) { + if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.sync) { + return new Promise((resolve) => { + chrome.storage.sync.set({ [key]: value }, resolve); + }); + } else { + // Fallback для локального тСстирования + localStorage.setItem(key, JSON.stringify(value)); + return Promise.resolve(); + } + }, + + async remove(key) { + if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.sync) { + return new Promise((resolve) => { + chrome.storage.sync.remove(key, resolve); + }); + } else { + localStorage.removeItem(key); + return Promise.resolve(); + } + } + }; + + // ΠœΠΈΠ³Ρ€Π°Ρ†ΠΈΡ Π΄Π°Π½Π½Ρ‹Ρ… ΠΈΠ· localStorage Π² chrome.storage.sync + async function migrateFromLocalStorage() { + const migrationKey = 'migrated_to_chrome_storage'; + const alreadyMigrated = await StorageHelper.get(migrationKey); + + if (!alreadyMigrated && typeof chrome !== 'undefined' && chrome.storage && chrome.storage.sync) { + console.log('НачинаСм ΠΌΠΈΠ³Ρ€Π°Ρ†ΠΈΡŽ Π΄Π°Π½Π½Ρ‹Ρ… ΠΈΠ· localStorage Π² chrome.storage.sync...'); + + const keysToMigrate = ['templates', 'theme', 'usageStats']; + + for (const key of keysToMigrate) { + const localValue = localStorage.getItem(key); + if (localValue) { + try { + const parsedValue = JSON.parse(localValue); + await StorageHelper.set(key, parsedValue); + console.log(`ΠœΠΈΠ³Ρ€ΠΈΡ€ΠΎΠ²Π°Π½ ΠΊΠ»ΡŽΡ‡: ${key}`); + } catch (e) { + console.error(`Ошибка ΠΏΡ€ΠΈ ΠΌΠΈΠ³Ρ€Π°Ρ†ΠΈΠΈ ΠΊΠ»ΡŽΡ‡Π° ${key}:`, e); + } + } + } + + // ΠœΠΈΠ³Ρ€Π°Ρ†ΠΈΡ состояний ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΉ + const categoryStates = {}; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && !keysToMigrate.includes(key)) { + const value = localStorage.getItem(key); + if (value === 'true' || value === 'false') { + categoryStates[key] = value; + } + } + } + + if (Object.keys(categoryStates).length > 0) { + await StorageHelper.set('categoryStates', categoryStates); + console.log('ΠœΠΈΠ³Ρ€ΠΈΡ€ΠΎΠ²Π°Π½Ρ‹ состояния ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΉ'); + } + + await StorageHelper.set(migrationKey, true); + console.log('ΠœΠΈΠ³Ρ€Π°Ρ†ΠΈΡ Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°!'); + } + } + + await migrateFromLocalStorage(); + + // Бтатистика использования шаблонов + let usageStats = await StorageHelper.get('usageStats') || {}; + + async function updateUsageStats(templateId) { + usageStats[templateId] = (usageStats[templateId] || 0) + 1; + await StorageHelper.set('usageStats', usageStats); + } + + function getUsageCount(templateId) { + return usageStats[templateId] || 0; + } + + // Toast увСдомлСния + function showToast(message, duration = 3000) { + const toastContainer = document.getElementById('toast-container'); + const toast = document.createElement('div'); + toast.className = 'toast show'; + toast.innerHTML = ` + + ${message} + `; + toastContainer.appendChild(toast); + + setTimeout(() => { + toast.remove(); + }, duration); + } + + // Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ пСрСопрСдСлСния встроСнных шаблонов + let builtinOverrides = await StorageHelper.get('builtinOverrides') || {}; + // ΠžΠ±ΡŠΠ΅ΠΊΡ‚ с тСкстами для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ элСмСнта - const clipboardTexts = { + let clipboardTexts = { "inWorkRu": "КоллСги, здравствуйтС.\nΠŸΡ€ΠΈΠ½ΡΠ»ΠΈ Π’Π°Ρˆ запрос Π² Ρ€Π°Π±ΠΎΡ‚Ρƒ. Π‘ΠΎΠΎΠ±Ρ‰ΠΈΠΌ ΠΏΠΎ ΠΌΠ΅Ρ€Π΅ поступлСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ.", "inWorkEn": "Dear customer,\nWe are working on your request.", "dialogueRu": "КоллСги, здравствуйтС.\nУвСдомляСм вас ΠΎ Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎ ΠΌΡ‹ Π½Π°Ρ‡Π°Π»ΠΈ Π΄ΠΈΠ°Π»ΠΎΠ³ с ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ΠΎΠΌ ΠΏΠΎ Π΄Π°Π½Π½ΠΎΠΌΡƒ запросу. ΠœΡ‹ ΠΎΠΆΠΈΠ΄Π°Π΅ΠΌ ΠΎΡ‚Π²Π΅Ρ‚Π° ΠΈ Π±ΡƒΠ΄Π΅ΠΌ Π΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ вас Π² курсС Π»ΡŽΠ±Ρ‹Ρ… ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ.", @@ -59,11 +177,59 @@ document.addEventListener("DOMContentLoaded", function () { "HLREn": "Dear colleagues,\nPlease clarify the correctness of HLR statuses for the following subscriber numbers:", "managerTemplate": "Π‘Ρ‚Ρ€Π°Π½Π°: \nΠžΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€: \nmccMnc: \nΠ‘Π΅Π½Π΄Π΅Ρ€: \nΠšΠ»ΠΈΠ΅Π½Ρ‚: \nSource node: \nDestination node: " }; + + // ΠŸΡ€ΠΈΠΌΠ΅Π½ΡΠ΅ΠΌ пСрСопрСдСлСния + Object.keys(builtinOverrides).forEach(key => { + if (clipboardTexts.hasOwnProperty(key)) { + clipboardTexts[key] = builtinOverrides[key]; + } + }); + + // Ѐункция для рСдактирования встроСнных шаблонов + async function editBuiltinTemplate(templateId) { + const newText = prompt('Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Π½ΠΎΠ²Ρ‹ΠΉ тСкст шаблона:', clipboardTexts[templateId]); + if (newText !== null && newText !== clipboardTexts[templateId]) { + clipboardTexts[templateId] = newText; + builtinOverrides[templateId] = newText; + await StorageHelper.set('builtinOverrides', builtinOverrides); + showToast('ВстроСнный шаблон ΠΎΠ±Π½ΠΎΠ²Π»Ρ‘Π½!'); + } + } + + // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΈ для ΠΊΠ½ΠΎΠΏΠΎΠΊ рСдактирования встроСнных шаблонов + document.querySelectorAll('.template-edit-builtin').forEach(btn => { + const template = btn.closest('.template'); + btn.addEventListener('click', () => { + // Находим RU ΠΈ EN ΠΊΠ½ΠΎΠΏΠΊΠΈ + const ruBtn = template.querySelector('[id$="Ru"]'); + const enBtn = template.querySelector('[id$="En"]'); + + if (ruBtn || enBtn) { + const templateName = template.querySelector('.template-title').textContent.trim(); + const action = confirm(`Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ шаблон "${templateName}"?\nОК - Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ RU\nΠžΡ‚ΠΌΠ΅Π½Π° - Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ EN`); + + if (action && ruBtn) { + editBuiltinTemplate(ruBtn.id); + } else if (!action && enBtn) { + editBuiltinTemplate(enBtn.id); + } + } + }); + }); // Ѐункция для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ ΠΊΠ»ΠΈΠΊΠΎΠ² function handleClipboardClick(buttonId) { if (clipboardTexts[buttonId]) { - navigator.clipboard.writeText(clipboardTexts[buttonId]); + navigator.clipboard.writeText(clipboardTexts[buttonId]) + .then(() => { + updateUsageStats(buttonId); + const count = getUsageCount(buttonId); + showToast(`ВСкст скопирован! (Использований: ${count})`); + }) + .catch(err => { + console.error('НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΡΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ тСкст: ', err); + showToast('Ошибка копирования'); + }); } } @@ -121,12 +287,12 @@ function createTemplate(title, RUText, ENText) { }); // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ для удалСния - deleteBtn.addEventListener('click', () => { - const templates = JSON.parse(localStorage.getItem('templates')) || []; + deleteBtn.addEventListener('click', async () => { + const templates = await StorageHelper.get('templates') || []; const templateIndex = templates.findIndex(template => template.title === titleEl.innerText); if (templateIndex !== -1) { templates.splice(templateIndex, 1); - localStorage.setItem('templates', JSON.stringify(templates)); + await StorageHelper.set('templates', templates); } templateEl.remove(); }); @@ -144,9 +310,18 @@ function createTemplate(title, RUText, ENText) { // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ для копирования тСкста Π² Π±ΡƒΡ„Π΅Ρ€ ΠΎΠ±ΠΌΠ΅Π½Π° const handleCopyText = (e, key) => { + const templateId = `custom-${title}-${key}`; navigator.clipboard.writeText(e.target.dataset[key]) - .then(() => console.log(`ВСкст ${key} скопирован Π² Π±ΡƒΡ„Π΅Ρ€ ΠΎΠ±ΠΌΠ΅Π½Π°`)) - .catch(err => console.error(`НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΡΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ тСкст ${key}: `, err)); + .then(() => { + updateUsageStats(templateId); + const count = getUsageCount(templateId); + showToast(`ВСкст скопирован! (Использований: ${count})`); + console.log(`ВСкст ${key} скопирован Π² Π±ΡƒΡ„Π΅Ρ€ ΠΎΠ±ΠΌΠ΅Π½Π°`); + }) + .catch(err => { + console.error(`НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΡΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ тСкст ${key}: `, err); + showToast('Ошибка копирования'); + }); }; textRUEl.addEventListener('click', (e) => handleCopyText(e, 'ruText')); @@ -155,19 +330,19 @@ function createTemplate(title, RUText, ENText) { return templateEl; } -function saveTemplateToLocalStorage(title, RUText, ENText) { +async function saveTemplateToLocalStorage(title, RUText, ENText) { const template = { title, RUText, ENText }; - const templates = JSON.parse(localStorage.getItem('templates')) || []; + const templates = await StorageHelper.get('templates') || []; templates.push(template); - localStorage.setItem('templates', JSON.stringify(templates)); + await StorageHelper.set('templates', templates); } -function updateTemplateInLocalStorage(title, key, value) { - const templates = JSON.parse(localStorage.getItem('templates')) || []; +async function updateTemplateInLocalStorage(title, key, value) { + const templates = await StorageHelper.get('templates') || []; const templateIndex = templates.findIndex(template => template.title === title); if (templateIndex !== -1) { templates[templateIndex][key] = value; - localStorage.setItem('templates', JSON.stringify(templates)); + await StorageHelper.set('templates', templates); } } @@ -181,8 +356,8 @@ addBtn.addEventListener('click', () => { saveTemplateToLocalStorage(title, RUText, ENText); }); -window.onload = function () { - const templates = JSON.parse(localStorage.getItem('templates')) || []; +window.onload = async function () { + const templates = await StorageHelper.get('templates') || []; templates.forEach(template => { const el = createTemplate(template.title, template.RUText, template.ENText); templatesEl.appendChild(el); @@ -195,8 +370,8 @@ const themeToggle = document.getElementById('theme-toggle'); const themeIcon = document.getElementById('theme-icon'); // ΠŸΡ€ΠΈΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ сохранСнной Ρ‚Π΅ΠΌΡ‹ -const applySavedTheme = () => { - const savedTheme = localStorage.getItem('theme'); +const applySavedTheme = async () => { + const savedTheme = await StorageHelper.get('theme'); if (savedTheme === 'dark') { document.body.classList.add('dark-theme'); if (themeIcon) { @@ -212,10 +387,10 @@ const applySavedTheme = () => { // ΠŸΠ΅Ρ€Π΅ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ Ρ‚Π΅ΠΌΡ‹ if (themeToggle && themeIcon) { - themeToggle.addEventListener('click', () => { + themeToggle.addEventListener('click', async () => { document.body.classList.toggle('dark-theme'); const theme = document.body.classList.contains('dark-theme') ? 'dark' : 'light'; - localStorage.setItem('theme', theme); // БохраняСм Π²Ρ‹Π±Ρ€Π°Π½Π½ΡƒΡŽ Ρ‚Π΅ΠΌΡƒ + await StorageHelper.set('theme', theme); // БохраняСм Π²Ρ‹Π±Ρ€Π°Π½Π½ΡƒΡŽ Ρ‚Π΅ΠΌΡƒ themeIcon.classList.replace(theme === 'dark' ? 'fa-moon' : 'fa-sun', theme === 'dark' ? 'fa-sun' : 'fa-moon'); }); } @@ -224,13 +399,14 @@ if (themeToggle && themeIcon) { applySavedTheme(); // Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ катСгориями -document.querySelectorAll('.category h3').forEach(function(header) { +document.querySelectorAll('.category h3').forEach(async function(header) { const categoryId = header.textContent.trim(); // Π£Π½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΈ ΠΏΠΎ названию const content = header.nextElementSibling; // Π‘ΠΎΠ΄Π΅Ρ€ΠΆΠΈΠΌΠΎΠ΅ ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΈ const icon = header.querySelector('.toggle-icon'); // Иконка стрСлки // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° состояния ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΈ ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ страницы - const isCategoryOpen = localStorage.getItem(categoryId) === 'true'; + const categoryStates = await StorageHelper.get('categoryStates') || {}; + const isCategoryOpen = categoryStates[categoryId] === 'true'; if (isCategoryOpen) { content.classList.add('show'); @@ -245,12 +421,180 @@ document.querySelectorAll('.category h3').forEach(function(header) { } // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ ΠΊΠ»ΠΈΠΊΠ° ΠΏΠΎ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΡƒ ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΈ - header.addEventListener('click', function() { + header.addEventListener('click', async function() { content.classList.toggle('show'); // ΠŸΠ΅Ρ€Π΅ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡ‚ΡŒ ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΈ icon.classList.toggle('fa-chevron-down'); icon.classList.toggle('fa-chevron-up'); // БохраняСм состояниС ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΈ ΠΊΠ°ΠΊ ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΠΎΠ΅ ΠΈΠ»ΠΈ Π·Π°ΠΊΡ€Ρ‹Ρ‚ΠΎΠ΅ - localStorage.setItem(categoryId, content.classList.contains('show') ? 'true' : 'false'); + const categoryStates = await StorageHelper.get('categoryStates') || {}; + categoryStates[categoryId] = content.classList.contains('show') ? 'true' : 'false'; + await StorageHelper.set('categoryStates', categoryStates); }); }); + +// Π˜ΠΌΠΏΠΎΡ€Ρ‚/Экспорт шаблонов +const exportBtn = document.querySelector('.export-btn'); +const importBtn = document.querySelector('.import-btn'); +const importFileInput = document.getElementById('import-file-input'); + +if (exportBtn) { + exportBtn.addEventListener('click', async () => { + const templates = await StorageHelper.get('templates') || []; + const dataStr = JSON.stringify(templates, null, 2); + const dataBlob = new Blob([dataStr], { type: 'application/json' }); + + const url = URL.createObjectURL(dataBlob); + const link = document.createElement('a'); + link.href = url; + link.download = `izipus-templates-${new Date().toISOString().split('T')[0]}.json`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + + showToast(`Экспортировано ${templates.length} шаблонов`); + }); +} + +if (importBtn && importFileInput) { + importBtn.addEventListener('click', () => { + importFileInput.click(); + }); + + importFileInput.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = async (event) => { + try { + const importedTemplates = JSON.parse(event.target.result); + + if (!Array.isArray(importedTemplates)) { + showToast('НСвСрный Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ Ρ„Π°ΠΉΠ»Π°'); + return; + } + + // Валидация шаблонов + const validTemplates = importedTemplates.filter(t => + t.title && typeof t.title === 'string' && + t.RUText && typeof t.RUText === 'string' && + t.ENText && typeof t.ENText === 'string' + ); + + if (validTemplates.length === 0) { + showToast('НСт Π²Π°Π»ΠΈΠ΄Π½Ρ‹Ρ… шаблонов для ΠΈΠΌΠΏΠΎΡ€Ρ‚Π°'); + return; + } + + // ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠ΅ ΡˆΠ°Π±Π»ΠΎΠ½Ρ‹ + const existingTemplates = await StorageHelper.get('templates') || []; + + // ОбъСдиняСм ΡˆΠ°Π±Π»ΠΎΠ½Ρ‹ (ΠΈΠ·Π±Π΅Π³Π°Π΅ΠΌ Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² ΠΏΠΎ названию) + const existingTitles = new Set(existingTemplates.map(t => t.title)); + const newTemplates = validTemplates.filter(t => !existingTitles.has(t.title)); + + if (newTemplates.length === 0) { + showToast('ВсС ΡˆΠ°Π±Π»ΠΎΠ½Ρ‹ ΡƒΠΆΠ΅ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‚'); + return; + } + + const mergedTemplates = [...existingTemplates, ...newTemplates]; + await StorageHelper.set('templates', mergedTemplates); + + // ДобавляСм Π½ΠΎΠ²Ρ‹Π΅ ΡˆΠ°Π±Π»ΠΎΠ½Ρ‹ Π½Π° страницу + newTemplates.forEach(template => { + const el = createTemplate(template.title, template.RUText, template.ENText); + templatesEl.appendChild(el); + }); + + showToast(`Π˜ΠΌΠΏΠΎΡ€Ρ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΎ ${newTemplates.length} Π½ΠΎΠ²Ρ‹Ρ… шаблонов`); + } catch (error) { + console.error('Ошибка ΠΈΠΌΠΏΠΎΡ€Ρ‚Π°:', error); + showToast('Ошибка ΠΏΡ€ΠΈ ΠΈΠΌΠΏΠΎΡ€Ρ‚Π΅ Ρ„Π°ΠΉΠ»Π°'); + } + + // БбрасываСм input + importFileInput.value = ''; + }; + + reader.readAsText(file); + }); +} + +// Поиск ΠΏΠΎ шаблонам +const searchInput = document.getElementById('search-input'); +if (searchInput) { + searchInput.addEventListener('input', function(e) { + const searchTerm = e.target.value.toLowerCase().trim(); + const categories = document.querySelectorAll('.category'); + + if (searchTerm === '') { + // Если поиск пуст, ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅ΠΌ всС ΡˆΠ°Π±Π»ΠΎΠ½Ρ‹ + categories.forEach(category => { + category.classList.remove('hidden-by-search'); + const templates = category.querySelectorAll('.template'); + templates.forEach(template => { + template.classList.remove('hidden-by-search'); + }); + }); + return; + } + + // Π˜Ρ‰Π΅ΠΌ ΠΏΠΎ всСм катСгориям + categories.forEach(category => { + const templates = category.querySelectorAll('.template'); + let hasVisibleTemplates = false; + + templates.forEach(template => { + // ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ шаблона + const titleEl = template.querySelector('.template-title, #template-title'); + const title = titleEl ? titleEl.textContent.toLowerCase() : ''; + + // ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ содСрТимоС шаблонов (RU ΠΈ EN) + const ruBtn = template.querySelector('[id$="Ru"], [data-ru-text]'); + const enBtn = template.querySelector('[id$="En"], [data-en-text]'); + + let ruText = ''; + let enText = ''; + + if (ruBtn) { + ruText = (ruBtn.dataset.ruText || clipboardTexts[ruBtn.id] || '').toLowerCase(); + } + + if (enBtn) { + enText = (enBtn.dataset.enText || clipboardTexts[enBtn.id] || '').toLowerCase(); + } + + // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ совпадСниС + const matches = title.includes(searchTerm) || + ruText.includes(searchTerm) || + enText.includes(searchTerm); + + if (matches) { + template.classList.remove('hidden-by-search'); + hasVisibleTemplates = true; + } else { + template.classList.add('hidden-by-search'); + } + }); + + // Π‘ΠΊΡ€Ρ‹Π²Π°Π΅ΠΌ ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΡŽ, Ссли Π² Π½Π΅ΠΉ Π½Π΅Ρ‚ Π²ΠΈΠ΄ΠΈΠΌΡ‹Ρ… шаблонов + if (hasVisibleTemplates) { + category.classList.remove('hidden-by-search'); + // ΠžΡ‚ΠΊΡ€Ρ‹Π²Π°Π΅ΠΌ ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΡŽ ΠΏΡ€ΠΈ поискС + const content = category.querySelector('.category-content'); + if (content && !content.classList.contains('show')) { + content.classList.add('show'); + const icon = category.querySelector('.toggle-icon'); + if (icon) { + icon.classList.replace('fa-chevron-down', 'fa-chevron-up'); + } + } + } else { + category.classList.add('hidden-by-search'); + } + }); + }); +} }); \ No newline at end of file