From 119d94b0e80b12b417cc1ad35f83957cbc36a572 Mon Sep 17 00:00:00 2001 From: FrigaT Date: Sun, 28 Dec 2025 22:24:14 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=20?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BB=D1=8C=20v2=20html?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQLLinter.CLI/Program.cs | 8 +- SQLLinter/Common/BaseRuleVisitor.cs | 14 +- .../Formatters/Html/v2/HtmlFormatter_v2.css | 1886 +++++++---------- .../Formatters/Html/v2/HtmlFormatter_v2.js | 117 +- 4 files changed, 891 insertions(+), 1134 deletions(-) diff --git a/SQLLinter.CLI/Program.cs b/SQLLinter.CLI/Program.cs index 683fe38..9153c39 100644 --- a/SQLLinter.CLI/Program.cs +++ b/SQLLinter.CLI/Program.cs @@ -68,24 +68,24 @@ namespace SQLLinter.CLI } linter.Run(files); - //diagramer.Run("test.sql", reader.BaseStream); + diagramer.Run("test.sql", reader.BaseStream); } //linter.Run(@"C:\Users\frost\Desktop\DISTR-2599\test.sql"); IReportFormatter formatter = new F.v3.HtmlReportFormatter(); - var content = formatter.Format(rep.Violations, null); + var content = formatter.Format(rep.Violations, bpmn); File.WriteAllText(@"C:\Users\frost\Downloads\Telegram Desktop\test3.html", content); formatter = new F.v2.HtmlReportFormatter(); - content = formatter.Format(rep.Violations, null); + content = formatter.Format(rep.Violations, bpmn); File.WriteAllText(@"C:\Users\frost\Downloads\Telegram Desktop\test2.html", content); formatter = new F.v1.HtmlReportFormatter(); - content = formatter.Format(rep.Violations, null); + content = formatter.Format(rep.Violations, bpmn); File.WriteAllText(@"C:\Users\frost\Downloads\Telegram Desktop\test1.html", content); } } diff --git a/SQLLinter/Common/BaseRuleVisitor.cs b/SQLLinter/Common/BaseRuleVisitor.cs index 36a1ad8..3b48447 100644 --- a/SQLLinter/Common/BaseRuleVisitor.cs +++ b/SQLLinter/Common/BaseRuleVisitor.cs @@ -104,6 +104,14 @@ public abstract class BaseRuleVisitor : TSqlFragmentVisitor, IRule case SchemaObjectFunctionTableReference: return current; + // DML + case InsertStatement: + case UpdateStatement: + case DeleteStatement: + case MergeStatement: + return current; + + // ---- Новые блоки для SqlDataTypeReference ---- // Определение столбца @@ -160,10 +168,12 @@ public abstract class BaseRuleVisitor : TSqlFragmentVisitor, IRule if (node == null || node.ScriptTokenStream == null) return null; + var endLine = node.ScriptTokenStream.Where(t => t.Offset < node.StartOffset + node.FragmentLength).Max(t => t.Line) + 2; + // 1. Получаем токены для блока var tokens = node.ScriptTokenStream - .Where(t => t.Line >= node.StartLine && - t.Offset < node.StartOffset + node.FragmentLength) + .Where(t => t.Line >= node.StartLine - 2 && + t.Line <= endLine) .ToList(); if (tokens.Count == 0) diff --git a/SQLLinter/Infrastructure/Reporters/Formatters/Html/v2/HtmlFormatter_v2.css b/SQLLinter/Infrastructure/Reporters/Formatters/Html/v2/HtmlFormatter_v2.css index 1b0e93d..6a264d4 100644 --- a/SQLLinter/Infrastructure/Reporters/Formatters/Html/v2/HtmlFormatter_v2.css +++ b/SQLLinter/Infrastructure/Reporters/Formatters/Html/v2/HtmlFormatter_v2.css @@ -5,17 +5,21 @@ --color-primary-darker: #005a9e; --color-primary-light: #c7e0f4; --color-primary-lighter: #deecf9; + /* Text Colors */ --color-text-primary: #323130; --color-text-secondary: #605e5c; --color-text-tertiary: #a19f9d; --color-text-disabled: #c8c6c4; + /* Background Colors */ --color-background: #ffffff; --color-background-alt: #faf9f8; --color-background-neutral: #f3f2f1; --color-background-neutral-dark: #edebe9; + /* Border Colors */ --color-border: #edebe9; --color-border-strong: #d2d0ce; --color-border-input: #8a8886; + /* Semantic Colors */ --color-critical: #d13438; --color-critical-bg: #fde7e9; --color-warning: #ffaa44; @@ -24,12 +28,13 @@ --color-success-bg: #dff6dd; --color-info: #0078d4; --color-info-bg: #c7e0f4; + /* Mermaid Colors */ --color-mermaid-node: #f0f8ff; --color-mermaid-node-border: #0078d4; --color-mermaid-cluster: #f3f2f1; --color-mermaid-edge: #8a8886; --color-mermaid-text: #323130; - /* Shadows (облегчённые) */ + /* Shadows */ --shadow-2: 0 1px 2px rgba(0, 0, 0, 0.08); --shadow-4: 0 2px 4px rgba(0, 0, 0, 0.12); --shadow-8: 0 4px 8px rgba(0, 0, 0, 0.14); @@ -91,13 +96,9 @@ --color-border: #424242; --color-border-strong: #616161; --color-border-input: #797775; - --color-critical: #d13438; --color-critical-bg: #4c191b; - --color-warning: #ffaa44; --color-warning-bg: #4c3b1a; - --color-success: #107c10; --color-success-bg: #1c3b1c; - --color-info: #2899f5; --color-info-bg: #0f2b4d; --color-mermaid-node: #2d2d2d; --color-mermaid-node-border: #2899f5; @@ -116,6 +117,7 @@ box-sizing: border-box; margin: 0; padding: 0; + -webkit-tap-highlight-color: transparent; } html { @@ -132,8 +134,6 @@ body { line-height: 1.5; color: var(--color-text-primary); background-color: var(--color-background-neutral); - margin: 0; - padding: 0; overflow-x: hidden; } @@ -148,15 +148,134 @@ h3 { color: var(--color-text-primary); } +/* --------------------------------------------------------- + COMMON COMPONENTS +--------------------------------------------------------- */ +/* Counters and Badges */ +.severity-count, +.tab-counter, +.violation-badge { + display: inline-flex; + align-items: center; + justify-content: center; + font-weight: 600; + border-radius: var(--border-radius-medium); + border: 1px solid transparent; + text-align: center; + font-feature-settings: "tnum"; + font-variant-numeric: tabular-nums; + flex-shrink: 0; + box-sizing: border-box; +} + +.severity-count { + font-size: var(--font-size-sm); + padding: var(--spacing-xs) var(--spacing-sm); + min-width: 24px; + background-color: var(--color-background-neutral); + color: var(--color-text-secondary); + border-color: var(--color-border); +} + +.tab-counter { + min-width: 16px; + height: 16px; + padding: 0 var(--spacing-xxs); + font-size: var(--font-size-xxs); + font-weight: 400; + border-radius: var(--border-radius-small); + line-height: 1; + color: white; + border: 1px solid var(--color-border); +} + +.violation-badge { + font-size: var(--font-size-xs); + padding: var(--spacing-xxs); + border-radius: var(--border-radius-small); + min-width: 22px; + height: 22px; +} + + /* Empty counters */ + .tab-counter.empty, + .violation-badge.empty { + background-color: transparent !important; + color: transparent !important; + border-style: dashed !important; + opacity: 0.5; + cursor: default; + box-shadow: none !important; + text-shadow: none !important; + background-image: none !important; + } + +/* Color variants */ +.critical { + border-color: var(--color-critical); +} + +.warning { + border-color: var(--color-warning); +} + +.info { + border-color: var(--color-info); +} + +.critical:not(.empty) { + background-color: var(--color-critical-bg); + color: var(--color-critical); +} + +.warning:not(.empty) { + background-color: var(--color-warning-bg); + color: var(--color-warning); +} + +.info:not(.empty) { + background-color: var(--color-info-bg); + color: var(--color-info); +} + +/* Progress bars */ +.progress-bar { + height: 8px; + background-color: var(--color-background-neutral); + border-radius: var(--border-radius-small); + overflow: hidden; + margin-top: var(--spacing-md); + display: flex; +} + +.progress-fill { + height: 100%; + transition: width var(--transition-medium); + flex-shrink: 0; +} + + .progress-fill.critical { + background-color: var(--color-critical); + order: 1; + } + + .progress-fill.warning { + background-color: var(--color-warning); + order: 2; + } + + .progress-fill.info { + background-color: var(--color-info); + order: 3; + } + /* --------------------------------------------------------- FILE REPORTS & LAYOUT --------------------------------------------------------- */ - main#main-content { min-height: 100vh; } -/* Основной контейнер отчёта по файлу */ .file-report { display: none; padding-bottom: 120px; @@ -179,7 +298,6 @@ main#main-content { } } -/* Заголовок файла */ .file-title-container { padding: var(--spacing-xl) var(--spacing-xl) var(--spacing-lg); border-bottom: 1px solid var(--color-border); @@ -210,7 +328,7 @@ main#main-content { white-space: nowrap; } -/* Состояние без нарушений */ +/* No violations state */ .no-violations { display: flex; align-items: center; @@ -287,36 +405,6 @@ main#main-content { gap: var(--spacing-sm); } -.severity-count { - background-color: var(--color-background-neutral); - color: var(--color-text-secondary); - font-size: var(--font-size-sm); - font-weight: 600; - padding: var(--spacing-xs) var(--spacing-sm); - border-radius: var(--border-radius-medium); - min-width: 24px; - text-align: center; - border: 1px solid var(--color-border); -} - -.severity-section.critical .severity-count { - background-color: var(--color-critical-bg); - color: var(--color-critical); - border-color: var(--color-critical); -} - -.severity-section.warning .severity-count { - background-color: var(--color-warning-bg); - color: var(--color-warning); - border-color: var(--color-warning); -} - -.severity-section.info .severity-count { - background-color: var(--color-info-bg); - color: var(--color-info); - border-color: var(--color-info); -} - /* --------------------------------------------------------- TABLES --------------------------------------------------------- */ @@ -339,7 +427,7 @@ main#main-content { .table-container thead { background-color: var(--color-background-alt); position: sticky; - top: calc(var(--z-index-sticky) + 70px); /* Компенсируем высоту заголовка файла */ + top: calc(var(--z-index-sticky) + 70px); z-index: var(--z-index-fixed); border-bottom: 2px solid var(--color-border); } @@ -363,6 +451,7 @@ td { tbody tr { transition: background-color var(--transition-fast); + border-left: 3px solid transparent; } tbody tr:last-child td { @@ -371,24 +460,24 @@ tbody tr { /* Table cell specific styles */ td.line, -td.column { +td.column, +td.index { font-family: 'Consolas', 'Monaco', 'Courier New', monospace; font-size: var(--font-size-xs); - color: var(--color-text-secondary); text-align: center; - width: 60px; font-feature-settings: "tnum"; font-variant-numeric: tabular-nums; } +td.line, +td.column { + color: var(--color-text-secondary); + width: 60px; +} + td.index { - font-family: 'Consolas', 'Monaco', 'Courier New', monospace; - font-size: var(--font-size-xs); color: var(--color-text-tertiary); - text-align: center; width: 40px; - font-feature-settings: "tnum"; - font-variant-numeric: tabular-nums; } td.rule { @@ -402,9 +491,9 @@ td.description { line-height: 1.4; } -/* Подсветка строк в зависимости от severity */ +/* Row highlighting */ .severity-section.critical tbody tr { - border-left: 3px solid var(--color-critical); + border-left-color: var(--color-critical); } .severity-section.critical tbody tr:hover { @@ -412,7 +501,7 @@ td.description { } .severity-section.warning tbody tr { - border-left: 3px solid var(--color-warning); + border-left-color: var(--color-warning); } .severity-section.warning tbody tr:hover { @@ -420,7 +509,7 @@ td.description { } .severity-section.info tbody tr { - border-left: 3px solid var(--color-info); + border-left-color: var(--color-info); } .severity-section.info tbody tr:hover { @@ -453,16 +542,11 @@ td.description { scroll-behavior: smooth; flex-wrap: nowrap; width: 100%; - -webkit-overflow-scrolling: touch; /* Для плавного скролла на iOS */ + -webkit-overflow-scrolling: touch; user-select: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -webkit-touch-callout: none; - /*white-space: nowrap;*/ - height: auto; /* Автоматическая высота */ - min-height: 50px; /* Минимальная высота */ + min-height: 50px; } + .tabs:active { cursor: grabbing; } @@ -480,6 +564,7 @@ td.description { background: var(--color-border-strong); border-radius: var(--border-radius-medium); } + .tabs::-webkit-scrollbar-thumb:hover { background: var(--color-text-tertiary); } @@ -495,21 +580,15 @@ td.description { font-size: var(--font-size-md); font-weight: 600; cursor: pointer; - white-space: normal; /* Изменено с nowrap на normal */ - transition: color var(--transition-medium), background-color var(--transition-medium), border-color var(--transition-medium), transform var(--transition-medium); + white-space: normal; + transition: all var(--transition-medium); border-radius: var(--border-radius-medium) var(--border-radius-medium) 0 0; - min-height: auto; display: flex; - align-items: flex-start; /* Важно: flex-start вместо center */ + align-items: flex-start; z-index: 1002; - user-select: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - justify-content: space-between; flex-shrink: 0; - max-width: 300px; /* Ограничиваем максимальную ширину */ - min-width: 150px; /* Минимальная ширина для читаемости */ + max-width: 300px; + min-width: 150px; text-align: left; line-height: 1.4; } @@ -530,7 +609,7 @@ td.description { align-items: flex-start; justify-content: space-between; width: 100%; - min-width: 0; /* Важно для работы text-overflow */ + min-width: 0; gap: 8px; } @@ -542,202 +621,27 @@ td.description { justify-content: flex-start; align-items: flex-end; flex-shrink: 0; - min-height: 32px; /* Минимальная высота для трех счетчиков с отступами */ -} - -.tab-counter { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 16px; - height: 16px; - padding: 0 var(--spacing-xxs); - font-size: var(--font-size-xxs); - font-weight: 400; - border-radius: var(--border-radius-small); - text-align: center; - color: white; - line-height: 1; - border: 1px solid var(--color-border); - font-feature-settings: "tnum"; - font-variant-numeric: tabular-nums; - flex-shrink: 0; - box-sizing: border-box; -} - - .tab-counter.empty { - background-color: transparent !important; - color: transparent !important; - border-style: dashed !important; - opacity: 0.5; - cursor: default; - box-shadow: none !important; - text-shadow: none !important; - background-image: none !important; - } - - /* Цветные пунктирные границы для пустых счетчиков */ - .tab-counter.critical.empty { - border-color: var(--color-critical) !important; - } - - .tab-counter.warning.empty { - border-color: var(--color-warning) !important; - } - - .tab-counter.info.empty { - border-color: var(--color-info) !important; - } - - .tab-counter.critical:not(.empty) { - background-color: var(--color-critical-bg); - color: var(--color-critical); - border-color: var(--color-critical); - } - - .tab-counter.warning:not(.empty) { - background-color: var(--color-warning-bg); - color: var(--color-warning); - border-color: var(--color-warning); - } - - .tab-counter.info:not(.empty) { - background-color: var(--color-info-bg); - color: var(--color-info); - border-color: var(--color-info); - } - -/* При наведении на таб не менять стили пустых счетчиков */ -.tab:hover .tab-counter.empty { - background-color: transparent !important; - color: transparent !important; - opacity: 0.5; -} - -/* Активный таб тоже не должен влиять на пустые счетчики */ -.tab.active .tab-counter.empty { - background-color: transparent !important; - color: transparent !important; - opacity: 0.5; -} - -/* Сохраняем прозрачный текст для пустых счетчиков */ -.tab-counter.empty::after { - content: ''; - display: block; - width: 0; - height: 0; + min-height: 32px; } .tab-text { display: block; - overflow-wrap: break-word; /* Используем вместо word-break */ - word-wrap: break-word; /* Для старых браузеров */ - hyphens: auto; /* Автоматическое добавление переносов */ + overflow-wrap: break-word; + word-wrap: break-word; + hyphens: auto; text-align: left; line-height: 1.3; white-space: normal; flex-grow: 1; min-width: 0; - word-break: break-word; /* Разрешаем разрыв длинных слов */ - max-height: 2.8em; /* Примерно 2 строки текста */ + word-break: break-word; + max-height: 2.8em; display: -webkit-box; - -webkit-line-clamp: 2; /* Ограничиваем 2 строками */ + -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } -/* Адаптивные стили для мобильных */ -@media (max-width: 768px) { - .tab { - padding: 8px 10px; - font-size: 13px; - min-width: 120px; - max-width: 200px; - } - - .tab-text { - font-size: 13px; - line-height: 1.2; - } - - .tab-counters { - margin-left: 8px; - gap: 2px; - min-height: 55px; /* Немного меньше для мобильных */ - } - - .tab-counter { - min-width: 18px; - height: 18px; - font-size: 10px; - padding: 0 4px; - } - .tab-counter.empty { - opacity: 0.2; - } -} - -@media (max-width: 480px) { - .tab { - padding: 6px 8px; - font-size: 12px; - min-width: 100px; - max-width: 160px; - } - - .tab-text { - font-size: 12px; - max-height: 2.4em; - } - - .tab-counters { - margin-left: 6px; - gap: 1px; - min-height: 50px; - } - - .tab-counter { - min-width: 16px; - height: 16px; - font-size: 9px; - padding: 0 3px; - } - .tab-counter.empty { - border-width: 1px !important; /* Уменьшаем толщину границы */ - } -} - -/* Усилить видимость пунктирных границ на темных темах */ -@media (prefers-color-scheme: dark) { - .tab-counter.empty { - opacity: 0.7; - } - - .tab-counter.critical.empty { - border-color: var(--color-critical) !important; - } - - .tab-counter.warning.empty { - border-color: var(--color-warning) !important; - } - - .tab-counter.info.empty { - border-color: var(--color-info) !important; - } -} - -/* Усилить видимость пунктирных границ при наведении на таб */ -.tab:hover .tab-counter.empty { - opacity: 0.8; - border-style: dashed !important; -} - -/* Активный таб - усилить видимость */ -.tab.active .tab-counter.empty { - opacity: 0.8; -} - /* --------------------------------------------------------- DIAGRAM VIEWER --------------------------------------------------------- */ @@ -757,7 +661,6 @@ td.description { display: block; } -/* Для диаграммы убираем заголовок */ #mermaid .file-title-container { display: none; } @@ -795,7 +698,7 @@ td.description { font-size: var(--font-size-md); background-color: var(--color-background); color: var(--color-text-primary); - transition: border-color var(--transition-medium), box-shadow var(--transition-medium), background-color var(--transition-medium); + transition: all var(--transition-medium); } .toolbar-search input:focus { @@ -820,7 +723,7 @@ td.description { padding: var(--spacing-xs); font-size: var(--font-size-sm); opacity: 0.7; - transition: opacity var(--transition-fast), background-color var(--transition-fast); + transition: all var(--transition-fast); border-radius: 50%; width: 24px; height: 24px; @@ -851,7 +754,7 @@ td.description { font-size: var(--font-size-md); font-weight: 600; cursor: pointer; - transition: background-color var(--transition-medium), box-shadow var(--transition-medium), transform var(--transition-medium); + transition: all var(--transition-medium); display: inline-flex; align-items: center; gap: var(--spacing-sm); @@ -885,7 +788,7 @@ td.description { top: 52px; left: 0; width: 100vw; - height: calc(100vh - 112px); /* 52px тулбар + ~60px табы */ + height: calc(100vh - 112px); overflow: hidden; background-color: var(--color-background); } @@ -908,15 +811,15 @@ td.description { cursor: grab; overflow: visible; display: block; - transition: transform 0.3s ease-out; /* Плавный переход при изменении масштаба */ + transition: transform 0.3s ease-out; + font-family: var(--font-family); } #diagramSvgContainer svg:active { cursor: grabbing; } - /* Индикатор загрузки для диаграммы */ - #diagramSvgContainer::before { + #diagramSvgContainer.loading::before { content: ''; position: absolute; top: 50%; @@ -929,12 +832,8 @@ td.description { border-radius: 50%; animation: spin 1s linear infinite; z-index: 100; - opacity: 0; - transition: opacity 0.3s; - } - - #diagramSvgContainer.loading::before { opacity: 1; + transition: opacity 0.3s; } @keyframes spin { @@ -952,20 +851,12 @@ td.description { height: 140px; border: 1px solid var(--color-border-strong); background-color: var(--color-background); - /* убран backdrop-filter для производительности */ border-radius: var(--border-radius-large); box-shadow: var(--shadow-8); z-index: var(--z-index-popover); overflow: hidden; } -@media (prefers-color-scheme: dark) { - #minimap { - background-color: var(--color-background-alt); - border-color: var(--color-border-input); - } -} - .minimap-viewport { position: absolute; border: 2px solid var(--color-primary); @@ -975,33 +866,6 @@ td.description { box-sizing: border-box; } -@media (prefers-color-scheme: dark) { - .minimap-viewport { - border-color: var(--color-primary); - background-color: rgba(40, 153, 245, 0.2); - } -} - -/* Упрощаем отображение элементов в миникарте */ -#minimap svg { - width: 100%; - height: 100%; -} - -#minimap .node rect { - stroke-width: 1px !important; - rx: 2px !important; - ry: 2px !important; -} - -#minimap .edgePath path { - stroke-width: 1px !important; -} - -#minimap text { - font-size: 4px !important; -} - /* Search Results Info */ .search-results-info { position: absolute; @@ -1022,17 +886,6 @@ td.description { /* --------------------------------------------------------- MERMAID DIAGRAM STYLING --------------------------------------------------------- */ - -#diagramSvgContainer svg { - font-family: var(--font-family); - font-size: var(--font-size-md); -} - - #diagramSvgContainer svg .label { - font-family: var(--font-family); - } - -/* Nodes */ svg .node rect { fill: var(--color-mermaid-node); stroke: var(--color-mermaid-node-border); @@ -1056,7 +909,6 @@ svg .node text { font-weight: 500; } -/* Clusters */ svg .cluster rect { fill: rgba(243, 242, 241, 0.9); stroke: var(--color-text-tertiary); @@ -1072,7 +924,6 @@ svg .cluster text { font-weight: 600; } -/* Arrow and Line Styling */ svg marker path { fill: var(--color-mermaid-edge); } @@ -1100,65 +951,11 @@ svg .edgeLabel rect { ry: 4px; } -/* Text Labels */ svg .label text { font-size: var(--font-size-sm); font-weight: 400; } -/* Dark Theme Mermaid Styles */ -@media (prefers-color-scheme: dark) { - svg .node rect { - fill: var(--color-mermaid-node); - stroke: var(--color-mermaid-node-border); - } - - svg .node circle, - svg .node ellipse, - svg .node polygon { - fill: var(--color-background-neutral); - stroke: var(--color-mermaid-node-border); - } - - svg .node text { - fill: var(--color-mermaid-text); - font-weight: 400; - } - - svg .cluster rect { - fill: rgba(32, 31, 30, 0.9); - stroke: var(--color-text-tertiary); - stroke-dasharray: 5, 5; - } - - svg .cluster text { - fill: var(--color-text-tertiary); - font-weight: 500; - } - - svg .edgePath path { - stroke: var(--color-mermaid-edge); - } - - svg .edgeLabel text { - fill: var(--color-mermaid-text); - } - - svg .edgeLabel rect { - fill: var(--color-background-alt); - stroke: var(--color-border-strong); - } - - svg .label text { - fill: var(--color-text-tertiary); - } - - #diagramSvgContainer svg, - #diagramContainer { - background-color: var(--color-background); - } -} - /* Node Type Specific Styles */ svg .node.condition rect { fill: var(--color-warning-bg); @@ -1186,20 +983,7 @@ svg .node.exec rect { stroke: #4b4bd8; } -@media (prefers-color-scheme: dark) { - svg .node.start rect, - svg .node.end rect { - fill: var(--color-primary-light); - stroke: var(--color-primary); - } - - svg .node.exec rect { - fill: #2d2a4d; - stroke: #4b4bd8; - } -} - -/* Node Highlighting (упрощённо, без тяжёлых фильтров) */ +/* Node Highlighting */ .node-search-match rect { stroke: #ffc107 !important; stroke-width: 3px !important; @@ -1215,7 +999,7 @@ svg .node.exec rect { stroke-width: 3px !important; } -/* Усиление видимости линий */ +/* Edge visibility */ svg .edgePath, svg .edgePath path, svg .flowchart-link { @@ -1231,6 +1015,75 @@ svg .flowchart-link { stroke-width: 3px !important; } +/* Dark Theme Mermaid Styles */ +@media (prefers-color-scheme: dark) { + svg .node rect { + fill: var(--color-mermaid-node); + stroke: var(--color-mermaid-node-border); + } + + svg .node circle, + svg .node ellipse, + svg .node polygon { + fill: var(--color-background-neutral); + stroke: var(--color-mermaid-node-border); + } + + svg .node text { + fill: var(--color-mermaid-text); + font-weight: 400; + } + + svg .cluster rect { + fill: rgba(32, 31, 30, 0.9); + stroke: var(--color-text-tertiary); + } + + svg .cluster text { + fill: var(--color-text-tertiary); + font-weight: 500; + } + + svg .edgeLabel rect { + fill: var(--color-background-alt); + stroke: var(--color-border-strong); + } + + svg .label text { + fill: var(--color-text-tertiary); + } + + #diagramSvgContainer svg, + #diagramContainer { + background-color: var(--color-background); + } + + svg .node.start rect, + svg .node.end rect { + fill: var(--color-primary-light); + stroke: var(--color-primary); + } + + svg .node.exec rect { + fill: #2d2a4d; + stroke: #4b4bd8; + } + + #minimap { + background-color: var(--color-background-alt); + border-color: var(--color-border-input); + } + + .minimap-viewport { + border-color: var(--color-primary); + background-color: rgba(40, 153, 245, 0.2); + } + + .tab-counter.empty { + opacity: 0.7; + } +} + /* --------------------------------------------------------- TOAST NOTIFICATIONS --------------------------------------------------------- */ @@ -1279,17 +1132,476 @@ svg .flowchart-link { background: var(--color-background-neutral); } -/* For Firefox */ * { scrollbar-width: thin; scrollbar-color: var(--color-border-strong) var(--color-background-neutral); } +/* --------------------------------------------------------- + SUMMARY PAGE STYLES +--------------------------------------------------------- */ +.summary-section { + margin: var(--spacing-xl) var(--spacing-xxxl); + padding: var(--spacing-xl); + background-color: var(--color-background); + border-radius: var(--border-radius-large); + box-shadow: var(--shadow-4); + transition: transform var(--transition-medium), box-shadow var(--transition-medium); +} + + .summary-section:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-8); + } + +.summary-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--spacing-xl); + padding-bottom: var(--spacing-md); + border-bottom: 1px solid var(--color-border); +} + +.summary-title { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.summary-stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--spacing-lg); + margin-bottom: var(--spacing-xl); +} + +.stat-card { + padding: var(--spacing-lg); + border-radius: var(--border-radius-medium); + background-color: var(--color-background-alt); + border: 1px solid var(--color-border); + transition: all var(--transition-medium); + border-top: 4px solid transparent; +} + + .stat-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-8); + } + + .stat-card.critical { + border-top-color: var(--color-critical); + } + + .stat-card.warning { + border-top-color: var(--color-warning); + } + + .stat-card.info { + border-top-color: var(--color-info); + } + + .stat-card.success { + border-top-color: var(--color-success); + } + +.stat-value { + font-size: var(--font-size-xxxl); + font-weight: 700; + margin-bottom: var(--spacing-xs); + line-height: 1; +} + +.stat-label { + font-size: var(--font-size-sm); + color: var(--color-text-secondary); + margin-bottom: var(--spacing-xs); +} + +.stat-change { + font-size: var(--font-size-xs); + display: flex; + align-items: center; + gap: 2px; +} + + .stat-change.positive { + color: var(--color-success); + } + + .stat-change.negative { + color: var(--color-critical); + } + +.files-overview { + margin-top: var(--spacing-xxl); +} + +.files-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: var(--spacing-lg); +} + +.file-card { + padding: var(--spacing-lg); + border-radius: var(--border-radius-medium); + background-color: var(--color-background-alt); + border: 1px solid var(--color-border); + transition: all var(--transition-medium); + display: flex; + flex-direction: column; +} + + .file-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-8); + } + +.file-card-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin-bottom: var(--spacing-md); + gap: var(--spacing-sm); +} + +.file-name-small { + font-weight: 600; + color: var(--color-text-primary); + overflow: visible; + text-overflow: clip; + white-space: normal; + word-wrap: break-word; + word-break: break-word; + flex: 1; + line-height: 1.4; +} + +.file-violations { + display: flex; + gap: var(--spacing-xs); + justify-content: flex-end; + flex-shrink: 0; + align-items: flex-start; +} + +/* --------------------------------------------------------- + CODE BLOCK STYLES +--------------------------------------------------------- */ +.code-block { + position: relative; + background: var(--color-background); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-medium); + margin: var(--spacing-md); + overflow: hidden; + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: var(--font-size-sm); + line-height: 1.5; +} + +.code-container { + overflow-x: auto; + padding: var(--spacing-sm) 0; +} + +.code-line { + display: flex; + min-height: 24px; + padding: 0 var(--spacing-sm); + transition: background-color var(--transition-fast); + border-left: 3px solid transparent; +} + + .code-line:hover { + background: var(--color-background-neutral); + } + +.line-number { + display: inline-block; + min-width: 40px; + padding-right: var(--spacing-md); + text-align: right; + color: var(--color-text-tertiary); + user-select: none; + font-size: var(--font-size-xs); + border-right: 1px solid var(--color-border); + margin-right: var(--spacing-md); +} + +/* Code highlighting */ +.code-line .critical, +.code-line .warning, +.code-line .info, +.code-line .success { + position: relative; + padding: 0 var(--spacing-xs); + border-radius: var(--border-radius-small); + border: 1px solid; +} + +.code-line .critical { + color: var(--color-critical); + background: var(--color-critical-bg); + border-color: var(--color-critical); +} + +.code-line .warning { + color: var(--color-warning); + background: var(--color-warning-bg); + border-color: var(--color-warning); +} + +.code-line .info { + color: var(--color-info); + background: var(--color-info-bg); + border-color: var(--color-info); +} + +.code-line .success { + color: var(--color-success); + background: var(--color-success-bg); + border-color: var(--color-success); +} + +.code-line.critical-line { + background: linear-gradient(90deg, rgba(209, 52, 56, 0.05) 0%, rgba(209, 52, 56, 0.1) 100%); + border-left-color: var(--color-critical); +} + +.code-line.warning-line { + background: linear-gradient(90deg, rgba(255, 170, 68, 0.05) 0%, rgba(255, 170, 68, 0.1) 100%); + border-left-color: var(--color-warning); +} + +.code-line.info-line { + background: linear-gradient(90deg, rgba(0, 120, 212, 0.05) 0%, rgba(0, 120, 212, 0.1) 100%); + border-left-color: var(--color-info); +} + +/* --------------------------------------------------------- + VIOLATION DETAILS +--------------------------------------------------------- */ +.violation-main-row { + transition: background-color 0.2s ease; +} + + .violation-main-row:hover { + background-color: rgba(0, 0, 0, 0.02); + } + +.description-content { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: var(--spacing-md); +} + +.detail-toggle-btn { + display: inline-flex; + align-items: center; + gap: var(--spacing-xs); + padding: var(--spacing-xs) var(--spacing-sm); + background: var(--color-background-neutral); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-small); + color: var(--color-text-secondary); + font-size: var(--font-size-xs); + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; + flex-shrink: 0; +} + + .detail-toggle-btn:hover { + background: var(--color-background-neutral-dark); + border-color: var(--color-border-strong); + color: var(--color-text-primary); + } + + .detail-toggle-btn.active { + background: var(--color-primary-light); + border-color: var(--color-primary); + color: var(--color-primary); + } + +.detail-toggle-icon { + transition: transform 0.3s ease; +} + +.detail-toggle-btn.active .detail-toggle-icon { + transform: rotate(180deg); +} + +.violation-detail-row { + background: var(--color-background-alt); + border-bottom: 2px solid var(--color-border); +} + +.detail-cell { + padding: 0 !important; +} + +.detail-container { + padding: var(--spacing-lg); + background: var(--color-background); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-medium); + margin: var(--spacing-sm) var(--spacing-md); + box-shadow: var(--shadow-4); +} + +.detail-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--spacing-lg); + padding-bottom: var(--spacing-sm); + border-bottom: 1px solid var(--color-border); +} + + .detail-header h4 { + margin: 0; + color: var(--color-text-primary); + font-size: var(--font-size-md); + font-weight: 600; + } + +.detail-close-btn { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + background: var(--color-background-neutral); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-small); + color: var(--color-text-secondary); + cursor: pointer; + transition: all 0.2s ease; +} + + .detail-close-btn:hover { + background: var(--color-background-neutral-dark); + border-color: var(--color-border-strong); + color: var(--color-text-primary); + } + +.detail-content { + margin-bottom: var(--spacing-lg); + line-height: 1.6; + color: var(--color-text-primary); +} + + .detail-content :first-child { + margin-top: 0; + } + + .detail-content :last-child { + margin-bottom: 0; + } + + .detail-content p { + margin: var(--spacing-sm) 0; + } + + .detail-content ul, + .detail-content ol { + margin: var(--spacing-sm) 0; + padding-left: var(--spacing-lg); + } + + .detail-content li { + margin: var(--spacing-xs) 0; + } + + .detail-content code { + background: var(--color-background-neutral); + padding: 2px 6px; + border-radius: var(--border-radius-small); + font-family: 'Consolas', 'Monaco', monospace; + font-size: 0.9em; + } + + .detail-content pre { + background: var(--color-background-neutral); + padding: var(--spacing-md); + border-radius: var(--border-radius-medium); + overflow-x: auto; + margin: var(--spacing-md) 0; + } + + .detail-content pre code { + background: transparent; + padding: 0; + } + +.detail-footer { + border-top: 1px solid var(--color-border); + padding-top: var(--spacing-sm); +} + +.detail-metadata { + display: flex; + justify-content: space-between; + align-items: center; + font-size: var(--font-size-xs); + color: var(--color-text-secondary); +} + +.detail-severity { + padding: 2px 8px; + border-radius: var(--border-radius-small); + font-weight: 500; + text-transform: uppercase; + font-size: 10px; + letter-spacing: 0.5px; +} + + .detail-severity.critical { + background: var(--color-critical-bg); + color: var(--color-critical); + } + + .detail-severity.warning { + background: var(--color-warning-bg); + color: var(--color-warning); + } + + .detail-severity.info { + background: var(--color-info-bg); + color: var(--color-info); + } + +.detail-location { + font-family: 'Consolas', 'Monaco', monospace; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +.violation-detail-row[style*="table-row"] .detail-container { + animation: slideDown 0.3s ease; +} + /* --------------------------------------------------------- RESPONSIVE ADJUSTMENTS --------------------------------------------------------- */ @media (max-width: 768px) { - .severity-section { + .severity-section, + .summary-section { margin: var(--spacing-lg); padding: var(--spacing-lg); } @@ -1342,6 +1654,71 @@ svg .flowchart-link { #diagramContainer { height: calc(100vh - 102px); } + + .summary-stats-grid { + grid-template-columns: repeat(2, 1fr); + } + + .files-grid { + grid-template-columns: 1fr; + } + + .detail-container { + margin: var(--spacing-xs); + padding: var(--spacing-md); + } + + .detail-header { + flex-direction: column; + align-items: flex-start; + gap: var(--spacing-sm); + } + + .detail-metadata { + flex-direction: column; + align-items: flex-start; + gap: var(--spacing-xs); + } + + .description-content { + flex-direction: column; + align-items: flex-start; + gap: var(--spacing-sm); + } + + .detail-toggle-btn { + align-self: flex-start; + } + + /* Tabs responsive */ + .tab { + padding: 8px 10px; + font-size: 13px; + min-width: 120px; + max-width: 200px; + } + + .tab-text { + font-size: 13px; + line-height: 1.2; + } + + .tab-counters { + margin-left: 8px; + gap: 2px; + min-height: 55px; + } + + .tab-counter { + min-width: 18px; + height: 18px; + font-size: 10px; + padding: 0 4px; + } + + .tab-counter.empty { + opacity: 0.2; + } } @media (max-width: 480px) { @@ -1378,6 +1755,58 @@ svg .flowchart-link { padding: var(--spacing-sm) var(--spacing-md); font-size: var(--font-size-sm); } + + .summary-stats-grid { + grid-template-columns: 1fr; + } + + .stat-value { + font-size: var(--font-size-xxl); + } + + /* Tabs mobile */ + .tab { + padding: 6px 8px; + font-size: 12px; + min-width: 100px; + max-width: 160px; + } + + .tab-text { + font-size: 12px; + max-height: 2.4em; + } + + .tab-counters { + margin-left: 6px; + gap: 1px; + min-height: 50px; + } + + .tab-counter { + min-width: 16px; + height: 16px; + font-size: 9px; + padding: 0 3px; + } + + .tab-counter.empty { + border-width: 1px !important; + } + + .code-block { + font-size: var(--font-size-xs); + } + + .line-number { + min-width: 30px; + padding-right: var(--spacing-sm); + margin-right: var(--spacing-sm); + } + + .code-line { + padding: 0 var(--spacing-xs); + } } /* --------------------------------------------------------- @@ -1439,684 +1868,3 @@ svg .flowchart-link { background-color: var(--color-primary); color: #ffffff; } - -/* Убираем blue highlight на mobile tap */ -* { - -webkit-tap-highlight-color: transparent; -} - - -/* Summary page styles */ -.summary-section { - margin: var(--spacing-xl) var(--spacing-xxxl); - padding: var(--spacing-xl); - background-color: var(--color-background); - border-radius: var(--border-radius-large); - box-shadow: var(--shadow-4); - transition: transform var(--transition-medium), box-shadow var(--transition-medium); -} - - .summary-section:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-8); - } - -.summary-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: var(--spacing-xl); - padding-bottom: var(--spacing-md); - border-bottom: 1px solid var(--color-border); -} - -.summary-title { - display: flex; - align-items: center; - gap: var(--spacing-sm); -} - -.summary-stats-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: var(--spacing-lg); - margin-bottom: var(--spacing-xl); -} - -.stat-card { - padding: var(--spacing-lg); - border-radius: var(--border-radius-medium); - background-color: var(--color-background-alt); - border: 1px solid var(--color-border); - transition: all var(--transition-medium); -} - - .stat-card:hover { - transform: translateY(-4px); - box-shadow: var(--shadow-8); - } - - .stat-card.critical { - border-top: 4px solid var(--color-critical); - } - - .stat-card.warning { - border-top: 4px solid var(--color-warning); - } - - .stat-card.info { - border-top: 4px solid var(--color-info); - } - - .stat-card.success { - border-top: 4px solid var(--color-success); - } - -.stat-value { - font-size: var(--font-size-xxxl); - font-weight: 700; - margin-bottom: var(--spacing-xs); - line-height: 1; -} - -.stat-label { - font-size: var(--font-size-sm); - color: var(--color-text-secondary); - margin-bottom: var(--spacing-xs); -} - -.stat-change { - font-size: var(--font-size-xs); - display: flex; - align-items: center; - gap: 2px; -} - - .stat-change.positive { - color: var(--color-success); - } - - .stat-change.negative { - color: var(--color-critical); - } - -.files-overview { - margin-top: var(--spacing-xxl); -} - -.files-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: var(--spacing-lg); -} - -.file-card { - padding: var(--spacing-lg); - border-radius: var(--border-radius-medium); - background-color: var(--color-background-alt); - border: 1px solid var(--color-border); - transition: all var(--transition-medium); - display: flex; - flex-direction: column; -} - - .file-card:hover { - transform: translateY(-4px); - box-shadow: var(--shadow-8); - } - -.file-card-header { - display: flex; - align-items: flex-start; /* Change from center to flex-start */ - justify-content: space-between; - margin-bottom: var(--spacing-md); - gap: var(--spacing-sm); /* Add gap between name and badges */ -} - - -.file-name-small { - font-weight: 600; - color: var(--color-text-primary); - /* Remove truncation properties */ - overflow: visible; - text-overflow: clip; - white-space: normal; /* Change from nowrap to normal */ - word-wrap: break-word; - word-break: break-word; - flex: 1; - line-height: 1.4; - max-height: none; /* Remove max-height if exists */ - display: block; /* Change from -webkit-box */ - -webkit-line-clamp: unset; /* Remove line clamp */ - -webkit-box-orient: unset; -} - -.file-violations { - display: flex; - gap: var(--spacing-xs); - justify-content: flex-end; - flex-shrink: 0; - align-items: flex-start; /* Align badges to top */ -} - -.violation-badge { - font-size: var(--font-size-xs); - padding: var(--spacing-xxs) var(--spacing-xxs); - border-radius: var(--border-radius-small); - font-weight: 600; - min-width: 22px; - height: 22px; - text-align: center; -} - - .violation-badge.critical { - background-color: var(--color-critical-bg); - color: var(--color-critical); - border: 1px solid var(--color-critical); - } - - .violation-badge.warning { - background-color: var(--color-warning-bg); - color: var(--color-warning); - border: 1px solid var(--color-warning); - } - - .violation-badge.info { - background-color: var(--color-info-bg); - color: var(--color-info); - border: 1px solid var(--color-info); - } - -.progress-bar { - height: 8px; - background-color: var(--color-background-neutral); - border-radius: var(--border-radius-small); - overflow: hidden; - margin-top: var(--spacing-md); - display: flex; /* Изменено с block на flex */ -} - -.progress-fill { - height: 100%; - transition: width var(--transition-medium); - flex-shrink: 0; /* Предотвращает сжатие */ -} - - .progress-fill.critical { - background-color: var(--color-critical); - order: 1; - } - - .progress-fill.warning { - background-color: var(--color-warning); - order: 2; - } - - .progress-fill.info { - background-color: var(--color-info); - order: 3; - } - -@media (max-width: 768px) { - .summary-section { - margin: var(--spacing-lg); - padding: var(--spacing-lg); - } - - .summary-stats-grid { - grid-template-columns: repeat(2, 1fr); - } - - .files-grid { - grid-template-columns: 1fr; - } -} - -@media (max-width: 480px) { - .summary-stats-grid { - grid-template-columns: 1fr; - } - - .stat-value { - font-size: var(--font-size-xxl); - } -} - -/* Стили для детальных описаний нарушений */ -.violation-main-row { - transition: background-color 0.2s ease; -} - - .violation-main-row:hover { - background-color: rgba(0, 0, 0, 0.02); - } - -.description-content { - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: var(--spacing-m); -} - -.detail-toggle-btn { - display: inline-flex; - align-items: center; - gap: var(--spacing-xs); - padding: var(--spacing-xs) var(--spacing-s); - background: var(--surface-neutral); - border: 1px solid var(--border-default); - border-radius: var(--border-radius-small); - color: var(--text-secondary); - font-size: var(--font-size-xs); - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - white-space: nowrap; - flex-shrink: 0; -} - - .detail-toggle-btn:hover { - background: var(--surface-neutral-hover); - border-color: var(--border-strong); - color: var(--text-primary); - } - - .detail-toggle-btn.active { - background: var(--primary-light); - border-color: var(--primary-color); - color: var(--primary-color); - } - -.detail-toggle-icon { - transition: transform 0.3s ease; -} - -.detail-toggle-btn.active .detail-toggle-icon { - transform: rotate(180deg); -} - -.violation-detail-row { - background: var(--surface-subtle); - border-bottom: 2px solid var(--border-default); -} - -.detail-cell { - padding: 0 !important; -} - -.detail-container { - padding: var(--spacing-l); - background: var(--surface-default); - border: 1px solid var(--border-default); - border-radius: var(--border-radius-medium); - margin: var(--spacing-s) var(--spacing-m); - box-shadow: var(--shadow-4); -} - -.detail-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: var(--spacing-l); - padding-bottom: var(--spacing-s); - border-bottom: 1px solid var(--border-default); -} - - .detail-header h4 { - margin: 0; - color: var(--text-primary); - font-size: var(--font-size-m); - font-weight: 600; - } - -.detail-close-btn { - display: flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - background: var(--surface-neutral); - border: 1px solid var(--border-default); - border-radius: var(--border-radius-small); - color: var(--text-secondary); - cursor: pointer; - transition: all 0.2s ease; -} - - .detail-close-btn:hover { - background: var(--surface-neutral-hover); - border-color: var(--border-strong); - color: var(--text-primary); - } - -.detail-content { - margin-bottom: var(--spacing-l); - line-height: 1.6; - color: var(--text-primary); -} - - .detail-content :first-child { - margin-top: 0; - } - - .detail-content :last-child { - margin-bottom: 0; - } - - .detail-content p { - margin: var(--spacing-s) 0; - } - - .detail-content ul, - .detail-content ol { - margin: var(--spacing-s) 0; - padding-left: var(--spacing-l); - } - - .detail-content li { - margin: var(--spacing-xs) 0; - } - - .detail-content code { - background: var(--surface-neutral); - padding: 2px 6px; - border-radius: var(--border-radius-small); - font-family: 'Consolas', 'Monaco', monospace; - font-size: 0.9em; - } - - .detail-content pre { - background: var(--surface-neutral); - padding: var(--spacing-m); - border-radius: var(--border-radius-medium); - overflow-x: auto; - margin: var(--spacing-m) 0; - } - - .detail-content pre code { - background: transparent; - padding: 0; - } - -.detail-footer { - border-top: 1px solid var(--border-subtle); - padding-top: var(--spacing-s); -} - -.detail-metadata { - display: flex; - justify-content: space-between; - align-items: center; - font-size: var(--font-size-xs); - color: var(--text-secondary); -} - -.detail-severity { - padding: 2px 8px; - border-radius: var(--border-radius-small); - font-weight: 500; - text-transform: uppercase; - font-size: 10px; - letter-spacing: 0.5px; -} - - .detail-severity.critical { - background: var(--critical-bg); - color: var(--critical-fg); - } - - .detail-severity.warning { - background: var(--warning-bg); - color: var(--warning-fg); - } - - .detail-severity.info { - background: var(--info-bg); - color: var(--info-fg); - } - -.detail-location { - font-family: 'Consolas', 'Monaco', monospace; -} - -/* Анимация появления деталей */ -@keyframes slideDown { - from { - opacity: 0; - transform: translateY(-10px); - } - - to { - opacity: 1; - transform: translateY(0); - } -} - -.violation-detail-row[style*="table-row"] .detail-container { - animation: slideDown 0.3s ease; -} - -/* Адаптивность для мобильных устройств */ -@media (max-width: 768px) { - .detail-container { - margin: var(--spacing-xs); - padding: var(--spacing-m); - } - - .detail-header { - flex-direction: column; - align-items: flex-start; - gap: var(--spacing-s); - } - - .detail-metadata { - flex-direction: column; - align-items: flex-start; - gap: var(--spacing-xs); - } - - .description-content { - flex-direction: column; - align-items: flex-start; - gap: var(--spacing-s); - } - - .detail-toggle-btn { - align-self: flex-start; - } -} - -/* Контейнер для блока кода */ -.code-block { - position: relative; - background: var(--color-background); - border: 1px solid var(--color-border); - border-radius: var(--border-radius-medium); - margin: var(--spacing-md); - overflow: hidden; - font-family: 'Consolas', 'Monaco', 'Courier New', monospace; - font-size: var(--font-size-sm); - line-height: 1.5; -} - -/* Контейнер для строк кода */ -.code-container { - overflow-x: auto; - padding: var(--spacing-sm) 0; -} - -/* Строка кода */ -.code-line { - display: flex; - min-height: 24px; - padding: 0 var(--spacing-sm); - transition: background-color var(--transition-fast); - border-left: 3px solid transparent; -} - - .code-line:hover { - background: var(--color-background-neutral); - } - -/* Номер строки */ -.line-number { - display: inline-block; - min-width: 40px; - padding-right: var(--spacing-md); - text-align: right; - color: var(--color-text-tertiary); - user-select: none; - font-size: var(--font-size-xs); - border-right: 1px solid var(--color-border); - margin-right: var(--spacing-md); -} - -/* Подсветка ошибок */ -.code-line .critical { - position: relative; - color: var(--color-critical); - background: var(--color-critical-bg); - padding: 0 var(--spacing-xs); - border-radius: var(--border-radius-small); - border: 1px solid var(--color-critical); -} - - .code-line .critical::after { - content: ''; - position: absolute; - bottom: -2px; - left: 0; - right: 0; - height: 1px; - background: linear-gradient(90deg, transparent 0%, var(--color-critical) 50%, transparent 100%); - } - - .code-line .critical:hover { - background: rgba(209, 52, 56, 0.2); - } - -/* Подсветка предупреждений */ -.code-line .warning { - position: relative; - color: var(--color-warning); - background: var(--color-warning-bg); - padding: 0 var(--spacing-xs); - border-radius: var(--border-radius-small); - border: 1px solid var(--color-warning); -} - - .code-line .warning:hover { - background: rgba(255, 170, 68, 0.2); - } - -/* Подсветка информационных сообщений */ -.code-line .info { - position: relative; - color: var(--color-info); - background: var(--color-info-bg); - padding: 0 var(--spacing-xs); - border-radius: var(--border-radius-small); - border: 1px solid var(--color-info); -} - - .code-line .info:hover { - background: rgba(0, 120, 212, 0.2); - } - -/* Подсветка успешных проверок */ -.code-line .success { - position: relative; - color: var(--color-success); - background: var(--color-success-bg); - padding: 0 var(--spacing-xs); - border-radius: var(--border-radius-small); - border: 1px solid var(--color-success); -} - - .code-line .success:hover { - background: rgba(16, 124, 16, 0.2); - } - -/* Строка с ошибкой */ -.code-line.critical-line { - background: linear-gradient(90deg, rgba(209, 52, 56, 0.05) 0%, rgba(209, 52, 56, 0.1) 100%); - border-left: 3px solid var(--color-critical); -} - -/* Строка с предупреждением */ -.code-line.warning-line { - background: linear-gradient(90deg, rgba(255, 170, 68, 0.05) 0%, rgba(255, 170, 68, 0.1) 100%); - border-left: 3px solid var(--color-warning); -} - -/* Строка с информацией */ -.code-line.info-line { - background: linear-gradient(90deg, rgba(0, 120, 212, 0.05) 0%, rgba(0, 120, 212, 0.1) 100%); - border-left: 3px solid var(--color-info); -} - -/* Темная тема */ -@media (prefers-color-scheme: dark) { - .code-block { - background: var(--color-background-alt); - border-color: var(--color-border-strong); - } - - .code-line:hover { - background: var(--color-background-neutral-dark); - } - - .line-number { - color: var(--color-text-tertiary); - border-color: var(--color-border-strong); - } - - .code-line .critical { - background: var(--color-critical-bg); - border-color: var(--color-critical); - } - - .code-line .warning { - background: var(--color-warning-bg); - border-color: var(--color-warning); - } - - .code-line .info { - background: var(--color-info-bg); - border-color: var(--color-info); - } - - .code-line .success { - background: var(--color-success-bg); - border-color: var(--color-success); - } - - .code-line.critical-line { - background: linear-gradient(90deg, rgba(209, 52, 56, 0.1) 0%, rgba(209, 52, 56, 0.15) 100%); - } - - .code-line.warning-line { - background: linear-gradient(90deg, rgba(255, 170, 68, 0.1) 0%, rgba(255, 170, 68, 0.15) 100%); - } - - .code-line.info-line { - background: linear-gradient(90deg, rgba(0, 120, 212, 0.1) 0%, rgba(0, 120, 212, 0.15) 100%); - } -} - -/* Адаптивность */ -@media (max-width: 768px) { - .code-block { - font-size: var(--font-size-xs); - } - - .line-number { - min-width: 30px; - padding-right: var(--spacing-sm); - margin-right: var(--spacing-sm); - } - - .code-line { - padding: 0 var(--spacing-xs); - } -} \ No newline at end of file diff --git a/SQLLinter/Infrastructure/Reporters/Formatters/Html/v2/HtmlFormatter_v2.js b/SQLLinter/Infrastructure/Reporters/Formatters/Html/v2/HtmlFormatter_v2.js index 9a50d49..2852f71 100644 --- a/SQLLinter/Infrastructure/Reporters/Formatters/Html/v2/HtmlFormatter_v2.js +++ b/SQLLinter/Infrastructure/Reporters/Formatters/Html/v2/HtmlFormatter_v2.js @@ -23,37 +23,37 @@ class ReportRenderer { this.setupTabsDragScroll(); this.renderAllReports(); this.setupEventListeners(); - this.activateTab(0); + this.activateTab('summary_report'); } setupEventListeners() { - // Делегирование для кнопок деталей + // Делегирование только для кнопок деталей (toggle) document.addEventListener('click', (e) => { const toggleBtn = e.target.closest('.detail-toggle-btn'); - const closeBtn = e.target.closest('.detail-close-btn'); - if (toggleBtn) { e.preventDefault(); this.toggleDetail(toggleBtn.dataset.detailId, toggleBtn); - } else if (closeBtn) { - e.preventDefault(); - this.hideDetail(closeBtn.dataset.detailId); } + // Блок с closeBtn полностью удалён — он больше не нужен }); // Переключение табов (делегирование) this.tabsList.addEventListener('click', (e) => { const tab = e.target.closest('.tab'); if (!tab) return; - e.preventDefault(); const targetId = tab.dataset.target; if (!targetId) return; this.activateTabById(targetId); - this.tabsList.querySelectorAll('.tab').forEach(t => t.classList.toggle('active', t === tab)); - if (targetId === 'mermaid' && window.viewer) { + // Обновляем активную вкладку + this.tabsList.querySelectorAll('.tab').forEach(t => + t.classList.toggle('active', t === tab) + ); + + // Автоматический рендер диаграммы при открытии вкладки mermaid + if (targetId === 'mermaid' && window.viewer && !window.viewer.isRendered) { window.viewer.render().catch(console.error); } }); @@ -75,40 +75,49 @@ class ReportRenderer { renderTabs() { const fragment = document.createDocumentFragment(); + // 1. Summary — первый таб + const summaryTab = document.createElement('button'); + summaryTab.className = 'tab active'; // сразу делаем активным + summaryTab.dataset.target = 'summary_report'; + summaryTab.dataset.index = '0'; // можно оставить 0 или любое значение + summaryTab.innerHTML = ` +
+ Summary +
+ `; + fragment.appendChild(summaryTab); + + // 2. Табы файлов this.files.forEach((file, i) => { const critical = file.v?.c?.length || 0; const warning = file.v?.w?.length || 0; - const tab = document.createElement('button'); - tab.className = `tab ${i === 0 ? 'active' : ''}`; + tab.className = 'tab'; tab.dataset.target = `file_${i}`; - tab.dataset.index = i; - + tab.dataset.index = i + 1; tab.innerHTML = ` -
- ${this.escapeHtml(file.n)} -
- ${critical || ''} - ${warning || ''} -
+
+ ${this.escapeHtml(file.n)} +
+ ${critical || ''} + ${warning || ''}
- `; +
+ `; fragment.appendChild(tab); }); - // Summary tab - const summaryTab = document.createElement('button'); - summaryTab.className = 'tab'; - summaryTab.dataset.target = 'summary_report'; - summaryTab.innerHTML = `
Summary
`; - fragment.appendChild(summaryTab); - - // Diagram tab + // 3. Диаграмма (если есть) — последняя if (this.diagram.h || this.diagram.hasDiagram) { const diagramTab = document.createElement('button'); diagramTab.className = 'tab'; diagramTab.dataset.target = 'mermaid'; - diagramTab.innerHTML = `
Диаграмма
`; + diagramTab.dataset.index = this.files.length + 1; + diagramTab.innerHTML = ` +
+ Диаграмма +
+ `; fragment.appendChild(diagramTab); } @@ -322,61 +331,51 @@ class ReportRenderer { } toggleDetail(detailId, toggleBtn) { - // Находим текущий видимый отчёт (активную вкладку) const activeReport = document.querySelector('.file-report.active'); - if (!activeReport) return; - // Ищем строку детали ТОЛЬКО внутри активного отчёта const detailRow = activeReport.querySelector(`#${detailId}`); if (!detailRow) return; const mainRow = toggleBtn.closest('.violation-main-row'); - if (!mainRow || !activeReport.contains(mainRow)) return; // защита от "чужих" кнопок + if (!mainRow || !activeReport.contains(mainRow)) return; - const isVisible = detailRow.style.display === 'table-row'; + // Проверяем, открыта ли уже эта деталь + const isOpen = detailRow.style.display === 'table-row'; - if (isVisible) { - this.hideDetail(detailId); + // Сначала закрываем ВСЕ открытые детали в ЭТОМ отчёте + activeReport.querySelectorAll('.violation-detail-row').forEach(row => { + row.style.display = 'none'; + }); + activeReport.querySelectorAll('.detail-toggle-btn.active').forEach(btn => { + btn.classList.remove('active'); + const icon = btn.querySelector('.detail-toggle-icon'); + if (icon) icon.style.transform = 'rotate(0deg)'; + }); + + if (isOpen) { + // Если была открыта — теперь закрыта (мы уже всё закрыли выше) return; } - // Показываем текущую + // Открываем текущую detailRow.style.display = 'table-row'; toggleBtn.classList.add('active'); - const icon = toggleBtn.querySelector('.detail-toggle-icon'); if (icon) { icon.style.transform = 'rotate(180deg)'; } - // Прокрутка к детали + // Прокрутка setTimeout(() => { detailRow.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); - }, 10); - } - - hideDetail(detailId) { - const detailRow = document.getElementById(detailId); - if (!detailRow) return; - - detailRow.style.display = 'none'; - - const toggleBtn = document.querySelector(`.detail-toggle-btn[data-detail-id="${detailId}"]`); - if (toggleBtn) { - toggleBtn.classList.remove('active'); - const icon = toggleBtn.querySelector('.detail-toggle-icon'); - if (icon) { - icon.style.transform = 'rotate(0deg)'; - } - } + }, 100); // чуть больше задержки, чтобы браузер успел перерисовать } renderSummaryReport() { const div = document.createElement('div'); div.id = 'summary_report'; div.className = 'file-report'; - div.style.display = 'none'; let totalCritical = 0, totalWarning = 0, totalInfo = 0; this.files.forEach(f => { @@ -417,7 +416,7 @@ class ReportRenderer {
-
+