diff --git a/web/src/main/resources/static/css/app.css b/web/src/main/resources/static/css/app.css index e2d9acc..85733c1 100644 --- a/web/src/main/resources/static/css/app.css +++ b/web/src/main/resources/static/css/app.css @@ -235,6 +235,9 @@ dialog.modal { box-shadow: 0 20px 60px rgba(0,0,0,0.35); } dialog.modal::backdrop { background: rgba(0,0,0,0.55); } +/* Пока открыта модалка — страница не прокручивается; прокрутка остаётся внутри .modal-body */ +html.modal-open, html.modal-open body { overflow: hidden; } +html.modal-open body { padding-right: var(--modal-scrollbar, 0); } .modal-header { display: flex; align-items: center; justify-content: space-between; padding: 1rem 1.5rem; border-bottom: 1px solid var(--c-border); } .modal-title { margin: 0; font-size: 1.0625rem; font-weight: 600; } .modal-close { border: none; background: none; cursor: pointer; font-size: 1.25rem; color: var(--c-text-muted); padding: 0 0.25rem; } @@ -304,17 +307,17 @@ code.code { padding: 0.125rem 0.5rem; border-radius: 0.25rem; font-family: monos .chip-neu { background: #5d6570; color: #fff; } .chip-neg { background: #b3283b; color: #fff; } /* Слоты комментариев в диалоге */ -.cs-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; } -.cs-text-wrap { position: relative; flex: 1 1 12rem; min-width: 0; } +.cs-row { display: flex; gap: 6px; align-items: center; } +.cs-text-wrap { position: relative; flex: 1 1 auto; min-width: 0; } .cs-text-wrap .cs-text { width: 100%; } .cs-row .cs-tone, .cs-row .btn { flex: 0 0 auto; } -.cs-tone { max-width: 9rem; } .ac-list { position: absolute; left: 0; right: 0; top: 100%; margin-top: 2px; z-index: 20; max-height: 14rem; overflow-y: auto; background: var(--c-surface); border: 1px solid var(--c-border); border-radius: 0.4rem; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15); } +.ac-list.drop-up { top: auto; bottom: 100%; margin-top: 0; margin-bottom: 2px; } .ac-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.4rem 0.6rem; cursor: pointer; font-size: 0.875rem; } .ac-item:hover, .ac-item.active { background: var(--c-bg); } .ac-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } diff --git a/web/src/main/resources/static/js/admin-group.js b/web/src/main/resources/static/js/admin-group.js index c92885d..7e62f42 100644 --- a/web/src/main/resources/static/js/admin-group.js +++ b/web/src/main/resources/static/js/admin-group.js @@ -47,7 +47,7 @@ function csRow(text, tone) { var sel = document.createElement('select'); sel.className = 'input cs-tone'; sel.title = 'Тональность комментария'; - [['positive', '+ позитив'], ['negative', '— негатив'], ['neutral', '○ нейтрал']].forEach(function (o) { + [['positive', '+ позитивный'], ['negative', '— негативный'], ['neutral', '○ нейтральный']].forEach(function (o) { var op = document.createElement('option'); op.value = o[0]; op.textContent = o[1]; @@ -152,8 +152,22 @@ function attachCommentAutocomplete(input) { }); list.hidden = false; active = -1; + positionList(); } + /* Если снизу внутри модалки мало места — раскрываем список вверх, чтобы он не обрезался. */ + function positionList() { + var wrap = input.parentNode; + var scrollParent = input.closest('.modal-body') || document.documentElement; + var wr = wrap.getBoundingClientRect(); + var sr = scrollParent.getBoundingClientRect(); + var below = sr.bottom - wr.bottom; + var above = wr.top - sr.top; + list.classList.toggle('drop-up', below < 220 && above > below); + } + + window.addEventListener('resize', function () { if (!list.hidden) positionList(); }); + function load() { clearTimeout(AC_TIMER); AC_TIMER = setTimeout(function () { diff --git a/web/src/main/resources/templates/fragments/base.html b/web/src/main/resources/templates/fragments/base.html index 4401f8e..3968f32 100644 --- a/web/src/main/resources/templates/fragments/base.html +++ b/web/src/main/resources/templates/fragments/base.html @@ -27,15 +27,30 @@ document.querySelectorAll('pre code').forEach(function(el){ window.hljs.highlightElement(el); }); }; })(); - document.addEventListener('DOMContentLoaded', function(){ window.__hljsSet(document.documentElement.getAttribute('data-theme')); window.__hljsAll(); }); + document.addEventListener('DOMContentLoaded', function(){ window.__hljsSet(document.documentElement.getAttribute('data-theme')); window.__hljsAll(); syncModalScrollLock(); }); function toggleTheme(){ var e=document.documentElement; var t=e.getAttribute('data-theme')==='dark'?'light':'dark'; e.setAttribute('data-theme',t); localStorage.setItem('lms-theme',t); window.__hljsSet(t); } - function openModal(id){ var d=document.getElementById(id); if(d) d.showModal(); } - function closeModal(id){ var d=document.getElementById(id); if(d) d.close(); } + function openModal(id){ var d=document.getElementById(id); if(d) d.showModal(); syncModalScrollLock(); } + function closeModal(id){ var d=document.getElementById(id); if(d) d.close(); syncModalScrollLock(); } function openModalData(btn){ openModal(btn.dataset.modal); } function closeModalData(btn){ closeModal(btn.dataset.modal); } + /* Пока открыта хоть одна модалка — блокируем прокрутку страницы (внутри .modal-body прокрутка остаётся). */ + function syncModalScrollLock(){ + var root = document.documentElement; + if (document.querySelector('dialog[open]')) { + var sw = window.innerWidth - root.clientWidth; + root.style.setProperty('--modal-scrollbar', (sw > 0 ? sw : 0) + 'px'); + root.classList.add('modal-open'); + } else { + root.classList.remove('modal-open'); + root.style.removeProperty('--modal-scrollbar'); + } + } + document.addEventListener('toggle', function (e) { + if (e.target && e.target.tagName === 'DIALOG') syncModalScrollLock(); + }, true); function sectionStateKey(id){ return 'lms-section-open:' + id; } function applySectionState(acc){ var id = acc.getAttribute('data-section-id');