Initial commit

This commit is contained in:
Popov_Grigorii
2026-07-15 17:17:03 +03:00
commit cc078a2c8d
94 changed files with 76773 additions and 0 deletions
@@ -0,0 +1,235 @@
@page
@model ListmonkIntegration.Pages.Templates.CreateModel
@{
ViewData["Title"] = "Создание шаблона письма";
}
<h1>Создание шаблона письма</h1>
<hr />
@if (TempData["SuccessMessage"] != null)
{
<div class="alert alert-success">
@TempData["SuccessMessage"]
</div>
}
@if (TempData["ErrorMessage"] != null)
{
<div class="alert alert-danger">
@TempData["ErrorMessage"]
</div>
}
<div class="row">
<div class="col-md-8">
<form method="post">
<div class="mb-3">
<label class="form-label">Название шаблона</label>
<input type="text" asp-for="TemplateName" class="form-control" required placeholder="Например: Приветственное письмо" />
</div>
<div class="mb-3">
<label class="form-label">Тема письма</label>
<input type="text" asp-for="Subject" class="form-control" required placeholder="Например: Добро пожаловать, {{ .Subscriber.Name }}!" />
<div class="form-text">Можно использовать переменные: {{ .Subscriber.Name }}, {{ .Subscriber.Email }}, {{ .Subscriber.Attribs.ИмяАтрибута }}</div>
</div>
<div class="mb-3">
<label class="form-label">Выберите список рассылки (для просмотра атрибутов)</label>
<select asp-for="SelectedListId" class="form-select" asp-items="Model.Lists" id="listSelect">
<option value="">-- Выберите список --</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Текст письма</label>
<textarea asp-for="Body" id="editor" class="form-control" rows="15" placeholder="Введите текст письма здесь..."></textarea>
<div class="form-text">
Используйте редактор для форматирования текста. HTML код генерируется автоматически.
</div>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary">Создать шаблон</button>
<a asp-page="/Campaigns/Index" class="btn btn-secondary">Отмена</a>
</div>
</form>
</div>
<div class="col-md-4">
<!-- Кнопки для вставки переменных -->
<div class="card mb-3">
<div class="card-header">
<h5>Быстрая вставка переменных</h5>
</div>
<div class="card-body">
<div class="mb-2">
<label class="form-label small text-muted">Стандартные поля:</label>
<div class="btn-group-vertical w-100">
<button type="button" class="btn btn-sm btn-outline-primary mb-1" onclick="insertVariable('{{ .Subscriber.Name }}')">
Имя (Name)
</button>
<button type="button" class="btn btn-sm btn-outline-primary mb-1" onclick="insertVariable('{{ .Subscriber.Email }}')">
Email
</button>
</div>
</div>
<hr />
<div id="attributesButtons">
<label class="form-label small text-muted">Атрибуты из списка:</label>
<p class="text-muted small">Выберите список для загрузки атрибутов</p>
</div>
</div>
</div>
<!-- Примеры использования -->
<div class="card">
<div class="card-header">
<h5>Пример использования</h5>
</div>
<div class="card-body">
<small class="text-muted">
Переменные подставляются автоматически при отправке письма каждому получателю.
</small>
</div>
</div>
</div>
</div>
@section Scripts {
<script src="https://cdn.tiny.cloud/1/k6rorn68v4xqaesxvaroqlkyhfmncfoq8bxgxv3nd39szqrc/tinymce/8/tinymce.min.js" referrerpolicy="origin" crossorigin="anonymous"></script>
<script>
console.log('Script started');
// Глобальная переменная для хранения ссылки на редактор
window.tinymceEditor = null;
// Инициализация TinyMCE
tinymce.init({
selector: '#editor', // ИСПРАВЛЕНО: конкретный ID вместо всех textarea
api_key: 'k6rorn68v4xqaesxvaroqlkyhfmncfoq8bxgxv3nd39szqrc',
height: 400,
plugins: 'lists link table code bold italic underline',
toolbar: 'bold italic underline | h1 h2 h3 | bullist numlist | link unlink | code',
menubar: false,
statusbar: true,
branding: false,
content_style: 'body { font-family: Arial, sans-serif; font-size: 14px; }',
// ИСПРАВЛЕНО: добавлена setup функция для сохранения редактора
setup: function(editor) {
window.tinymceEditor = editor;
console.log('TinyMCE editor saved to window.tinymceEditor');
editor.on('change', function() {
tinymce.triggerSave();
});
},
init_instance_callback: function(editor) {
console.log('TinyMCE initialized successfully');
}
});
// Глобальная функция для вставки переменной
window.insertVariable = function(variable) {
console.log('insertVariable called with:', variable);
try {
// Проверяем, существует ли редактор
if (window.tinymceEditor && !window.tinymceEditor.destroyed) {
// Вставляем в TinyMCE
window.tinymceEditor.insertContent(variable);
window.tinymceEditor.focus();
console.log('✓ Inserted into TinyMCE editor');
} else if (window.tinymce && window.tinymce.editors.length > 0) {
// Альтернативный способ - берём первый редактор
var editor = window.tinymce.editors[0];
editor.insertContent(variable);
editor.focus();
console.log('✓ Inserted via tinymce.editors');
} else {
// Если TinyMCE не инициализирован, вставляем в textarea
var textarea = document.querySelector('textarea[name="Body"]');
if (textarea) {
var start = textarea.selectionStart || 0;
var end = textarea.selectionEnd || 0;
var text = textarea.value;
textarea.value = text.substring(0, start) + variable + text.substring(end);
textarea.focus();
console.log('✓ Inserted into textarea (TinyMCE not ready)');
} else {
console.error('✗ Textarea not found');
}
}
} catch (error) {
console.error('✗ Error inserting variable:', error);
}
};
console.log('Script loaded, waiting for DOM...');
// Загрузка атрибутов при выборе списка
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM loaded');
const listSelect = document.getElementById('listSelect');
if (listSelect) {
listSelect.addEventListener('change', async function() {
const listId = this.value;
const attributesDiv = document.getElementById('attributesButtons');
console.log('List selected, ID:', listId);
if (listId) {
attributesDiv.innerHTML = '<label class="form-label small text-muted">Атрибуты из списка:</label><div class="text-center"><div class="spinner-border spinner-border-sm" role="status"><span class="visually-hidden">Загрузка...</span></div></div>';
try {
const response = await fetch(`/api/lists/${listId}/attributes`);
console.log('API response status:', response.status);
if (response.ok) {
const attributes = await response.json();
console.log('Attributes loaded:', attributes);
displayAttributeButtons(attributes);
} else {
attributesDiv.innerHTML = '<label class="form-label small text-muted">Атрибуты из списка:</label><p class="text-danger small">Ошибка загрузки атрибутов.</p>';
}
} catch (error) {
console.error('Error loading attributes:', error);
attributesDiv.innerHTML = '<label class="form-label small text-muted">Атрибуты из списка:</label><p class="text-danger small">Ошибка загрузки атрибутов.</p>';
}
} else {
attributesDiv.innerHTML = '<label class="form-label small text-muted">Атрибуты из списка:</label><p class="text-muted small">Выберите список для загрузки атрибутов</p>';
}
});
}
});
// Отображение кнопок для атрибутов
window.displayAttributeButtons = function(attributes) {
console.log('displayAttributeButtons called with:', attributes);
const attributesDiv = document.getElementById('attributesButtons');
if (attributes && attributes.length > 0) {
let html = '<label class="form-label small text-muted">Атрибуты из списка:</label><div class="d-flex flex-wrap gap-1">';
attributes.forEach(attr => {
const variable = `{{ .Subscriber.Attribs.${attr} }}`;
console.log('Creating button for attr:', attr, 'variable:', variable);
// Экранируем специальные символы для onclick
const escapedVar = variable.replace(/'/g, "\\'").replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
html += `<button type="button" class="btn btn-sm btn-outline-secondary m-1" onclick="insertVariable('${escapedVar}')">
${attr}
</button>`;
});
html += '</div>';
attributesDiv.innerHTML = html;
console.log('Buttons created');
} else {
attributesDiv.innerHTML = '<label class="form-label small text-muted">Атрибуты из списка:</label><p class="text-muted small">У выбранного списка нет пользовательских атрибутов.</p>';
}
};
console.log('All scripts initialized');
</script>
}
@@ -0,0 +1,99 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using ListmonkIntegration.Services;
using ListmonkIntegration.Models;
namespace ListmonkIntegration.Pages.Templates
{
public class CreateModel : PageModel
{
private readonly IListmonkService _listmonkService;
private readonly ILogger<CreateModel> _logger;
[BindProperty]
public string TemplateName { get; set; } = string.Empty;
[BindProperty]
public string Subject { get; set; } = string.Empty;
[BindProperty]
public string Body { get; set; } = string.Empty;
[BindProperty]
public int? SelectedListId { get; set; }
public SelectList Lists { get; set; } = new SelectList(Enumerable.Empty<SelectListItem>());
public string? DebugInfo { get; set; }
public CreateModel(IListmonkService listmonkService, ILogger<CreateModel> logger)
{
_listmonkService = listmonkService;
_logger = logger;
}
public async Task OnGetAsync()
{
_logger.LogInformation("OnGetAsync called, loading lists...");
await LoadListsAsync();
}
public async Task<IActionResult> OnPostAsync()
{
if (string.IsNullOrWhiteSpace(TemplateName) || string.IsNullOrWhiteSpace(Subject) || string.IsNullOrWhiteSpace(Body))
{
TempData["ErrorMessage"] = "Çàïîëíèòå âñå îáÿçàòåëüíûå ïîëÿ";
await LoadListsAsync();
return Page();
}
try
{
_logger.LogInformation($"Creating template: {TemplateName}");
var templateId = await _listmonkService.CreateTemplateAsync(
TemplateName,
Subject,
Body
);
_logger.LogInformation($"Template created successfully with ID: {templateId}");
TempData["SuccessMessage"] = $"Øàáëîí \"{TemplateName}\" óñïåøíî ñîçäàí!";
return RedirectToPage("/Campaigns/Index");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating template");
TempData["ErrorMessage"] = $"Îøèáêà ïðè ñîçäàíèè øàáëîíà: {ex.Message}";
await LoadListsAsync();
return Page();
}
}
private async Task LoadListsAsync()
{
try
{
_logger.LogInformation("Calling GetListsAsync...");
var lists = await _listmonkService.GetListsAsync();
_logger.LogInformation($"Got {lists.Count} lists from Listmonk");
foreach (var list in lists)
{
_logger.LogInformation($" List: ID={list.Id}, Name={list.Name}, Subscribers={list.SubscriberCount}");
}
Lists = new SelectList(lists, "Id", "Name");
DebugInfo = $"Çàãðóæåíî ñïèñêîâ: {lists.Count}";
}
catch (Exception ex)
{
_logger.LogError(ex, "Error loading lists");
DebugInfo = $"Îøèáêà çàãðóçêè ñïèñêîâ: {ex.Message}";
Lists = new SelectList(Enumerable.Empty<SelectListItem>());
}
}
}
}