From addb73b7713134cc38d65ea548be5f2aa71a0510 Mon Sep 17 00:00:00 2001 From: Link <131011403+LinkF2kkk@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:56:37 +0300 Subject: [PATCH 1/2] Export file update --- Endpoints/ExportEndpoints.cs | 461 ++++++++++++++++++++++++++++++++ Program.cs | 3 +- WebEditor.csproj | 2 +- client/package-lock.json | 311 ++++++++------------- client/package.json | 9 + client/src/App.js | 15 +- client/src/hooks/useAnalysis.js | 44 +-- 7 files changed, 609 insertions(+), 236 deletions(-) create mode 100644 Endpoints/ExportEndpoints.cs diff --git a/Endpoints/ExportEndpoints.cs b/Endpoints/ExportEndpoints.cs new file mode 100644 index 0000000..6e09a39 --- /dev/null +++ b/Endpoints/ExportEndpoints.cs @@ -0,0 +1,461 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using DocumentFormat.OpenXml; +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using WColor = DocumentFormat.OpenXml.Wordprocessing.Color; +using QColors = QuestPDF.Helpers.Colors; + +namespace WebEditor.Endpoints +{ + public static class ExportEndpoints + { + // Цвета категорий для PDF + private static readonly Dictionary CategoryColors = new() + { + { "Орфография", "#FCA5A5" }, + { "Пунктуация", "#FCD34D" }, + { "Грамматика", "#F9A8D4" }, + { "Тавтология", "#A5B4FC" }, + { "Стиль", "#6EE7B7" }, + { "Канцеляризм", "#D8B4FE" }, + }; + + public static void MapExportEndpoints(this WebApplication app) + { + app.MapPost("/export/docx", (ExportPayload request) => + { + if (string.IsNullOrWhiteSpace(request.Text)) + return Results.BadRequest("Текст не может быть пустым"); + try + { + var stream = GenerateDocxReport(request.Text, request.AnalysisJson); + return Results.File(stream, + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + $"report_{DateTime.Now:yyyyMMdd_HHmm}.docx"); + } + catch (Exception ex) + { + return Results.BadRequest($"Ошибка генерации DOCX: {ex.Message}"); + } + }); + + app.MapPost("/export/pdf", (ExportPayload request) => + { + if (string.IsNullOrWhiteSpace(request.Text)) + return Results.BadRequest("Текст не может быть пустым"); + try + { + var issues = ParseIssues(request.AnalysisJson); + var stats = ParseStatistics(request.AnalysisJson); + + QuestPDF.Settings.License = LicenseType.Community; + + var pdfBytes = QuestPDF.Fluent.Document.Create(container => + { + container.Page(page => + { + page.Size(PageSizes.A4); + page.Margin(1.5f, Unit.Centimetre); + page.DefaultTextStyle(x => x.FontSize(10).FontFamily("Arial")); + + // ===== ШАПКА ===== + page.Header().Column(col => + { + col.Item().Row(row => + { + row.RelativeItem().Column(c => + { + c.Item().Text("Text Analyzer") + .FontSize(9).FontColor("#94a3b8"); + c.Item().Text("Отчёт анализа текста") + .SemiBold().FontSize(20).FontColor("#1e293b"); + }); + row.ConstantItem(200).AlignRight().Column(c => + { + c.Item().Text($"Дата: {DateTime.Now:dd.MM.yyyy HH:mm}") + .FontSize(9).FontColor("#64748b"); + c.Item().Text($"Проблем найдено: {issues.Count}") + .FontSize(9).FontColor("#64748b"); + }); + }); + + col.Item().PaddingTop(8).LineHorizontal(1).LineColor("#e2e8f0"); + + // Статистика блоками + col.Item().PaddingTop(10).Row(row => + { + StatBox(row, "Слов", stats.WordCount.ToString()); + StatBox(row, "Предложений", stats.SentenceCount.ToString()); + StatBox(row, "Читаемость", stats.ReadabilityScore.ToString("F1")); + StatBox(row, "Водность", $"{stats.WaterScore}%"); + StatBox(row, "Проблем", issues.Count.ToString()); + }); + + col.Item().PaddingTop(10).LineHorizontal(1).LineColor("#e2e8f0"); + }); + + // ===== КОНТЕНТ ===== + page.Content().PaddingTop(16).Column(col => + { + col.Spacing(16); + + // Исходный текст + col.Item().Column(c => + { + c.Item().Text("Исходный текст") + .SemiBold().FontSize(13).FontColor("#1e293b"); + c.Item().PaddingTop(6) + .Background("#f8fafc") + .Border(1).BorderColor("#e2e8f0") + .Padding(10) + .Text(request.Text) + .FontColor("#334155"); + }); + + // Ошибки сгруппированные по категории + if (issues.Count == 0) + { + col.Item() + .Background("#D1FAE5") + .Padding(12) + .Text("Проблем не найдено! Текст выглядит хорошо.") + .FontColor("#065F46"); + } + else + { + col.Item().Text("Найденные проблемы") + .SemiBold().FontSize(13).FontColor("#1e293b"); + + var grouped = issues.GroupBy(i => i.Category); + foreach (var group in grouped) + { + var color = CategoryColors.GetValueOrDefault(group.Key, "#E2E8F0"); + + col.Item().Column(c => + { + // Заголовок категории + c.Item().Background(color) + .Padding(6) + .Text($"{group.Key} ({group.Count()})") + .SemiBold().FontColor("#1e293b"); + + // Таблица ошибок категории + c.Item().Table(table => + { + table.ColumnsDefinition(cols => + { + cols.RelativeColumn(3); // Описание + cols.RelativeColumn(3); // Было + cols.RelativeColumn(3); // Стало + }); + + // Заголовки + table.Header(h => + { + foreach (var title in new[] { "Описание", "Было", "Стало" }) + { + h.Cell() + .Background("#f1f5f9") + .BorderBottom(1).BorderColor("#e2e8f0") + .Padding(6) + .Text(title) + .SemiBold().FontSize(9).FontColor("#475569"); + } + }); + + // Строки + foreach (var issue in group) + { + table.Cell() + .BorderBottom(1).BorderColor("#f1f5f9") + .Padding(6) + .Text(issue.Description) + .FontSize(9).FontColor("#334155"); + + table.Cell() + .BorderBottom(1).BorderColor("#f1f5f9") + .Padding(6) + .Text(issue.Original) + .FontSize(9).FontColor("#dc2626"); + + table.Cell() + .BorderBottom(1).BorderColor("#f1f5f9") + .Padding(6) + .Text(issue.Suggestion) + .FontSize(9).FontColor("#16a34a"); + } + }); + }); + } + } + }); + + // ===== ФУТЕР ===== + page.Footer().AlignCenter() + .Text(x => + { + x.Span("Text Analyzer · Страница ") + .FontSize(8).FontColor("#94a3b8"); + x.CurrentPageNumber().FontSize(8).FontColor("#94a3b8"); + x.Span(" из ").FontSize(8).FontColor("#94a3b8"); + x.TotalPages().FontSize(8).FontColor("#94a3b8"); + }); + }); + }).GeneratePdf(); + + return Results.File(pdfBytes, "application/pdf", + $"report_{DateTime.Now:yyyyMMdd_HHmm}.pdf"); + } + catch (Exception ex) + { + return Results.BadRequest($"Ошибка генерации PDF: {ex.Message}"); + } + }); + } + + // ===== Вспомогательный метод для блока статистики ===== + private static void StatBox(RowDescriptor row, string label, string value) + { + row.RelativeItem().Border(1).BorderColor("#e2e8f0") + .Background("#f8fafc") + .Padding(8) + .Column(c => + { + c.Item().AlignCenter().Text(value) + .SemiBold().FontSize(16).FontColor("#4F46E5"); + c.Item().AlignCenter().Text(label) + .FontSize(8).FontColor("#64748b"); + }); + } + + // ===== DOCX ===== + private static MemoryStream GenerateDocxReport(string text, string analysisJson) + { + var stream = new MemoryStream(); + var issues = ParseIssues(analysisJson); + var stats = ParseStatistics(analysisJson); + + using (var wordDoc = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document)) + { + var mainPart = wordDoc.AddMainDocumentPart(); + mainPart.Document = new DocumentFormat.OpenXml.Wordprocessing.Document(); + var body = new Body(); + + // Заголовок + body.AppendChild(new Paragraph( + new ParagraphProperties(new Justification { Val = JustificationValues.Center }), + new Run( + new RunProperties(new Bold(), new FontSize { Val = "36" }, new WColor { Val = "1e293b" }), + new Text("Отчёт анализа текста") + ) + )); + + // Дата + body.AppendChild(new Paragraph( + new ParagraphProperties(new Justification { Val = JustificationValues.Center }), + new Run( + new RunProperties(new WColor { Val = "64748b" }, new FontSize { Val = "18" }), + new Text($"Сформирован: {DateTime.Now:dd.MM.yyyy HH:mm} · Проблем: {issues.Count}") + ) + )); + + body.AppendChild(new Paragraph(new Run(new Text("")))); + + // Статистика — таблица + body.AppendChild(DocxBoldParagraph("Статистика текста")); + body.AppendChild(GenerateStatsTable(stats, issues.Count)); + body.AppendChild(new Paragraph(new Run(new Text("")))); + + // Исходный текст + body.AppendChild(DocxBoldParagraph("Исходный текст")); + var textPara = new Paragraph(); + var textRun = new Run(); + foreach (var line in text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)) + { + textRun.AppendChild(new Text(line) { Space = SpaceProcessingModeValues.Preserve }); + textRun.AppendChild(new Break()); + } + textPara.AppendChild(textRun); + body.AppendChild(textPara); + body.AppendChild(new Paragraph(new Run(new Text("")))); + + // Ошибки по категориям + body.AppendChild(DocxBoldParagraph($"Найденные проблемы ({issues.Count})")); + + if (issues.Count == 0) + { + body.AppendChild(new Paragraph( + new Run(new RunProperties(new WColor { Val = "065F46" }), + new Text("Проблем не найдено! Текст выглядит хорошо.")) + )); + } + else + { + var grouped = issues.GroupBy(i => i.Category); + foreach (var group in grouped) + { + // Заголовок категории + body.AppendChild(new Paragraph( + new Run( + new RunProperties(new Bold(), new WColor { Val = "4F46E5" }), + new Text($"{group.Key} ({group.Count()})") + ) + )); + + // Таблица ошибок + body.AppendChild(GenerateIssuesTable(group.ToList())); + body.AppendChild(new Paragraph(new Run(new Text("")))); + } + } + + mainPart.Document.AppendChild(body); + mainPart.Document.Save(); + } + + stream.Position = 0; + return stream; + } + + private static Table GenerateStatsTable(StatisticsDto stats, int issueCount) + { + var table = new Table(); + table.AppendChild(new TableProperties( + new TableBorders( + new TopBorder { Val = BorderValues.Single, Size = 4 }, + new BottomBorder { Val = BorderValues.Single, Size = 4 }, + new LeftBorder { Val = BorderValues.Single, Size = 4 }, + new RightBorder { Val = BorderValues.Single, Size = 4 }, + new InsideHorizontalBorder { Val = BorderValues.Single, Size = 4 }, + new InsideVerticalBorder { Val = BorderValues.Single, Size = 4 } + ), + new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct } + )); + + table.Append(new TableRow( + CreateTableCell("Слов", true), + CreateTableCell("Предложений", true), + CreateTableCell("Читаемость", true), + CreateTableCell("Водность", true), + CreateTableCell("Проблем", true) + )); + + table.Append(new TableRow( + CreateTableCell(stats.WordCount.ToString()), + CreateTableCell(stats.SentenceCount.ToString()), + CreateTableCell(stats.ReadabilityScore.ToString("F1")), + CreateTableCell($"{stats.WaterScore}%"), + CreateTableCell(issueCount.ToString()) + )); + + return table; + } + + private static Table GenerateIssuesTable(List issues) + { + var table = new Table(); + table.AppendChild(new TableProperties( + new TableBorders( + new TopBorder { Val = BorderValues.Single, Size = 4 }, + new BottomBorder { Val = BorderValues.Single, Size = 4 }, + new LeftBorder { Val = BorderValues.Single, Size = 4 }, + new RightBorder { Val = BorderValues.Single, Size = 4 }, + new InsideHorizontalBorder { Val = BorderValues.Single, Size = 4 }, + new InsideVerticalBorder { Val = BorderValues.Single, Size = 4 } + ), + new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct } + )); + + table.Append(new TableRow( + CreateTableCell("Описание", true), + CreateTableCell("Было", true), + CreateTableCell("Стало", true) + )); + + foreach (var issue in issues) + { + table.Append(new TableRow( + CreateTableCell(issue.Description), + CreateTableCell(issue.Original), + CreateTableCell(issue.Suggestion) + )); + } + + return table; + } + + private static Paragraph DocxBoldParagraph(string text) => + new Paragraph( + new Run( + new RunProperties(new Bold(), new FontSize { Val = "24" }), + new Text(text) + ) + ); + + private static TableCell CreateTableCell(string text, bool isHeader = false) + { + var run = new Run(new Text(text ?? "")); + if (isHeader) run.RunProperties = new RunProperties(new Bold()); + return new TableCell(new Paragraph(run)); + } + + // ===== ПАРСИНГ ===== + private static List ParseIssues(string analysisJson) + { + if (string.IsNullOrWhiteSpace(analysisJson)) return new(); + try + { + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var doc = JsonDocument.Parse(analysisJson); + if (doc.RootElement.TryGetProperty("issues", out var issuesProp)) + return issuesProp.Deserialize>(options) ?? new(); + return doc.RootElement.Deserialize>(options) ?? new(); + } + catch { return new(); } + } + + private static StatisticsDto ParseStatistics(string analysisJson) + { + if (string.IsNullOrWhiteSpace(analysisJson)) return new(); + try + { + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var doc = JsonDocument.Parse(analysisJson); + if (doc.RootElement.TryGetProperty("statistics", out var statsProp)) + return statsProp.Deserialize(options) ?? new(); + return new(); + } + catch { return new(); } + } + } + + public class ExportPayload + { + public string Text { get; set; } = string.Empty; + public string AnalysisJson { get; set; } = string.Empty; + } + + public class IssueExportDto + { + public string Category { get; set; } = string.Empty; + public string Original { get; set; } = string.Empty; + public string Suggestion { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + } + + public class StatisticsDto + { + public int WordCount { get; set; } + public int SentenceCount { get; set; } + public double ReadabilityScore { get; set; } + public double WaterScore { get; set; } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index fd003d7..61934d0 100644 --- a/Program.cs +++ b/Program.cs @@ -16,7 +16,7 @@ public static void Main(string[] args) options.UseSqlServer( builder.Configuration.GetConnectionString("Default"))); - // SEVICES + // SERVICES builder.Services.AddSingleton(); // CORS @@ -38,6 +38,7 @@ public static void Main(string[] args) // ENDPOINTS app.MapAnalysisEndpoints(); + app.MapExportEndpoints(); app.Run(); } diff --git a/WebEditor.csproj b/WebEditor.csproj index f20b57a..d8fadbe 100644 --- a/WebEditor.csproj +++ b/WebEditor.csproj @@ -13,7 +13,6 @@ - @@ -27,6 +26,7 @@ + diff --git a/client/package-lock.json b/client/package-lock.json index 7700837..499c5ed 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -12,6 +12,15 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^13.5.0", + "@tiptap/extension-bullet-list": "^3.22.4", + "@tiptap/extension-color": "^3.22.4", + "@tiptap/extension-font-family": "^3.22.4", + "@tiptap/extension-heading": "^3.22.4", + "@tiptap/extension-history": "^3.22.4", + "@tiptap/extension-ordered-list": "^3.22.4", + "@tiptap/extension-text-align": "^3.22.4", + "@tiptap/extension-text-style": "^3.22.4", + "@tiptap/extension-underline": "^3.22.4", "@tiptap/pm": "^3.22.3", "@tiptap/react": "^3.22.3", "@tiptap/starter-kit": "^3.22.3", @@ -3017,12 +3026,6 @@ } } }, - "node_modules/@remirror/core-constants": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", - "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", - "license": "MIT" - }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -3468,16 +3471,16 @@ } }, "node_modules/@tiptap/core": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.3.tgz", - "integrity": "sha512-Dv9MKK5BDWCF0N2l6/Pxv3JNCce2kwuWf2cKMBc2bEetx0Pn6o7zlFmSxMvYK4UtG1Tw9Yg/ZHi6QOFWK0Zm9Q==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.4.tgz", + "integrity": "sha512-vGIGm/HpqLg8EAAQXQ+koV+/S828OEpzocfWcPOwo1u2QUVf9dQG47Yy6JJ8zFFaJwfv4dBcOXli+7BrJwsxDQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/pm": "^3.22.3" + "@tiptap/pm": "3.22.4" } }, "node_modules/@tiptap/extension-blockquote": { @@ -3525,16 +3528,16 @@ } }, "node_modules/@tiptap/extension-bullet-list": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.22.3.tgz", - "integrity": "sha512-xOmW/b1hgECIE6r3IeZvKn4VVlG3+dfTjCWE6lnnyLaqdNkNhKS1CwUmDZdYNLUS2ryIUtgz5ID1W/8A3PhbiA==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.22.4.tgz", + "integrity": "sha512-TB+d3fGcTixYjO7coKqTr1mGTJuqr8hjDCPUFgzuvKyJnBhqWITmBzQ/8CLq4rr6mihgGURbD3N+xkQuPAKFiw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "^3.22.3" + "@tiptap/extension-list": "3.22.4" } }, "node_modules/@tiptap/extension-code": { @@ -3564,6 +3567,19 @@ "@tiptap/pm": "^3.22.3" } }, + "node_modules/@tiptap/extension-color": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-3.22.4.tgz", + "integrity": "sha512-1vDuVsrOETshe4j4nZhWalbKYcWfNybRCe30h829ExX06XwFryUYLb/LgTIaGCr9beWZUldsK+vOkBWdDTGMTw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-text-style": "3.22.4" + } + }, "node_modules/@tiptap/extension-document": { "version": "3.22.3", "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.22.3.tgz", @@ -3606,6 +3622,19 @@ "@tiptap/pm": "^3.22.3" } }, + "node_modules/@tiptap/extension-font-family": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-font-family/-/extension-font-family-3.22.4.tgz", + "integrity": "sha512-e4DSZTQeM0P/ko3mMeZIb/otLUWCv/vobtEX5FGwstl9RJzaso5cTA6avMQaAff+xerovxvhkCMfye0+PL8YGw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-text-style": "3.22.4" + } + }, "node_modules/@tiptap/extension-gapcursor": { "version": "3.22.3", "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.22.3.tgz", @@ -3633,16 +3662,29 @@ } }, "node_modules/@tiptap/extension-heading": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.22.3.tgz", - "integrity": "sha512-XBHuhiEV2EEhZHpOLcplLqAmBIhJciU3I6AtwmqeEqDC0P114uMEfAO7JGlbBZdCYotNer26PKnu44TBTeNtkw==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.22.4.tgz", + "integrity": "sha512-TUaj5f0Ir5qy9HKKt2ocnwfXKpZDYeHgbbP9gshKFzdq5PLe1RbIgkjfy6bnoI865cYjmPYWRjcT7XsKyIcb9Q==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.22.4" + } + }, + "node_modules/@tiptap/extension-history": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-3.22.4.tgz", + "integrity": "sha512-yk1RrBJty4F3Os+w6zIS3nOSOKy+iIax3UyXwCKiP+JI4nsTlYvgQTgTtT9Wqa14ZrypasV8gAuQeTNf/spFLg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "3.22.4" } }, "node_modules/@tiptap/extension-horizontal-rule": { @@ -3690,17 +3732,17 @@ } }, "node_modules/@tiptap/extension-list": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.22.3.tgz", - "integrity": "sha512-rqvv/dtqwbX+8KnPv0eMYp6PnBcuhPMol5cv1GlS8Nq/Cxt68EWGUHBuTFesw+hdnRQLmKwzoO1DlRn7PhxYRQ==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.22.4.tgz", + "integrity": "sha512-Xe8UFvvHmyp/c/TJsFwlwU9CWACYbBirNsluJ3U1+H8BTu1wqdrT/AXR5uIXeyCl5kiWKgX5q71eHWbYFOrqrg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" + "@tiptap/core": "3.22.4", + "@tiptap/pm": "3.22.4" } }, "node_modules/@tiptap/extension-list-item": { @@ -3730,16 +3772,16 @@ } }, "node_modules/@tiptap/extension-ordered-list": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.22.3.tgz", - "integrity": "sha512-orAghtmd+K4Euu4BgI1hG+iZDXBYOyl5YTwiLBc2mQn+pqtZ9LqaH2us4ETwEwNP3/IWXGSAimUZ19nuL+eM2w==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.22.4.tgz", + "integrity": "sha512-w77hPVf7pcHt97vfrybg/l0t5CimCd4y75OJKuHuo3CfgM5xbUP/gaPNMDyLLe7MYole/UHi/XvG3XjgzqTzAw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "^3.22.3" + "@tiptap/extension-list": "3.22.4" } }, "node_modules/@tiptap/extension-paragraph": { @@ -3781,55 +3823,75 @@ "@tiptap/core": "^3.22.3" } }, + "node_modules/@tiptap/extension-text-align": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.22.4.tgz", + "integrity": "sha512-W7TnXWSyfDXSatGXp5y/CahE8G4btrQPb0/sy+eG+42FxdzYsqvh1ys3OE9j2XSuTrZ1q/tZA/NLPUkc7vw6Kw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.22.4" + } + }, + "node_modules/@tiptap/extension-text-style": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-3.22.4.tgz", + "integrity": "sha512-24DVBdySNKq3ovY+v9ERVxAyHStDa6ftUlyoHuZv0YXQ2amjUNOmqQtGEHBIULpCbBb1jZ+atHhv9MBZ0Ia9Pw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.22.4" + } + }, "node_modules/@tiptap/extension-underline": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.22.3.tgz", - "integrity": "sha512-Ch6CBWRa5w90yYSPUW6x9Py9JdrXMqk3pZ9OIlMYD8A7BqyZGfiHerX7XDMYDS09KjyK3U9XH60/zxYOzXdDLA==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.22.4.tgz", + "integrity": "sha512-08kGdbhIrA6h10GWXqOkqIveaBj5tmxclK208/nUIAlonI9hPd739vu7fmVtpnmqCnSSNpoRtU4u6Gj5at0ZpA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.22.4" } }, "node_modules/@tiptap/extensions": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.22.3.tgz", - "integrity": "sha512-s5eiMq0m5N6N+W7dU6rd60KgZyyCD7FvtPNNswISfPr12EQwJBfbjWwTqd0UKNzA4fNrhQEERXnzORkykttPeA==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.22.4.tgz", + "integrity": "sha512-fOe8VptJvLPs32bNdUYo8SRyljwqKNQVXWW056VoXIc5en/59OdJlJQVeHI0jRRciH3MtrqODi/gfJR0VHNZ8A==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" + "@tiptap/core": "3.22.4", + "@tiptap/pm": "3.22.4" } }, "node_modules/@tiptap/pm": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.3.tgz", - "integrity": "sha512-NjfWjZuvrqmpICT+GZWNIjtOdhPyqFKDMtQy7tsQ5rErM9L2ZQdy/+T/BKSO1JdTeBhdg9OP+0yfsqoYp2aT6A==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.4.tgz", + "integrity": "sha512-hj8Qka6WcHRllHUdeSjDnq2XaisUo4KsoGJc1WcFpoa1Yd+OeD861zUMnV7DFVGdZRy45Obht0CUYJpXQ4yA4w==", "license": "MIT", "dependencies": { "prosemirror-changeset": "^2.3.0", - "prosemirror-collab": "^1.3.1", "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", - "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", - "prosemirror-markdown": "^1.13.1", - "prosemirror-menu": "^1.2.4", "prosemirror-model": "^1.24.1", - "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.5.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.4", - "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.38.1" }, @@ -4123,28 +4185,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "license": "MIT" }, - "node_modules/@types/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "license": "MIT" - }, - "node_modules/@types/markdown-it": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", - "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", - "license": "MIT", - "dependencies": { - "@types/linkify-it": "^5", - "@types/mdurl": "^2" - } - }, - "node_modules/@types/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "license": "MIT" - }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -6416,12 +6456,6 @@ "node": ">=10" } }, - "node_modules/crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -11585,15 +11619,6 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, "node_modules/linkifyjs": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz", @@ -11756,41 +11781,6 @@ "tmpl": "1.0.5" } }, - "node_modules/markdown-it": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", - "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/markdown-it/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/markdown-it/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -11806,12 +11796,6 @@ "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", "license": "CC0-1.0" }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "license": "MIT" - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -14095,15 +14079,6 @@ "prosemirror-transform": "^1.0.0" } }, - "node_modules/prosemirror-collab": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", - "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0" - } - }, "node_modules/prosemirror-commands": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", @@ -14150,16 +14125,6 @@ "rope-sequence": "^1.3.0" } }, - "node_modules/prosemirror-inputrules": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", - "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" - } - }, "node_modules/prosemirror-keymap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", @@ -14170,29 +14135,6 @@ "w3c-keyname": "^2.2.0" } }, - "node_modules/prosemirror-markdown": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz", - "integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==", - "license": "MIT", - "dependencies": { - "@types/markdown-it": "^14.0.0", - "markdown-it": "^14.0.0", - "prosemirror-model": "^1.25.0" - } - }, - "node_modules/prosemirror-menu": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz", - "integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==", - "license": "MIT", - "dependencies": { - "crelt": "^1.0.0", - "prosemirror-commands": "^1.0.0", - "prosemirror-history": "^1.0.0", - "prosemirror-state": "^1.0.0" - } - }, "node_modules/prosemirror-model": { "version": "1.25.4", "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", @@ -14202,15 +14144,6 @@ "orderedmap": "^2.0.0" } }, - "node_modules/prosemirror-schema-basic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", - "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.25.0" - } - }, "node_modules/prosemirror-schema-list": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", @@ -14246,21 +14179,6 @@ "prosemirror-view": "^1.41.4" } }, - "node_modules/prosemirror-trailing-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", - "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", - "license": "MIT", - "dependencies": { - "@remirror/core-constants": "3.0.0", - "escape-string-regexp": "^4.0.0" - }, - "peerDependencies": { - "prosemirror-model": "^1.22.1", - "prosemirror-state": "^1.4.2", - "prosemirror-view": "^1.33.8" - } - }, "node_modules/prosemirror-transform": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz", @@ -14324,15 +14242,6 @@ "node": ">=6" } }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -16956,12 +16865,6 @@ "node": ">=4.2.0" } }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "license": "MIT" - }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", diff --git a/client/package.json b/client/package.json index 4c4f88c..1ba59ea 100644 --- a/client/package.json +++ b/client/package.json @@ -7,6 +7,15 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^13.5.0", + "@tiptap/extension-bullet-list": "^3.22.4", + "@tiptap/extension-color": "^3.22.4", + "@tiptap/extension-font-family": "^3.22.4", + "@tiptap/extension-heading": "^3.22.4", + "@tiptap/extension-history": "^3.22.4", + "@tiptap/extension-ordered-list": "^3.22.4", + "@tiptap/extension-text-align": "^3.22.4", + "@tiptap/extension-text-style": "^3.22.4", + "@tiptap/extension-underline": "^3.22.4", "@tiptap/pm": "^3.22.3", "@tiptap/react": "^3.22.3", "@tiptap/starter-kit": "^3.22.3", diff --git a/client/src/App.js b/client/src/App.js index cedae0a..57d310f 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -15,7 +15,7 @@ export default function App() { } const handleExport = (format) => { - exportReport(format, editorText, result, 'ru') + exportReport(format, editorText, result) } return ( @@ -48,13 +48,12 @@ export default function App() { {showReport && ( - setShowReport(false)} - onExport={handleExport} - /> + setShowReport(false)} + onExport={handleExport} + /> )} ) diff --git a/client/src/hooks/useAnalysis.js b/client/src/hooks/useAnalysis.js index 56f5506..90e1830 100644 --- a/client/src/hooks/useAnalysis.js +++ b/client/src/hooks/useAnalysis.js @@ -49,30 +49,30 @@ export function useAnalysis() { } }, []) - const exportReport = useCallback(async (format, text, analysisResult, language) => { - try { - const response = await fetch(`${API_URL}/export/${format}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - text, - analysisJson: JSON.stringify(analysisResult), - language - }) - }) + const exportReport = useCallback(async (format, text, analysisResult) => { + try { + const response = await fetch(`${API_URL}/export/${format}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + text: text, + analysisJson: JSON.stringify(analysisResult) // весь объект СЃ issues Рё statistics + }) + }) - if (!response.ok) throw new Error(`Ошибка экспорта: ${response.status}`) + if (!response.ok) throw new Error(`Ошибка: ${response.status}`) - const blob = await response.blob() - const url = URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = `report.${format}` - a.click() - URL.revokeObjectURL(url) - } catch (err) { - setError(`Ошибка экспорта: ${err.message}`) - } + const blob = await response.blob() + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `report.${format}` + a.click() + URL.revokeObjectURL(url) + + } catch (err) { + setError(`Ошибка экспорта: ${err.message}`) + } }, []) const reset = useCallback(() => { From e8d093ec9d092036ba12affcc3b55c4a26032c2b Mon Sep 17 00:00:00 2001 From: Link <131011403+LinkF2kkk@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:59:37 +0300 Subject: [PATCH 2/2] Update ExportEndpoints.cs --- Endpoints/ExportEndpoints.cs | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/Endpoints/ExportEndpoints.cs b/Endpoints/ExportEndpoints.cs index 6e09a39..908bc06 100644 --- a/Endpoints/ExportEndpoints.cs +++ b/Endpoints/ExportEndpoints.cs @@ -18,7 +18,7 @@ namespace WebEditor.Endpoints { public static class ExportEndpoints { - // Цвета категорий для PDF + // COLOR private static readonly Dictionary CategoryColors = new() { { "Орфография", "#FCA5A5" }, @@ -67,7 +67,7 @@ public static void MapExportEndpoints(this WebApplication app) page.Margin(1.5f, Unit.Centimetre); page.DefaultTextStyle(x => x.FontSize(10).FontFamily("Arial")); - // ===== ШАПКА ===== + // HEAD page.Header().Column(col => { col.Item().Row(row => @@ -90,7 +90,6 @@ public static void MapExportEndpoints(this WebApplication app) col.Item().PaddingTop(8).LineHorizontal(1).LineColor("#e2e8f0"); - // Статистика блоками col.Item().PaddingTop(10).Row(row => { StatBox(row, "Слов", stats.WordCount.ToString()); @@ -103,12 +102,11 @@ public static void MapExportEndpoints(this WebApplication app) col.Item().PaddingTop(10).LineHorizontal(1).LineColor("#e2e8f0"); }); - // ===== КОНТЕНТ ===== + // CONTENT page.Content().PaddingTop(16).Column(col => { col.Spacing(16); - // Исходный текст col.Item().Column(c => { c.Item().Text("Исходный текст") @@ -121,7 +119,6 @@ public static void MapExportEndpoints(this WebApplication app) .FontColor("#334155"); }); - // Ошибки сгруппированные по категории if (issues.Count == 0) { col.Item() @@ -142,23 +139,20 @@ public static void MapExportEndpoints(this WebApplication app) col.Item().Column(c => { - // Заголовок категории c.Item().Background(color) .Padding(6) .Text($"{group.Key} ({group.Count()})") .SemiBold().FontColor("#1e293b"); - // Таблица ошибок категории c.Item().Table(table => { table.ColumnsDefinition(cols => { - cols.RelativeColumn(3); // Описание - cols.RelativeColumn(3); // Было - cols.RelativeColumn(3); // Стало + cols.RelativeColumn(3); + cols.RelativeColumn(3); + cols.RelativeColumn(3); }); - // Заголовки table.Header(h => { foreach (var title in new[] { "Описание", "Было", "Стало" }) @@ -172,7 +166,6 @@ public static void MapExportEndpoints(this WebApplication app) } }); - // Строки foreach (var issue in group) { table.Cell() @@ -199,7 +192,7 @@ public static void MapExportEndpoints(this WebApplication app) } }); - // ===== ФУТЕР ===== + // FOOTER page.Footer().AlignCenter() .Text(x => { @@ -222,7 +215,7 @@ public static void MapExportEndpoints(this WebApplication app) }); } - // ===== Вспомогательный метод для блока статистики ===== + // SUP METHOD private static void StatBox(RowDescriptor row, string label, string value) { row.RelativeItem().Border(1).BorderColor("#e2e8f0") @@ -237,7 +230,7 @@ private static void StatBox(RowDescriptor row, string label, string value) }); } - // ===== DOCX ===== + // DOCX private static MemoryStream GenerateDocxReport(string text, string analysisJson) { var stream = new MemoryStream(); @@ -250,7 +243,6 @@ private static MemoryStream GenerateDocxReport(string text, string analysisJson) mainPart.Document = new DocumentFormat.OpenXml.Wordprocessing.Document(); var body = new Body(); - // Заголовок body.AppendChild(new Paragraph( new ParagraphProperties(new Justification { Val = JustificationValues.Center }), new Run( @@ -259,7 +251,6 @@ private static MemoryStream GenerateDocxReport(string text, string analysisJson) ) )); - // Дата body.AppendChild(new Paragraph( new ParagraphProperties(new Justification { Val = JustificationValues.Center }), new Run( @@ -270,12 +261,10 @@ private static MemoryStream GenerateDocxReport(string text, string analysisJson) body.AppendChild(new Paragraph(new Run(new Text("")))); - // Статистика — таблица body.AppendChild(DocxBoldParagraph("Статистика текста")); body.AppendChild(GenerateStatsTable(stats, issues.Count)); body.AppendChild(new Paragraph(new Run(new Text("")))); - // Исходный текст body.AppendChild(DocxBoldParagraph("Исходный текст")); var textPara = new Paragraph(); var textRun = new Run(); @@ -288,7 +277,6 @@ private static MemoryStream GenerateDocxReport(string text, string analysisJson) body.AppendChild(textPara); body.AppendChild(new Paragraph(new Run(new Text("")))); - // Ошибки по категориям body.AppendChild(DocxBoldParagraph($"Найденные проблемы ({issues.Count})")); if (issues.Count == 0) @@ -303,7 +291,6 @@ private static MemoryStream GenerateDocxReport(string text, string analysisJson) var grouped = issues.GroupBy(i => i.Category); foreach (var group in grouped) { - // Заголовок категории body.AppendChild(new Paragraph( new Run( new RunProperties(new Bold(), new WColor { Val = "4F46E5" }), @@ -311,7 +298,6 @@ private static MemoryStream GenerateDocxReport(string text, string analysisJson) ) )); - // Таблица ошибок body.AppendChild(GenerateIssuesTable(group.ToList())); body.AppendChild(new Paragraph(new Run(new Text("")))); } @@ -407,7 +393,7 @@ private static TableCell CreateTableCell(string text, bool isHeader = false) return new TableCell(new Paragraph(run)); } - // ===== ПАРСИНГ ===== + // PARSING private static List ParseIssues(string analysisJson) { if (string.IsNullOrWhiteSpace(analysisJson)) return new();