Карточка: обновление в DOM на месте (без перезагрузки) — страница не дёргается; бейджи не переносятся

Вместо location.reload при смене подписи карточка теперь обновляется рекурсивным морфингом
<main>: догружаем ту же страницу и сливаем только изменившиеся узлы, одинаковые поддеревья не
трогаем. Прокрутка окна и боковых блоков («Путь заявки» .claim-tl / .in-tl) не сбивается,
выделение в неизменном тексте сохраняется, нет прыжков и мигания. Морфинг пропускается, если
правится форма (dirty) или идёт выделение. Копи-кнопки переинициализируются (bjInitCopyBars).
Плюс .badge { white-space:nowrap } — в журнале стрелка «↗ Исходящий» больше не переносится над словом.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
fontvielle
2026-10-13 18:08:34 +03:00
parent 007164d4cf
commit a6c925572b
+48 -4
View File
@@ -161,7 +161,7 @@ th { color:var(--muted); font-weight:600; font-size:11px; text-transform:upperca
tbody tr:hover td { background:var(--accent-weak); }
/* ---- Бейджи статусов ---- */
.badge { display:inline-block; padding:3px 9px; border-radius:20px; font-size:11px; font-weight:600; }
.badge { display:inline-block; padding:3px 9px; border-radius:20px; font-size:11px; font-weight:600; white-space:nowrap; }
.live { font-size:11.5px; margin-top:4px; color:var(--muted); display:flex; align-items:center; gap:6px; line-height:1.3 }
.live::before { content:''; width:7px; height:7px; border-radius:50%; background:var(--muted); flex:none }
.live-wait::before { background:var(--accent); animation:pulse 2.4s var(--ease) infinite }
@@ -798,7 +798,8 @@ button.dq-link, .dq-link { background:none !important; border:0 !important; padd
return Promise.resolve(fallback());
};
// Кнопка «Скопировать» над каждым блоком скрипта/кода (pre.copyable): копирует текст целиком.
(function(){
// Вынесено в window.bjInitCopyBars, чтобы вызывать повторно после обновления карточки в DOM.
window.bjInitCopyBars = function(){
document.querySelectorAll('pre.copyable').forEach(function(pre){
if (pre.previousElementSibling && pre.previousElementSibling.classList.contains('copy-bar')) return;
var bar=document.createElement('div'); bar.className='copy-bar';
@@ -812,7 +813,8 @@ button.dq-link, .dq-link { background:none !important; border:0 !important; padd
});
bar.appendChild(b); pre.parentNode.insertBefore(bar, pre);
});
})();
};
bjInitCopyBars();
// Цепочка связи с НРД в шапке: обновляется каждые 30 с, чтобы открытая страница не показывала старое.
(function(){
var el = document.getElementById('nsd-chain');
@@ -856,6 +858,48 @@ button.dq-link, .dq-link { background:none !important; border:0 !important; padd
} catch(e){}
location.reload();
}
// Обновление карточки заявки БЕЗ перезагрузки: догружаем ту же страницу и аккуратно
// сливаем изменения в DOM (рекурсивный морфинг <main>). Одинаковые поддеревья не трогаем —
// страница не дёргается, прокрутка окна и боковых блоков («Путь заявки») не сбивается,
// выделение в неизменном тексте сохраняется. Скролл прокручиваемых блоков восстанавливается.
function bjMorphKids(oldP, newP){
if (!oldP || !newP) return;
var top = oldP.scrollTop, left = oldP.scrollLeft;
var olds = [].slice.call(oldP.childNodes), news = [].slice.call(newP.childNodes);
var common = Math.min(olds.length, news.length), i;
for (i = 0; i < common; i++) bjMorphNode(oldP, olds[i], news[i]);
for (i = olds.length - 1; i >= common; i--) oldP.removeChild(olds[i]);
for (i = common; i < news.length; i++) oldP.appendChild(news[i].cloneNode(true));
oldP.scrollTop = top; oldP.scrollLeft = left;
}
function bjMorphNode(parent, o, n){
if (o.nodeType === 3 || o.nodeType === 8) { // текст/комментарий
if (n.nodeType === o.nodeType) { if (o.nodeValue !== n.nodeValue) o.nodeValue = n.nodeValue; }
else parent.replaceChild(n.cloneNode(true), o);
return;
}
if (o.nodeType !== 1 || n.nodeType !== 1 || o.nodeName !== n.nodeName) { parent.replaceChild(n.cloneNode(true), o); return; }
if (o.outerHTML === n.outerHTML) return; // поддерево не изменилось — не трогаем (скролл/выделение целы)
var oa = o.attributes, na = n.attributes, j;
for (j = oa.length - 1; j >= 0; j--) { if (!n.hasAttribute(oa[j].name)) o.removeAttribute(oa[j].name); }
for (j = 0; j < na.length; j++) { if (o.getAttribute(na[j].name) !== na[j].value) o.setAttribute(na[j].name, na[j].value); }
bjMorphKids(o, n); // рекурсивно внутрь
}
var bjRefreshing = false;
function bjRefreshClaim(){
if (bjRefreshing) return;
bjRefreshing = true;
fetch(location.href, {cache: 'no-store', credentials: 'same-origin'})
.then(function(r){ return r.ok ? r.text() : Promise.reject(); })
.then(function(html){
if (dirty || (window.bjSelecting && window.bjSelecting())) return; // правит форму или выделяет — не трогаем
var doc = new DOMParser().parseFromString(html, 'text/html');
bjMorphKids(document.querySelector('main'), doc.querySelector('main'));
if (window.bjInitCopyBars) window.bjInitCopyBars();
})
.catch(function(){})
.then(function(){ bjRefreshing = false; });
}
try {
var raw = sessionStorage.getItem(scrollKey);
if (raw !== null) {
@@ -895,7 +939,7 @@ button.dq-link, .dq-link { background:none !important; border:0 !important; padd
if (!st) return;
var big = e.classList.contains('live-big'), was = e.getAttribute('data-sig');
if (big && was && st.sig && st.sig !== was) {
if (!dirty) { bjReloadKeepScroll(); return; }
if (!dirty) { bjRefreshClaim(); e.setAttribute('data-sig', st.sig); return; }
meta(e, 'Заявка изменилась. <a href="' + location.pathname + location.search + '">Обновить страницу</a> (отмеченное в форме не сохранится)', 'changed');
e.setAttribute('data-sig', st.sig);
}