Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c53f174959 | |||
| 5c11d21290 | |||
| e08e220c19 | |||
| 93b67f2e73 | |||
| 86936cdf05 | |||
| 19c5af6e68 | |||
| b89af2ca0d |
101
lzipus/CHANGELOG.md
Normal file
101
lzipus/CHANGELOG.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Changelog
|
||||
|
||||
Все значимые изменения в проекте Izipus документируются в этом файле.
|
||||
|
||||
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.0.0/),
|
||||
версионирование следует [Semantic Versioning](https://semver.org/lang/ru/).
|
||||
|
||||
## [0.4.0] - 2025-10-26
|
||||
|
||||
### Добавлено
|
||||
- 🎉 **Toast уведомления**: Визуальное подтверждение при копировании шаблонов с отображением счётчика использований
|
||||
- 🔍 **Поиск по шаблонам**: Мгновенный поиск по названию и содержимому шаблонов (RU/EN)
|
||||
- Автоматическое открытие категорий с найденными шаблонами
|
||||
- Скрытие категорий без результатов
|
||||
- 📤 **Экспорт шаблонов**: Сохранение пользовательских шаблонов в JSON файл
|
||||
- Формат имени файла: `izipus-templates-YYYY-MM-DD.json`
|
||||
- Валидация данных перед экспортом
|
||||
- 📥 **Импорт шаблонов**: Загрузка шаблонов из JSON файла
|
||||
- Автоматическая валидация структуры
|
||||
- Предотвращение дубликатов по названию
|
||||
- Информирование о количестве импортированных шаблонов
|
||||
- 📊 **Счётчик использования**: Отслеживание частоты использования каждого шаблона
|
||||
- Отображение в toast уведомлениях
|
||||
- Раздельная статистика для встроенных и пользовательских шаблонов
|
||||
- 🖊️ **Редактирование встроенных шаблонов**: Возможность переопределять встроенные шаблоны
|
||||
- Система overrides с сохранением в chrome.storage
|
||||
- Выбор языка при редактировании (RU/EN)
|
||||
- 💾 **Chrome Storage Sync API**: Синхронизация данных между устройствами
|
||||
- Автоматическая миграция из localStorage
|
||||
- Fallback на localStorage для локального тестирования
|
||||
- Синхронизация: шаблонов, статистики, темы, состояния категорий
|
||||
- 🎨 **Улучшенный UI заголовка**: Группировка кнопок управления
|
||||
- Кнопки добавления, импорта и экспорта объединены в header-buttons
|
||||
|
||||
### Изменено
|
||||
- 📦 **Версия**: Обновлена с 0.3.1 до 0.4.0
|
||||
- 🔄 **Storage API**: Все операции с хранилищем переведены на chrome.storage.sync
|
||||
- `templates` - пользовательские шаблоны
|
||||
- `builtinOverrides` - переопределения встроенных шаблонов
|
||||
- `usageStats` - статистика использования
|
||||
- `theme` - текущая тема
|
||||
- `categoryStates` - состояния категорий
|
||||
- ⚡ **Асинхронные операции**: Переход на async/await для всех операций хранения
|
||||
- 🎯 **Header layout**: Изменён с `justify-content: center` на `space-between`
|
||||
|
||||
### Исправлено
|
||||
- 🐛 **HTML опечатка**: Исправлен тег `</1--div>` на корректный `</div>` (строка 280 в popup.html)
|
||||
- ✨ **Анимации**: Улучшены анимации toast уведомлений
|
||||
- slideIn: плавное появление справа
|
||||
- fadeOut: постепенное исчезновение через 2.7 секунды
|
||||
|
||||
### Технические детали
|
||||
- Добавлен `StorageHelper` - универсальная обёртка для работы с хранилищем
|
||||
- Функция `migrateFromLocalStorage()` для одноразовой миграции данных
|
||||
- Новые CSS классы: `.toast-container`, `.toast`, `.search-wrapper`, `.hidden-by-search`, `.header-buttons`
|
||||
- Улучшена структура кода с использованием async/await
|
||||
|
||||
### Производительность
|
||||
- Оптимизирован поиск с использованием `includes()` вместо регулярных выражений
|
||||
- Кэширование состояния категорий для быстрого доступа
|
||||
- Ленивая загрузка шаблонов при импорте
|
||||
|
||||
## [0.3.1] - Предыдущая версия
|
||||
|
||||
### Возможности
|
||||
- Базовая библиотека встроенных шаблонов (60+ шаблонов)
|
||||
- Создание пользовательских шаблонов
|
||||
- Организация по категориям:
|
||||
- Промежуточные ответы
|
||||
- Запросы партнерам
|
||||
- Ответы клиентам
|
||||
- Даофис (специфичные шаблоны)
|
||||
- Тёмная/светлая тема
|
||||
- Сворачивание категорий с сохранением состояния
|
||||
- Копирование в буфер обмена одним кликом
|
||||
- Редактирование и удаление пользовательских шаблонов
|
||||
|
||||
---
|
||||
|
||||
## Планы на будущее
|
||||
|
||||
### [0.5.0] - Фаза 2: Переменные и история
|
||||
- Система переменных: `{{client_name}}`, `{{ticket_id}}`, `{{date}}`
|
||||
- История последних использованных шаблонов
|
||||
- Категория "Недавние" для быстрого доступа
|
||||
- Графики и статистика популярности шаблонов
|
||||
|
||||
### [0.6.0] - Фаза 2: Интеграция
|
||||
- Context menu для быстрого доступа
|
||||
- Content script для вставки в текстовые поля
|
||||
- Горячие клавиши для избранных шаблонов
|
||||
|
||||
### [1.0.0] - Стабильный релиз
|
||||
- Полная локализация интерфейса
|
||||
- Интеграция с Zendesk/Jira
|
||||
- Облачная синхронизация для команд
|
||||
- Полное тестовое покрытие
|
||||
|
||||
[0.4.0]: https://git.gorshenin.info/Dgors03/Answer_Templates/compare/v0.3.1...v0.4.0
|
||||
[0.3.1]: https://git.gorshenin.info/Dgors03/Answer_Templates/releases/tag/v0.3.1
|
||||
|
||||
166
lzipus/README.md
Normal file
166
lzipus/README.md
Normal file
@@ -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
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "Izipus",
|
||||
"version": "1.1.0",
|
||||
"manifest_version": 2,
|
||||
"description": "Izipus -- help your a...",
|
||||
"homepage_url": "https://github.com/AM-EO/izipus",
|
||||
"version": "0.4.0",
|
||||
"manifest_version": 3,
|
||||
"description": "Lzipus - assists your workflow by simplifying interactions.",
|
||||
"homepage_url": "https://git.gorshenin.info/Dgors03/Answer_Templates",
|
||||
"icons": {
|
||||
"16": "icons/icon16.png",
|
||||
"48": "icons/icon48.png",
|
||||
"128": "icons/icon128.png"
|
||||
},
|
||||
"browser_action": {
|
||||
"default_icon": "icons/icon19.png",
|
||||
"default_title": "browser action demo",
|
||||
"default_popup": "src/browser_action/popup.html"
|
||||
"action": {
|
||||
"default_icon": "icons/icon48.png",
|
||||
"default_title": "Izipus - Quick Actions",
|
||||
"default_popup": "src/popup.html"
|
||||
},
|
||||
"permissions": [
|
||||
"clipboardRead",
|
||||
@@ -20,5 +20,10 @@
|
||||
"contextMenus",
|
||||
"storage",
|
||||
"cookies"
|
||||
]
|
||||
}
|
||||
],
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "dmitriy.gorshenin1@gmail.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 300px;
|
||||
max-width: 3000px;
|
||||
width: 400px;
|
||||
background: #abcef5; /* Светлый фон */
|
||||
font-family: Helvetica, Arial, sans-serif; /* Более минималистичный шрифт */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
div.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: 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;
|
||||
}
|
||||
|
||||
button.template-add,
|
||||
button.template-edit,
|
||||
button.template-delete {
|
||||
margin: 0 4px; /* Меньше отступов */
|
||||
padding: 5px 10px; /* Более компактные кнопки */
|
||||
background-color: transparent; /* Без фона */
|
||||
border: 1px solid #abcef5; /* Тонкая рамка */
|
||||
color: #333; /* Цвет текста */
|
||||
cursor: pointer;
|
||||
font-size: 14px; /* Меньший размер текста */
|
||||
border-radius: 5px; /* Скругленные углы */
|
||||
transition: background-color 0.3s, color 0.3s, border-color 0.3s; /* Плавные переходы */
|
||||
}
|
||||
|
||||
/* Эффект при наведении */
|
||||
button.template-add:hover,
|
||||
button.template-edit:hover,
|
||||
button.template-delete:hover {
|
||||
background-color: #abcef5; /* Подсветка фоном */
|
||||
color: #fff; /* Белый цвет текста */
|
||||
border-color: #88aee5; /* Немного темнее рамка */
|
||||
}
|
||||
|
||||
/* Эффект при фокусе */
|
||||
button.template-add:focus,
|
||||
button.template-edit:focus,
|
||||
button.template-delete:focus {
|
||||
outline: none; /* Убираем стандартную обводку */
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.2); /* Легкая тень для фокуса */
|
||||
}
|
||||
|
||||
div.templates {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
span.template {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
height: fit-content;
|
||||
padding: 8px 7px;
|
||||
border: 1px solid #8888c6; /* Цвет рамки совпадает с рамкой категории */
|
||||
border-radius: 8px; /* Скругленные углы */
|
||||
overflow: hidden;
|
||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; /* Плавный переход при наведении */
|
||||
}
|
||||
|
||||
/* Подсветка шаблонов при наведении */
|
||||
span.template:hover {
|
||||
background: #d0e6ff; /* Подсветка для светлой темы */
|
||||
border-color: #a56fbf; /* Немного темнее рамка при наведении */
|
||||
}
|
||||
|
||||
.dark-theme span.template:hover {
|
||||
background: #444; /* Подсветка для тёмной темы */
|
||||
color: #fff; /* Белый цвет текста в тёмной теме */
|
||||
}
|
||||
|
||||
.formochka {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#template-title-input {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
span.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
span.buttonsLong {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
span.template:hover {
|
||||
background: #d0e6ff; /* Подсветка при наведении */
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.category {
|
||||
margin-bottom: 10px;
|
||||
border: solid #8888c6 1px; /* Тонкая рамка для категории */
|
||||
border-radius: 1px 10px / 12px;
|
||||
box-shadow: 5px 3px 5px #8888c6; /* Легкая тень для категории */
|
||||
}
|
||||
|
||||
/* Стили для темной темы */
|
||||
body.dark-theme {
|
||||
background: #333; /* Темный фон */
|
||||
color: white; /* Светлый текст */
|
||||
}
|
||||
|
||||
/* Базовый стиль кнопок */
|
||||
.btn {
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
border: 1px solid #8888c6;
|
||||
}
|
||||
|
||||
/* Стили для кнопок в тёмной теме */
|
||||
.dark-theme .btn {
|
||||
color: #fff;
|
||||
background-color: #333;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
|
||||
.theme-btn {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1.5rem;
|
||||
color: #333;
|
||||
z-index: 1000;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.dark-theme .theme-btn {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.category h3 {
|
||||
display: flex;
|
||||
justify-content: space-between; /* Располагаем элементы по краям */
|
||||
align-items: center;
|
||||
cursor: pointer; /* Курсор при наведении на весь блок */
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.category-content {
|
||||
display: none;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.category-content.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.category h3 i {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
@@ -1,386 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
document.getElementById("inWorkRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги, здравствуйте.\n\nПриняли Ваш запрос в работу. Сообщим по мере поступления информации.")
|
||||
});
|
||||
document.getElementById("inWorkEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Dear customer,\n\nWe are working on your request.")
|
||||
});
|
||||
document.getElementById("dialogueRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги, здравствуйте.\n\nУведомляем вас о том, что мы начали диалог с оператором по данному запросу. Мы ожидаем ответа и будем держать вас в курсе любых изменений.")
|
||||
});
|
||||
document.getElementById("dialogueEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Dear colleagues.\n\nWe would like to inform you that we have started a dialogue with the operator on this request. We are awaiting a response and will keep you informed of any changes.")
|
||||
});
|
||||
document.getElementById("nonDelRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги, здравствуйте.\n\nПросьба уточнить причину недоставки следующих сообщений клиенту:")
|
||||
});
|
||||
document.getElementById("nonDelEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Dear colleagues,\n\nPlease check why the following messages were not delivered:")
|
||||
});
|
||||
document.getElementById("fixItRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги,\n\nМы не фиксируем на нашей платформе ваших СМС-сообщений, просьба предоставить ответ от нашей платформы на ваш запрос отправки. Так же просьба предоставить логи, подтверждающие отправку на нашу платформу, данная информация необходима для дальнейшего анализа вашего запроса.")
|
||||
});
|
||||
document.getElementById("fixItEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Dear colleagues,\n\nWe do not record your SMS messages on our platform, please provide a response from our platform to your request to send. Please also provide logs confirming sending to our platform, this information is necessary for further analysis of your request.")
|
||||
});
|
||||
document.getElementById("fakeRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги, здравствуйте.\n\nПросьба уточнить корректность статусов доставки следующих сообщений клиенту. Абонент не получал СМС.")
|
||||
});
|
||||
document.getElementById("fakeEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Dear colleagues,\n\nPlease clarify the correctness of the delivery statuses of the following messages to the client. The subscriber did not receive an SMS.")
|
||||
});
|
||||
document.getElementById("weworkRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги, здравствуйте.\n\nУведомляем вас о том, что работы по вашему запросу продолжаются. Как только появится информация, мы вам сообщим.\n\nБлагодарим за понимание и приносим извинения за доставленные неудобства.\n")
|
||||
});
|
||||
document.getElementById("weworkEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Dear Colleagues.\n\nWe notify you that work on your request is ongoing. As soon as information becomes available, we will inform you.\n\nThank you for your understanding and we apologize for the inconvenience caused.\n")
|
||||
});
|
||||
document.getElementById("wework2Ru").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги, здравствуйте.\n\nЖдем ответа от оператора. Сообщим, как только появится соответствующая информация. Спасибо!")
|
||||
});
|
||||
document.getElementById("wework2En").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Dear Colleagues.\n\nWe are waiting for a response from the operator. We will inform you as soon as we have an update. Thank you!")
|
||||
});
|
||||
document.getElementById("wework3Ru").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Здравствуйте!\n\nОтправили повторный запрос оператору по данному поводу.\n\nСпасибо!")
|
||||
});
|
||||
document.getElementById("wework3En").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Hello!\n\nWe have sent an additional request to the operator.\n\nThank you!")
|
||||
});
|
||||
document.getElementById("theyworkRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Здравствуйте, коллеги.\n\nПоявилась ли информация по данному вопросу?\n\nСпасибо.")
|
||||
});
|
||||
document.getElementById("theyworkEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Dear colleagues,\n\nIs there any information on this request?\n\nThank you.")
|
||||
});
|
||||
document.getElementById("theywork2Ru").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Добрый день!\n\nПросим вернуться с ответом.\n\nБольшое спасибо!")
|
||||
});
|
||||
document.getElementById("theywork2En").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Hello Team!\n\nPlease let us know if there are any updates.\n\nThank you!")
|
||||
});
|
||||
document.getElementById("theywork3Ru").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Здравствуйте!\n\nПодскажите, пожалуйста, появились ли новости.\n\nСпасибо!")
|
||||
});
|
||||
document.getElementById("theywork3En").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Hello!\n\nPlease kindly advise on issue status.\n\nThank you.")
|
||||
});
|
||||
document.getElementById("delivedRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги,\n\nСообщение успешно прошло через платформу и было отправлено оператору. По информации от оператора, сообщение было своевременно доставлено. В случае, если абонент утверждает, что сообщения не были получены, просьба проверить исправность работы ТА. Возможные рекомендации: перезагрузка телефона, очистка памяти от устаревших сообщений, проверка спам-фильтров и черных списков, обновление ПО.\n\nПри сохранении проблемы, просьба запросить у абонента детализацию. Данная информация потребуется для дальнейшего взаимодействия с оператором.")
|
||||
});
|
||||
document.getElementById("delivedEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Colleagues, according to the information from the operator, messages were delivered in a timely manner. If the subscriber claims that the messages have not been received, please check that device is working correctly. Possible recommendations: reboot the phone, clear message memory, check spam filters and blacklists, update the software.\n\nIf the problem still persists, please ask the subscriber for details. This information is necessary to continue interaction with the operator.")
|
||||
});
|
||||
document.getElementById("problphRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги,\n\nСообщение успешно прошло через платформу и было отправлено оператору. По информации от оператора, сообщение не было доставлено абоненту по причине сбоя ТА. Возможные рекомендации для абонента: перезагрузка телефона, очистка памяти от устаревших сообщений, проверка спам-фильтров и черных списков, обновление ПО.\n\nПросьба уведомить нас в случае, если ситуация повторится.")
|
||||
});
|
||||
document.getElementById("problphEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Colleagues, according to the information from the operator, messages were not delivered due to subscriber’s device failure. Possible recommendations: reboot the phone, clear message memory, check spam filters and blacklists, update the software.\nPlease let us know if the problem persist.")
|
||||
});
|
||||
document.getElementById("notservRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Здравствуйте, коллеги.\n\nСообщение успешно прошло через платформу и было отправлено оператору. По информации от оператора, сообщение абоненту не было доставлено, т.к. номер телефона не существует или не обслуживается.")
|
||||
});
|
||||
document.getElementById("notservEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Colleagues\n\nThe message successfully passed through the platform and was sent to the operator. According to the information from the operator, the messages were not delivered to the subscriber because the phone number does not exist or is not serviced.")
|
||||
});
|
||||
document.getElementById("insentRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги,\n\nСообщение успешно прошло через платформу и было отправлено оператору. Сообщение находится в статусе Отправлено, SMS-центр оператора продолжает осуществлять попытки его доставки.\nНаиболее вероятной причиной задержки в доставке может являться нахождение абонента в зоне неуверенного приема сети, также возможен технический сбой в работе телефона абонента.")
|
||||
});
|
||||
document.getElementById("insentEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Colleagues,\n\nThe messages are in Sent status, operator's SMS center is continuing delivery attempts. Most likely, delivery delays are caused by subscriber being in the area of uncertain network reception, or a technical failure in the operation of the subscriber's phone is also possible.")
|
||||
});
|
||||
document.getElementById("dubleRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги,\n\nМониторингом были зафиксированы дубликаты сообщений, в связи с чем сработала блокировка. Если в короткий промежуток времени на площадку поступает два одинаковых сообщения, повторное блокируется как дубликат.\nЕсли отправка дубликатов была запланирована, либо номер абонента используется для тестирования подключения, просьба сообщить, мы добавим абонента в белый список. ")
|
||||
});
|
||||
document.getElementById("dubleEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Colleagues, duplicates were blocked by our monitoring system. If two identical messages are received within a short period of time, the second one is blocked as a duplicate. If sending duplicates was planned, or the subscriber's number is used for testing, please let us know and we will whitelist the subscriber.")
|
||||
});
|
||||
document.getElementById("unvelbRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги,\n\nСообщение успешно прошло через платформу и было отправлено оператору. Сообщение абоненту не было доставлено т.к. ТА абонента был недоступен - выключен или находился в зоне неуверенного приема сигнала.\nРекомендуем абоненту перезагрузить телефон, проверить доступность сети оператора, очистить память телефона от устаревших сообщений.\n")
|
||||
});
|
||||
document.getElementById("unvelbEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Colleagues,\n\nThe messages were not delivered to the subscriber because the subscriber's phone was unavailable - turned off or was in the zone of uncertain signal reception.\nWe recommend that the subscriber restart the phone, check the availability of the operator's network, and clear the phone's memory of outdated messages.\n")
|
||||
});
|
||||
document.getElementById("delayRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги, здравствуйте.\n\nПросьба уточнить текущий статус сообщений и причины задержек:")
|
||||
});
|
||||
document.getElementById("delayEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Dear Colleagues.\n\nPlease clarify current messages status and the reasons for the delays:")
|
||||
});
|
||||
document.getElementById("conbreRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги, здравствуйте.\n\nНаблюдаем разрыв соединения. Просьба уточнить, ситуация штатная?")
|
||||
});
|
||||
document.getElementById("conbreEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Dear colleagues.\n\nWe observe a connection break. Please clarify whether it’s a standard situation.")
|
||||
});
|
||||
document.getElementById("corroutRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги,\n\nПо информации от оператора, для абонента были внесены корректировки в таблицы маршрутизации.\n\nПросьба проверить и сообщить в случае повторения ситуации.")
|
||||
});
|
||||
document.getElementById("corroutEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Dear colleagues,\n\nThe adjustments have been made in routing settings.\n\nAs our logs are now showing positive delivery results, we would like to ask you to perform a fresh check on your side and share the results in case if further assistance is required.")
|
||||
});
|
||||
document.getElementById("tehtroubRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги,\n\nПо информации от оператора, сообщения не были доставлены абоненту из-за временных технических неполадок на стороне оператора. На текущий момент доставка сообщений осуществляется в штатном режиме.\nПросьба проверить и сообщить в случае повторения ситуации.")
|
||||
});
|
||||
document.getElementById("tehtroubEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Dear colleagues,\n\nAccording to information we got from the operator, the messages were not delivered to the subscriber due to temporary technical issues on the operator's side. At the moment, messages are being delivered in the regular mode.")
|
||||
});
|
||||
document.getElementById("senderRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги,\nСообщение не было доставлено, так как для имени отправителя ######## требуется регистрация.\nПросьба подать имя на регистрацию в ЛК или обратиться по данному вопросу к Вашему менеджеру.")
|
||||
});
|
||||
document.getElementById("senderEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Colleagues, the below reported traffic failed to be delivered due to an unregistered Sender ID. Alphanumeric registration is required towards COUNRTY OPERATOR (MCCMNC), and delivery of messages with unregistered Sender IDs is on the best effort basis. To register, please contact your Account Manager.")
|
||||
});
|
||||
document.getElementById("notResponseRU").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Здравствуйте!\n\nК сожалению, мы не получили от Вас ответа в течение 20 дней, поэтому запрос будет автоматически закрыт. Вы можете ответить на это письмо, если проблема не решена и требуется поддержка.")
|
||||
});
|
||||
document.getElementById("notResponseEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Hello!\n\nUnfortunately, we have not received a response from you within 20 days, so the request will be automatically closed. You can reply to this email if the problem has not been resolved and support is required.")
|
||||
});
|
||||
document.getElementById("fwd2treadRU").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги,\nПередали вопрос ответственной команде. Сообщим по мере поступления информации.")
|
||||
});
|
||||
document.getElementById("fwd2treadEn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Dear colleagues,\n\nWe have escalated the issue to the responsible team.\nWe will keep you informed as we receive any updates.")
|
||||
});
|
||||
document.getElementById("daoffice1RU").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Добрый день!\nДля регистрации в приложении \"КингДом\" необходимо внести Ваш номер телефона в Axapta.\n\nПо данному вопросу, пожалуйста, обратитесь к директору своего ресторана или на электронную почту: kingdom@burgerking.ru\n\nЧерез три часа после внесения номера, Вы сможете войти в сеть по номеру телефона, используя приложение \"КингДом\".")
|
||||
});
|
||||
document.getElementById("officeRU").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Добрый день!\n\nМы проверили Вашу учетную запись - все данные корректны.\nПроверочный СМС-код успешно отправлен на Ваш мобильный телефон.\n\nПожалуйста, переустановите приложение \"КингДом\" (удалить\\скачать заново) по ссылкам ниже, чтобы исключить отсутствие свежих обновлений:\n\nAndroid:\n\nhttps://play.google.com/store/apps/details?id=ru.kingdom\n\nIOS:\n\nhttps://apps.apple.com/ru/app/%D0%BA%D0%B8%D0%BD%D0%B3%D0%B4%D0%BE%D0%BC/id1543206272?l=en-GB\n\nЕсли данные действия не помогут, то, пожалуйста, опишите подробнее, на каком моменте у Вас не получается зайти в систему (при наличии ошибки приложите скриншот).")
|
||||
});
|
||||
document.getElementById("blockMsisdnRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги,\n\nДанный номер находится в глобальном ЧС.\nЭто означает, что клиент жаловался на получение СМС и отказался от получения всех СМС, либо были жалобы от данного абонента в УФАС.\nИз глобального ЧС, к сожалению, мы не можем разблокировать по Вашей просьбе, только в случае, если абонент напишет сам о просьбе разблокировать, с пометкой о согласии получения всех сообщений от оператора.\n\nЗаявление пишется в свободной форме, скан необходимо прислать на почту вашему менеджеру. Пример:\nКому: ООО «АйДиджитал»\nГенеральному директору: Баранову А.В.\nПрошу убрать мой номер ____________ из черного списка. Понимаю и соглашаюсь на возможное получение рассылок от других компаний, которые осуществляют рассылки через платформу АйДиджитал.\n\nПодпись и дата.")
|
||||
});
|
||||
document.getElementById("commonSenderRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги, сообщения были заблокированы согласно правил отправки для каналов с Общим именем. А именно -\n\n----------------------------\n\n в тексте сообщения содержится упоминания международного бренда (рассылки в интересах международных брендов).\n на канале запрещена отправка сообщений, если в качестве сендера используется международный сендер\n на канале запрещена отправка сообщений на латинице\n на канале запрещена отправка сообщений с кодами в тексте которых нет упоминания сервиса от которого идет отправка\n на канале запрещена отправка сообщений в тексте которых содержится ссылка (ссылкой считается - наличие http в URL)\n на канале запрещена отправка сообщений в тексте сообщений которых указаны только цифры\n на канале запрещена отправка сообщений с менее 13 символов в тексте")
|
||||
});
|
||||
document.getElementById("noResponse").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Здравствуйте!\nК сожалению, мы не получили от Вас ответа в течение 5 рабочих дней, поэтому запрос будет автоматически закрыт.\nВы можете ответить на это письмо, если проблема не решена и требуется дополнительная помощь.")
|
||||
});
|
||||
document.getElementById("conductedTesting").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги,\nМы провели тестирование данного направления, используя живые номера. Все сообщения были доставлены на устройства правильно и без задержек.\nПросьба провести повторное тестирование со своей стороны и сообщить в случае повторения ситуации.")
|
||||
});
|
||||
document.getElementById("HLRRu").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Коллеги, здравствуйте.\nПросьба уточнить причину возврата некорректных HLR статусов на указанные номера:")
|
||||
});
|
||||
document.getElementById("HLREn").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Dear colleagues,\nPlease clarify the correctness of HLR statuses for the following subscriber numbers:")
|
||||
});
|
||||
document.getElementById("managerTemplate").addEventListener("click", () => {
|
||||
navigator.clipboard.writeText("Страна: \nОператор: \nmccMnc: \nСендер: \nКлиент: \nSource node: \nDestination node: ")
|
||||
});
|
||||
});
|
||||
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text)
|
||||
.then(() => {
|
||||
console.log('Текст скопирован в буфер обмена');
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Не удалось скопировать текст: ', err);
|
||||
});
|
||||
};
|
||||
|
||||
const templatesEl = document.querySelector('.templates');
|
||||
const addBtn = document.querySelector('.template-add');
|
||||
const editExisted = document.querySelector('template-edit-existed');
|
||||
|
||||
function createTemplate(title, RUText, ENText) {
|
||||
const templateId = Date.now();
|
||||
const templateEl = document.createElement('span');
|
||||
templateEl.setAttribute('data-template-id', templateId);
|
||||
templateEl.classList.add('template');
|
||||
templateEl.innerHTML = `
|
||||
<span id="template-title" class="font-monospace">${title}</span>
|
||||
<div class="bigFormochka">
|
||||
<div class="formochka">
|
||||
<input id="template-title-input" class="hidden form-control-sm"></input>
|
||||
<textarea id="template-textareaRU" class="hidden form-control-lg border-2" rows="1" cols="30.5">${RUText}</textarea>
|
||||
<textarea id="template-textareaEN" class="hidden form-control-lg border-2" rows="1" cols="30.5">${ENText}</textarea>
|
||||
</div>
|
||||
<span class="buttonsLong">
|
||||
<span class="btn btn-light" data-ru-text="${RUText}" id="template-ru-text">RU</span>
|
||||
<span class="btn btn-light" data-en-text="${ENText}" id="template-en-text">ENG</span>
|
||||
<button class="template-edit btn"><i class="fa-solid fa-pen-to-square"></i></button>
|
||||
<button class="template-delete btn"><i class="fa-solid fa-trash"></i></button>
|
||||
</span>
|
||||
</span>
|
||||
</div> `
|
||||
|
||||
const editBtn = templateEl.querySelector('.template-edit');
|
||||
const deleteBtn = templateEl.querySelector('.template-delete');
|
||||
const titleEl = templateEl.querySelector('#template-title');
|
||||
const textRUEl = templateEl.querySelector('#template-ru-text');
|
||||
const textENEl = templateEl.querySelector('#template-en-text');
|
||||
const titleInputEl = templateEl.querySelector('#template-title-input');
|
||||
const textRUInputEl = templateEl.querySelector('#template-textareaRU');
|
||||
const textENInputEl = templateEl.querySelector('#template-textareaEN');
|
||||
|
||||
editBtn.addEventListener('click', (e) => {
|
||||
titleEl.classList.toggle('hidden');
|
||||
textRUEl.classList.toggle('hidden');
|
||||
textENEl.classList.toggle('hidden');
|
||||
|
||||
titleInputEl.classList.toggle('hidden');
|
||||
textRUInputEl.classList.toggle('hidden');
|
||||
textENInputEl.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
deleteBtn.addEventListener('click', (e) => {
|
||||
const templates = JSON.parse(localStorage.getItem('templates')) || [];
|
||||
const templateIndex = templates.findIndex(template => template.title === titleEl.innerText);
|
||||
if (templateIndex !== -1) {
|
||||
templates.splice(templateIndex, 1);
|
||||
localStorage.setItem('templates', JSON.stringify(templates));
|
||||
}
|
||||
templateEl.remove();
|
||||
});
|
||||
|
||||
titleInputEl.addEventListener('change', (e) => {
|
||||
titleEl.innerText = e.target.value;
|
||||
navigator.clipboard.writeText(titleEl);
|
||||
updateTemplateInLocalStorage(title, 'title', e.target.value);
|
||||
});
|
||||
|
||||
textRUInputEl.addEventListener('change', (e) => {
|
||||
textRUEl.dataset.ruText = e.target.value;
|
||||
navigator.clipboard.writeText(textRUEl);
|
||||
updateTemplateInLocalStorage(title, 'RUText', e.target.value);
|
||||
});
|
||||
|
||||
textENInputEl.addEventListener('change', (e) => {
|
||||
textENEl.dataset.enText = e.target.value;
|
||||
navigator.clipboard.writeText(textRUEl);
|
||||
updateTemplateInLocalStorage(title, 'ENText', e.target.value);
|
||||
});
|
||||
|
||||
textRUEl.addEventListener('click', (e) => {
|
||||
navigator.clipboard.writeText(e.target.dataset.ruText)
|
||||
.then(() => {
|
||||
console.log('Текст RU скопирован в буфер обмена');
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Не удалось скопировать текст RU: ', err);
|
||||
});
|
||||
});
|
||||
|
||||
textENEl.addEventListener('click', (e) => {
|
||||
navigator.clipboard.writeText(e.target.dataset.enText)
|
||||
.then(() => {
|
||||
console.log('Текст EN скопирован в буфер обмена');
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Не удалось скопировать текст EN: ', err);
|
||||
});
|
||||
});
|
||||
|
||||
return templateEl;
|
||||
};
|
||||
|
||||
function saveTemplateToLocalStorage(title, RUText, ENText) {
|
||||
const template = {
|
||||
title: title,
|
||||
RUText: RUText,
|
||||
ENText: ENText
|
||||
};
|
||||
const templates = JSON.parse(localStorage.getItem('templates')) || [];
|
||||
templates.push(template);
|
||||
localStorage.setItem('templates', JSON.stringify(templates));
|
||||
};
|
||||
|
||||
function updateTemplateInLocalStorage(title, key, value) {
|
||||
const templates = JSON.parse(localStorage.getItem('templates')) || [];
|
||||
const templateIndex = templates.findIndex(template => template.title === title);
|
||||
|
||||
if (templateIndex !== -1) {
|
||||
templates[templateIndex][key] = value;
|
||||
localStorage.setItem('templates', JSON.stringify(templates));
|
||||
};
|
||||
};
|
||||
|
||||
addBtn.addEventListener('click', (e) => {
|
||||
const title = "Title";
|
||||
const RUText = "RUText";
|
||||
const ENText = "ENText";
|
||||
|
||||
const el = createTemplate(title, RUText, ENText);
|
||||
templatesEl.appendChild(el);
|
||||
saveTemplateToLocalStorage(title, RUText, ENText);
|
||||
});
|
||||
|
||||
window.onload = function () {
|
||||
const templates = JSON.parse(localStorage.getItem('templates')) || [];
|
||||
templates.forEach(template => {
|
||||
const el = createTemplate(template.title, template.RUText, template.ENText);
|
||||
templatesEl.appendChild(el);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const themeIcon = document.getElementById('theme-icon');
|
||||
|
||||
|
||||
const applySavedTheme = () => {
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
document.body.classList.add('dark-theme');
|
||||
themeIcon.classList.replace('fa-moon', 'fa-sun');
|
||||
} else {
|
||||
document.body.classList.remove('dark-theme');
|
||||
themeIcon.classList.replace('fa-sun', 'fa-moon');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
themeToggle.addEventListener('click', () => {
|
||||
document.body.classList.toggle('dark-theme');
|
||||
|
||||
|
||||
if (document.body.classList.contains('dark-theme')) {
|
||||
themeIcon.classList.replace('fa-moon', 'fa-sun');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
themeIcon.classList.replace('fa-sun', 'fa-moon');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
applySavedTheme();
|
||||
});
|
||||
|
||||
document.querySelectorAll('.category h3').forEach(function(header) {
|
||||
const categoryId = header.textContent.trim(); // Уникальный идентификатор категории по названию
|
||||
|
||||
// Проверка состояния категории при загрузке страницы
|
||||
const isCategoryOpen = localStorage.getItem(categoryId) === 'true';
|
||||
const content = header.nextElementSibling; // Содержимое категории
|
||||
const icon = header.querySelector('.toggle-icon'); // Иконка стрелки
|
||||
|
||||
// Если категория была открыта ранее, делаем ее открытой
|
||||
if (isCategoryOpen) {
|
||||
content.classList.add('show');
|
||||
icon.classList.remove('fa-chevron-down');
|
||||
icon.classList.add('fa-chevron-up');
|
||||
}
|
||||
|
||||
// Обработчик клика по заголовку категории
|
||||
header.addEventListener('click', function() {
|
||||
content.classList.toggle('show'); // Переключаем видимость категории
|
||||
|
||||
if (content.classList.contains('show')) {
|
||||
icon.classList.remove('fa-chevron-down');
|
||||
icon.classList.add('fa-chevron-up');
|
||||
// Сохраняем состояние категории как открытое
|
||||
localStorage.setItem(categoryId, 'true');
|
||||
} else {
|
||||
icon.classList.remove('fa-chevron-up');
|
||||
icon.classList.add('fa-chevron-down');
|
||||
// Сохраняем состояние категории как закрытое
|
||||
localStorage.setItem(categoryId, 'false');
|
||||
}
|
||||
});
|
||||
});
|
||||
394
lzipus/src/popup.css
Normal file
394
lzipus/src/popup.css
Normal file
@@ -0,0 +1,394 @@
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 300px;
|
||||
max-width: 3000px;
|
||||
width: 100%; /* Используем 100%, чтобы не ограничивать ширину */
|
||||
background: #abcef5; /* Светлый фон */
|
||||
font-family: Helvetica, Arial, sans-serif; /* Более минималистичный шрифт */
|
||||
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;
|
||||
padding: 1px;
|
||||
width: 100%; /* Обеспечиваем, чтобы контейнер растягивался по ширине */
|
||||
}
|
||||
|
||||
div.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
/* Кнопки */
|
||||
button.template-add,
|
||||
button.template-edit,
|
||||
button.template-delete {
|
||||
margin: 0 4px; /* Меньше отступов */
|
||||
padding: 5px 10px; /* Более компактные кнопки */
|
||||
background-color: transparent; /* Без фона */
|
||||
border: 1px solid #abcef5; /* Тонкая рамка */
|
||||
color: #333; /* Цвет текста */
|
||||
cursor: pointer;
|
||||
font-size: 14px; /* Меньший размер текста */
|
||||
border-radius: 5px; /* Скругленные углы */
|
||||
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 {
|
||||
outline: none; /* Убираем стандартную обводку */
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.2); /* Легкая тень для фокуса */
|
||||
}
|
||||
|
||||
/* Шаблоны */
|
||||
div.templates {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 200px;
|
||||
width: 100%; /* Обеспечиваем, чтобы шаблоны занимали всю доступную ширину */
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
span.template {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
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; /* Применяем анимацию к элементам шаблона */
|
||||
}
|
||||
|
||||
/* Подсветка шаблонов при наведении */
|
||||
span.template:hover {
|
||||
background: #d0e6ff; /* Подсветка для светлой темы */
|
||||
border-color: #a56fbf; /* Немного темнее рамка при наведении */
|
||||
}
|
||||
|
||||
.dark-theme span.template:hover {
|
||||
background: #444; /* Подсветка для тёмной темы */
|
||||
color: #fff; /* Белый цвет текста в тёмной теме */
|
||||
}
|
||||
|
||||
.formochka {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#template-title-input {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
span.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
span.buttonsLong {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
span.template:hover {
|
||||
background: #d0e6ff; /* Подсветка при наведении */
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.category {
|
||||
margin-bottom: 10px;
|
||||
border: solid #8888c6 1px; /* Тонкая рамка для категории */
|
||||
border-radius: 1px 10px / 12px;
|
||||
box-shadow: 5px 3px 5px #8888c6; /* Легкая тень для категории */
|
||||
width: 100%; /* Категория растягивается на всю ширину */
|
||||
box-sizing: border-box; /* Чтобы паддинги не влияли на размер */
|
||||
}
|
||||
|
||||
.category h3 {
|
||||
display: flex;
|
||||
justify-content: space-between; /* Располагаем элементы по краям */
|
||||
align-items: center;
|
||||
cursor: pointer; /* Курсор при наведении на весь блок */
|
||||
width: 100%; /* Заголовок занимает всю ширину */
|
||||
box-sizing: border-box; /* Чтобы паддинги не влияли на размер */
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.category-content {
|
||||
display: none;
|
||||
padding-left: 20px;
|
||||
width: 100%; /* Содержимое категории растягивается на всю ширину */
|
||||
}
|
||||
|
||||
.category-content.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.category h3 i {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
/* Стили для темной темы */
|
||||
body.dark-theme {
|
||||
background: #333; /* Темный фон */
|
||||
color: white; /* Светлый текст */
|
||||
}
|
||||
|
||||
/* Базовый стиль кнопок */
|
||||
.btn {
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
border: 1px solid #8888c6;
|
||||
}
|
||||
|
||||
/* Стили для кнопок в тёмной теме */
|
||||
.dark-theme .btn {
|
||||
color: #fff;
|
||||
background-color: #333;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
|
||||
.theme-btn {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1.5rem;
|
||||
color: #333;
|
||||
z-index: 1000;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.dark-theme .theme-btn {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Кнопка добавления шаблона */
|
||||
.add-template-btn {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
padding: 10px 20px;
|
||||
background-color: #abcef5;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
border-radius: 50px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: background-color 0.3s, box-shadow 0.3s;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.add-template-btn i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.add-template-btn:hover {
|
||||
background-color: #88aee5;
|
||||
box-shadow: 0px 6px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.add-template-btn:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Для кнопок и textarea, чтобы не "гуляли" */
|
||||
.template {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
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;
|
||||
}
|
||||
|
||||
/* Скрытие полей */
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Стили для input и textarea */
|
||||
input, textarea {
|
||||
width: 100%;
|
||||
height: 40px; /* Сделаем высоту фиксированной */
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Убираем возможность изменения размеров textarea */
|
||||
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;
|
||||
}
|
||||
@@ -11,16 +11,34 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="toast-container" id="toast-container"></div>
|
||||
<button id="theme-toggle" class="theme-btn">
|
||||
<i id="theme-icon" class="fa-moon fa-solid"></i>
|
||||
</button>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 class="name fs-4 fw-lighter font-monospace">Твои шаблоны</h1>
|
||||
<button class="template-add btn btn-primary" type="button"><i class="fa-duotone fa-plus"></i></button>
|
||||
<div class="header-buttons">
|
||||
<button class="template-add btn btn-primary" type="button" title="Добавить шаблон">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
</button>
|
||||
<button class="import-btn btn btn-secondary" type="button" title="Импортировать шаблоны">
|
||||
<i class="fa-solid fa-file-import"></i>
|
||||
</button>
|
||||
<button class="export-btn btn btn-secondary" type="button" title="Экспортировать шаблоны">
|
||||
<i class="fa-solid fa-file-export"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="templates" id="template-list">
|
||||
<input type="file" id="import-file-input" accept=".json" style="display: none;">
|
||||
|
||||
<div class="search-wrapper">
|
||||
<input type="text" id="search-input" class="form-control" placeholder="Поиск по шаблонам...">
|
||||
<i class="fa-solid fa-search search-icon"></i>
|
||||
</div>
|
||||
|
||||
<div class="templates" id="template-list">
|
||||
<div class="category">
|
||||
<h3 class="font-monospace">
|
||||
Промежуточные ответы
|
||||
@@ -28,12 +46,12 @@
|
||||
|
||||
</h3>
|
||||
<div class="category-content">
|
||||
<span class="template">
|
||||
<span class="template" data-builtin-id="inWork">
|
||||
<span class="template-title font-monospace">Приняли в работу</span>
|
||||
<span class="buttons">
|
||||
<span class="btn btn-light" id="inWorkRu">RU</span>
|
||||
<span class="btn btn-light" id="inWorkEn">ENG</span>
|
||||
<!-- <button class="template-edit-existed btn"><i class="fa-solid fa-pen-to-square"></i></button> -->
|
||||
<button class="template-edit-builtin btn"><i class="fa-solid fa-pen-to-square"></i></button>
|
||||
</span>
|
||||
</span>
|
||||
<span class="template">
|
||||
@@ -277,7 +295,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</1--div>
|
||||
</div>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
|
||||
600
lzipus/src/popup.js
Normal file
600
lzipus/src/popup.js
Normal file
@@ -0,0 +1,600 @@
|
||||
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 = `
|
||||
<i class="fa-solid fa-check-circle"></i>
|
||||
<span class="toast-message">${message}</span>
|
||||
`;
|
||||
toastContainer.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, duration);
|
||||
}
|
||||
|
||||
// Загружаем переопределения встроенных шаблонов
|
||||
let builtinOverrides = await StorageHelper.get('builtinOverrides') || {};
|
||||
|
||||
// Объект с текстами для каждого элемента
|
||||
let clipboardTexts = {
|
||||
"inWorkRu": "Коллеги, здравствуйте.\nПриняли Ваш запрос в работу. Сообщим по мере поступления информации.",
|
||||
"inWorkEn": "Dear customer,\nWe are working on your request.",
|
||||
"dialogueRu": "Коллеги, здравствуйте.\nУведомляем вас о том, что мы начали диалог с оператором по данному запросу. Мы ожидаем ответа и будем держать вас в курсе любых изменений.",
|
||||
"dialogueEn": "Dear colleagues.\nWe would like to inform you that we have started a dialogue with the operator on this request. We are awaiting a response and will keep you informed of any changes.",
|
||||
"nonDelRu": "Коллеги, здравствуйте.\nПросьба уточнить причину недоставки следующих сообщений клиенту:",
|
||||
"nonDelEn": "Dear colleagues,\nPlease check why the following messages were not delivered:",
|
||||
"fixItRu": "Коллеги,\nМы не фиксируем на нашей платформе ваших СМС-сообщений, просьба предоставить ответ от нашей платформы на ваш запрос отправки. Так же просьба предоставить логи, подтверждающие отправку на нашу платформу, данная информация необходима для дальнейшего анализа вашего запроса.",
|
||||
"fixItEn": "Dear colleagues,\nWe do not record your SMS messages on our platform, please provide a response from our platform to your request to send. Please also provide logs confirming sending to our platform, this information is necessary for further analysis of your request.",
|
||||
"fakeRu": "Коллеги, здравствуйте.\nПросьба уточнить корректность статусов доставки следующих сообщений клиенту. Абонент не получал СМС.",
|
||||
"fakeEn": "Dear colleagues,\nPlease clarify the correctness of the delivery statuses of the following messages to the client. The subscriber did not receive an SMS.",
|
||||
"weworkRu": "Коллеги, здравствуйте.\nУведомляем вас о том, что работы по вашему запросу продолжаются. Как только появится информация, мы вам сообщим.\nБлагодарим за понимание и приносим извинения за доставленные неудобства.\n",
|
||||
"weworkEn": "Dear Colleagues.\nWe notify you that work on your request is ongoing. As soon as information becomes available, we will inform you.\nThank you for your understanding and we apologize for the inconvenience caused.\n",
|
||||
"wework2Ru": "Коллеги, здравствуйте.\nЖдем ответа от оператора. Сообщим, как только появится соответствующая информация. Спасибо!",
|
||||
"wework2En": "Dear Colleagues.\nWe are waiting for a response from the operator. We will inform you as soon as we have an update. Thank you!",
|
||||
"wework3Ru": "Здравствуйте!\nОтправили повторный запрос оператору по данному поводу.\nСпасибо!",
|
||||
"wework3En": "Hello!\nWe have sent an additional request to the operator.\nThank you!",
|
||||
"theyworkRu": "Здравствуйте, коллеги.\nПоявилась ли информация по данному вопросу?\nСпасибо.",
|
||||
"theyworkEn": "Dear colleagues,\nIs there any information on this request?\nThank you.",
|
||||
"theywork2Ru": "Добрый день!\nПросим вернуться с ответом.\nБольшое спасибо!",
|
||||
"theywork2En": "Hello Team!\nPlease let us know if there are any updates.\nThank you!",
|
||||
"theywork3Ru": "Здравствуйте!\nПодскажите, пожалуйста, появились ли новости.\nСпасибо!",
|
||||
"theywork3En": "Hello!\nPlease kindly advise on issue status.\nThank you.",
|
||||
"delivedRu": "Коллеги,\nСообщение успешно прошло через платформу и было отправлено оператору. По информации от оператора, сообщение было своевременно доставлено. В случае, если абонент утверждает, что сообщения не были получены, просьба проверить исправность работы ТА. Возможные рекомендации: перезагрузка телефона, очистка памяти от устаревших сообщений, проверка спам-фильтров и черных списков, обновление ПО.\nПри сохранении проблемы, просьба запросить у абонента детализацию. Данная информация потребуется для дальнейшего взаимодействия с оператором.",
|
||||
"delivedEn": "Colleagues, according to the information from the operator, messages were delivered in a timely manner. If the subscriber claims that the messages have not been received, please check that device is working correctly. Possible recommendations: reboot the phone, clear message memory, check spam filters and blacklists, update the software.\nIf the problem still persists, please ask the subscriber for details. This information is necessary to continue interaction with the operator.",
|
||||
"problphRu": "Коллеги,\nСообщение успешно прошло через платформу и было отправлено оператору. По информации от оператора, сообщение не было доставлено абоненту по причине сбоя ТА. Возможные рекомендации для абонента: перезагрузка телефона, очистка памяти от устаревших сообщений, проверка спам-фильтров и черных списков, обновление ПО.\nПросьба уведомить нас в случае, если ситуация повторится.",
|
||||
"problphEn": "Colleagues, according to the information from the operator, messages were not delivered due to subscriber’s device failure. Possible recommendations: reboot the phone, clear message memory, check spam filters and blacklists, update the software.\nPlease let us know if the problem persist.",
|
||||
"notservRu": "Здравствуйте, коллеги.\nСообщение успешно прошло через платформу и было отправлено оператору. По информации от оператора, сообщение абоненту не было доставлено, т.к. номер телефона не существует или не обслуживается.",
|
||||
"notservEn": "Colleagues\nThe message successfully passed through the platform and was sent to the operator. According to the information from the operator, the messages were not delivered to the subscriber because the phone number does not exist or is not serviced.",
|
||||
"insentRu": "Коллеги,\nСообщение успешно прошло через платформу и было отправлено оператору. Сообщение находится в статусе Отправлено, SMS-центр оператора продолжает осуществлять попытки его доставки.\nНаиболее вероятной причиной задержки в доставке может являться нахождение абонента в зоне неуверенного приема сети, также возможен технический сбой в работе телефона абонента.",
|
||||
"insentEn": "Colleagues,\nThe messages are in Sent status, operator's SMS center is continuing delivery attempts. Most likely, delivery delays are caused by subscriber being in the area of uncertain network reception, or a technical failure in the operation of the subscriber's phone is also possible.",
|
||||
"dubleRu": "Коллеги,\nМониторингом были зафиксированы дубликаты сообщений, в связи с чем сработала блокировка. Если в короткий промежуток времени на площадку поступает два одинаковых сообщения, повторное блокируется как дубликат.\nЕсли отправка дубликатов была запланирована, либо номер абонента используется для тестирования подключения, просьба сообщить, мы добавим абонента в белый список.",
|
||||
"dubleEn": "Colleagues, duplicates were blocked by our monitoring system. If two identical messages are received within a short period of time, the second one is blocked as a duplicate. If sending duplicates was planned, or the subscriber's number is used for testing, please let us know and we will whitelist the subscriber.",
|
||||
"unvelbRu": "Коллеги,\nСообщение успешно прошло через платформу и было отправлено оператору. Сообщение абоненту не было доставлено т.к. ТА абонента был недоступен - выключен или находился в зоне неуверенного приема сигнала.\nРекомендуем абоненту перезагрузить телефон, проверить доступность сети оператора, очистить память телефона от устаревших сообщений.\n",
|
||||
"unvelbEn": "Colleagues,\nThe messages were not delivered to the subscriber because the subscriber's phone was unavailable - turned off or was in the zone of uncertain signal reception.\nWe recommend that the subscriber restart the phone, check the availability of the operator's network, and clear the phone's memory of outdated messages.\n",
|
||||
"delayRu": "Коллеги, здравствуйте.\nПросьба уточнить текущий статус сообщений и причины задержек:",
|
||||
"delayEn": "Dear Colleagues.\nPlease clarify current messages status and the reasons for the delays:",
|
||||
"conbreRu": "Коллеги, здравствуйте.\nНаблюдаем разрыв соединения. Просьба уточнить, ситуация штатная?",
|
||||
"conbreEn": "Dear colleagues.\nWe observe a connection break. Please clarify whether it’s a standard situation.",
|
||||
"corroutRu": "Коллеги,\nПо информации от оператора, для абонента были внесены корректировки в таблицы маршрутизации.\nПросьба проверить и сообщить в случае повторения ситуации.",
|
||||
"corroutEn": "Dear colleagues,\nThe adjustments have been made in routing settings.\nAs our logs are now showing positive delivery results, we would like to ask you to perform a fresh check on your side and share the results in case if further assistance is required.",
|
||||
"tehtroubRu": "Коллеги,\nПо информации от оператора, сообщения не были доставлены абоненту из-за временных технических неполадок на стороне оператора. На текущий момент доставка сообщений осуществляется в штатном режиме.\nПросьба проверить и сообщить в случае повторения ситуации.",
|
||||
"tehtroubEn": "Dear colleagues,\nAccording to information we got from the operator, the messages were not delivered to the subscriber due to temporary technical issues on the operator's side. At the moment, messages are being delivered in the regular mode.",
|
||||
"senderRu": "Коллеги,\nСообщение не было доставлено, так как для имени отправителя ######## требуется регистрация.\nПросьба подать имя на регистрацию в ЛК или обратиться по данному вопросу к Вашему менеджеру.",
|
||||
"senderEn": "Colleagues, the below reported traffic failed to be delivered due to an unregistered Sender ID. Alphanumeric registration is required towards COUNRTY OPERATOR (MCCMNC), and delivery of messages with unregistered Sender IDs is on the best effort basis. To register, please contact your Account Manager.",
|
||||
"notResponseRU": "Здравствуйте!\nК сожалению, мы не получили от Вас ответа в течение 20 дней, поэтому запрос будет автоматически закрыт. Вы можете ответить на это письмо, если проблема не решена и требуется поддержка.",
|
||||
"notResponseEn": "Hello!\nUnfortunately, we have not received a response from you within 20 days, so the request will be automatically closed. You can reply to this email if the problem has not been resolved and support is required.",
|
||||
"fwd2treadRU": "Коллеги,\nПередали вопрос ответственной команде. Сообщим по мере поступления информации.",
|
||||
"fwd2treadEn": "Dear colleagues,\nWe have escalated the issue to the responsible team.\nWe will keep you informed as we receive any updates.",
|
||||
"daoffice1RU": "Добрый день!\nДля регистрации в приложении \"КингДом\" необходимо внести Ваш номер телефона в Axapta.\nПо данному вопросу, пожалуйста, обратитесь к директору своего ресторана или на электронную почту: kingdom@burgerking.ru\nЧерез три часа после внесения номера, Вы сможете войти в сеть по номеру телефона, используя приложение \"КингДом\".",
|
||||
"officeRU": "Добрый день!\nМы проверили Вашу учетную запись - все данные корректны.\nПроверочный СМС-код успешно отправлен на Ваш мобильный телефон.\nПожалуйста, переустановите приложение \"КингДом\" (удалить\\скачать заново) по ссылкам ниже, чтобы исключить отсутствие свежих обновлений:\nAndroid:\nhttps://play.google.com/store/apps/details?id=ru.kingdom\nIOS:\nhttps://apps.apple.com/ru/app/%D0%BA%D0%B8%D0%BD%D0%B3%D0%B4%D0%BE%D0%BC/id1543206272?l=en-GB\nЕсли данные действия не помогут, то, пожалуйста, опишите подробнее, на каком моменте у Вас не получается зайти в систему (при наличии ошибки приложите скриншот).",
|
||||
"blockMsisdnRu": "Коллеги,\nДанный номер находится в глобальном ЧС.\nЭто означает, что клиент жаловался на получение СМС и отказался от получения всех СМС, либо были жалобы от данного абонента в УФАС.\nИз глобального ЧС, к сожалению, мы не можем разблокировать по Вашей просьбе, только в случае, если абонент напишет сам о просьбе разблокировать, с пометкой о согласии получения всех сообщений от оператора.\nЗаявление пишется в свободной форме, скан необходимо прислать на почту вашему менеджеру. Пример:\nКому: ООО «АйДиджитал»\nГенеральному директору: Баранову А.В.\nПрошу убрать мой номер ____________ из черного списка. Понимаю и соглашаюсь на возможное получение рассылок от других компаний, которые осуществляют рассылки через платформу АйДиджитал.\nПодпись и дата.",
|
||||
"commonSenderRu": "Коллеги, сообщения были заблокированы согласно правил отправки для каналов с Общим именем. А именно -\n----------------------------\n в тексте сообщения содержится упоминания международного бренда (рассылки в интересах международных брендов).\n на канале запрещена отправка сообщений, если в качестве сендера используется международный сендер\n на канале запрещена отправка сообщений на латинице\n на канале запрещена отправка сообщений с кодами в тексте которых нет упоминания сервиса от которого идет отправка\n на канале запрещена отправка сообщений в тексте которых содержится ссылка (ссылкой считается - наличие http в URL)\n на канале запрещена отправка сообщений в тексте сообщений которых указаны только цифры\n на канале запрещена отправка сообщений с менее 13 символов в тексте",
|
||||
"noResponse": "Здравствуйте!\nК сожалению, мы не получили от Вас ответа в течение 5 рабочих дней, поэтому запрос будет автоматически закрыт.\nВы можете ответить на это письмо, если проблема не решена и требуется дополнительная помощь.",
|
||||
"conductedTesting": "Коллеги,\nМы провели тестирование данного направления, используя живые номера. Все сообщения были доставлены на устройства правильно и без задержек.\nПросьба провести повторное тестирование со своей стороны и сообщить в случае повторения ситуации.",
|
||||
"HLRRu": "Коллеги, здравствуйте.\nПросьба уточнить причину возврата некорректных HLR статусов на указанные номера:",
|
||||
"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])
|
||||
.then(() => {
|
||||
updateUsageStats(buttonId);
|
||||
const count = getUsageCount(buttonId);
|
||||
showToast(`Текст скопирован! (Использований: ${count})`);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Не удалось скопировать текст: ', err);
|
||||
showToast('Ошибка копирования');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Привязываем обработчик событий к кнопкам
|
||||
Object.keys(clipboardTexts).forEach(buttonId => {
|
||||
const button = document.getElementById(buttonId);
|
||||
if (button) {
|
||||
button.addEventListener("click", () => handleClipboardClick(buttonId));
|
||||
}
|
||||
});
|
||||
//---------------------------------------------------Создание новых шаблонов локально---------------------------------------------------------//
|
||||
const templatesEl = document.querySelector('.templates');
|
||||
const addBtn = document.querySelector('.template-add');
|
||||
|
||||
function createTemplate(title, RUText, ENText) {
|
||||
const templateId = Date.now();
|
||||
const templateEl = document.createElement('span');
|
||||
templateEl.setAttribute('data-template-id', templateId);
|
||||
templateEl.classList.add('template');
|
||||
|
||||
templateEl.innerHTML = `
|
||||
<span id="template-title" class="font-monospace">${title}</span>
|
||||
<div class="bigFormochka">
|
||||
<div class="formochka">
|
||||
<input id="template-title-input" class="hidden form-control-sm">
|
||||
<textarea id="template-textareaRU" class="hidden form-control-lg border-2" rows="1" cols="30.5">${RUText}</textarea>
|
||||
<textarea id="template-textareaEN" class="hidden form-control-lg border-2" rows="1" cols="30.5">${ENText}</textarea>
|
||||
</div>
|
||||
<span class="buttonsLong">
|
||||
<span class="btn btn-light" data-ru-text="${RUText}" id="template-ru-text">RU</span>
|
||||
<span class="btn btn-light" data-en-text="${ENText}" id="template-en-text">ENG</span>
|
||||
<button class="template-edit btn"><i class="fa-solid fa-pen-to-square"></i></button>
|
||||
<button class="template-delete btn"><i class="fa-solid fa-trash"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const editBtn = templateEl.querySelector('.template-edit');
|
||||
const deleteBtn = templateEl.querySelector('.template-delete');
|
||||
const titleEl = templateEl.querySelector('#template-title');
|
||||
const textRUEl = templateEl.querySelector('#template-ru-text');
|
||||
const textENEl = templateEl.querySelector('#template-en-text');
|
||||
const titleInputEl = templateEl.querySelector('#template-title-input');
|
||||
const textRUInputEl = templateEl.querySelector('#template-textareaRU');
|
||||
const textENInputEl = templateEl.querySelector('#template-textareaEN');
|
||||
|
||||
// Функция для переключения видимости элементов
|
||||
function toggleVisibility(...elements) {
|
||||
elements.forEach(el => el.classList.toggle('hidden'));
|
||||
}
|
||||
|
||||
// Обработчик для редактирования
|
||||
editBtn.addEventListener('click', () => {
|
||||
toggleVisibility(titleEl, textRUEl, textENEl, titleInputEl, textRUInputEl, textENInputEl);
|
||||
});
|
||||
|
||||
// Обработчик для удаления
|
||||
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);
|
||||
await StorageHelper.set('templates', templates);
|
||||
}
|
||||
templateEl.remove();
|
||||
});
|
||||
|
||||
// Обработчики для изменений в полях
|
||||
const handleChange = (key, valueEl, inputEl) => {
|
||||
valueEl.innerText = inputEl.value;
|
||||
navigator.clipboard.writeText(valueEl);
|
||||
updateTemplateInLocalStorage(title, key, inputEl.value);
|
||||
};
|
||||
|
||||
titleInputEl.addEventListener('change', (e) => handleChange('title', titleEl, e.target));
|
||||
textRUInputEl.addEventListener('change', (e) => handleChange('RUText', textRUEl, e.target));
|
||||
textENInputEl.addEventListener('change', (e) => handleChange('ENText', textENEl, e.target));
|
||||
|
||||
// Обработчик для копирования текста в буфер обмена
|
||||
const handleCopyText = (e, key) => {
|
||||
const templateId = `custom-${title}-${key}`;
|
||||
navigator.clipboard.writeText(e.target.dataset[key])
|
||||
.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'));
|
||||
textENEl.addEventListener('click', (e) => handleCopyText(e, 'enText'));
|
||||
|
||||
return templateEl;
|
||||
}
|
||||
|
||||
async function saveTemplateToLocalStorage(title, RUText, ENText) {
|
||||
const template = { title, RUText, ENText };
|
||||
const templates = await StorageHelper.get('templates') || [];
|
||||
templates.push(template);
|
||||
await StorageHelper.set('templates', 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;
|
||||
await StorageHelper.set('templates', templates);
|
||||
}
|
||||
}
|
||||
|
||||
addBtn.addEventListener('click', () => {
|
||||
const title = "Title";
|
||||
const RUText = "RUText";
|
||||
const ENText = "ENText";
|
||||
|
||||
const el = createTemplate(title, RUText, ENText);
|
||||
templatesEl.appendChild(el);
|
||||
saveTemplateToLocalStorage(title, RUText, ENText);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
};
|
||||
//--------------------------------------------------------------------------------------------------------------------------------//
|
||||
|
||||
// Тема
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const themeIcon = document.getElementById('theme-icon');
|
||||
|
||||
// Применение сохраненной темы
|
||||
const applySavedTheme = async () => {
|
||||
const savedTheme = await StorageHelper.get('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
document.body.classList.add('dark-theme');
|
||||
if (themeIcon) {
|
||||
themeIcon.classList.replace('fa-moon', 'fa-sun');
|
||||
}
|
||||
} else {
|
||||
document.body.classList.remove('dark-theme');
|
||||
if (themeIcon) {
|
||||
themeIcon.classList.replace('fa-sun', 'fa-moon');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Переключение темы
|
||||
if (themeToggle && themeIcon) {
|
||||
themeToggle.addEventListener('click', async () => {
|
||||
document.body.classList.toggle('dark-theme');
|
||||
const theme = document.body.classList.contains('dark-theme') ? 'dark' : 'light';
|
||||
await StorageHelper.set('theme', theme); // Сохраняем выбранную тему
|
||||
themeIcon.classList.replace(theme === 'dark' ? 'fa-moon' : 'fa-sun', theme === 'dark' ? 'fa-sun' : 'fa-moon');
|
||||
});
|
||||
}
|
||||
|
||||
// Применить сохраненную тему при загрузке страницы
|
||||
applySavedTheme();
|
||||
|
||||
// Управление категориями
|
||||
document.querySelectorAll('.category h3').forEach(async function(header) {
|
||||
const categoryId = header.textContent.trim(); // Уникальный идентификатор категории по названию
|
||||
const content = header.nextElementSibling; // Содержимое категории
|
||||
const icon = header.querySelector('.toggle-icon'); // Иконка стрелки
|
||||
|
||||
// Проверка состояния категории при загрузке страницы
|
||||
const categoryStates = await StorageHelper.get('categoryStates') || {};
|
||||
const isCategoryOpen = categoryStates[categoryId] === 'true';
|
||||
|
||||
if (isCategoryOpen) {
|
||||
content.classList.add('show');
|
||||
if (icon) {
|
||||
icon.classList.replace('fa-chevron-down', 'fa-chevron-up');
|
||||
}
|
||||
} else {
|
||||
content.classList.remove('show');
|
||||
if (icon) {
|
||||
icon.classList.replace('fa-chevron-up', 'fa-chevron-down');
|
||||
}
|
||||
}
|
||||
|
||||
// Обработчик клика по заголовку категории
|
||||
header.addEventListener('click', async function() {
|
||||
content.classList.toggle('show'); // Переключаем видимость категории
|
||||
icon.classList.toggle('fa-chevron-down');
|
||||
icon.classList.toggle('fa-chevron-up');
|
||||
// Сохраняем состояние категории как открытое или закрытое
|
||||
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');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user