diff --git a/assets/js/messages.js b/assets/js/messages.js index e66f6d9c..f7120258 100644 --- a/assets/js/messages.js +++ b/assets/js/messages.js @@ -121,19 +121,10 @@ if (settings.append && state.paginateType === 'ajax_lists') { firstAdded = appendMessagePage(result.data.content); } else { - // 替换时保留骨架容器(参考主题 success 后只 hide() 不删除), - // 让后续翻页复用同一节点而不是回到无骨架状态。 - var keep = content.querySelector('.post_ajax_loader'); + // 整块替换:服务端每个 .messages-list 都自带收起态骨架,无需搬运旧节点。 + // 旧节点此刻带着 .is-loading,搬进空列表 / 详情页会脱离 + // `.messages-list >` 选择器,finally 摘不掉类,骨架会常驻。 content.innerHTML = result.data.content; - var nextSkeleton = content.querySelector('.post_ajax_loader'); - if (keep && !nextSkeleton) { - var list = content.querySelector('.messages-list'); - if (list) { - list.appendChild(keep); - } else { - content.appendChild(keep); - } - } } updateHeaderBadge(result.data.unread); @@ -153,8 +144,10 @@ }).finally(function () { root.classList.remove('is-loading'); content.removeAttribute('aria-busy'); - var loader = content.querySelector('.messages-list > .post_ajax_loader'); - if (loader) loader.classList.remove('is-loading'); + // 收起所有残留的骨架(不限于 .messages-list 直接子节点),保证任何分支都不会卡在加载态。 + content.querySelectorAll('.post_ajax_loader.is-loading').forEach(function (loader) { + loader.classList.remove('is-loading'); + }); }); } diff --git a/tests/widget-system/message-center-contract.test.cjs b/tests/widget-system/message-center-contract.test.cjs index 2c1adad7..229660e4 100644 --- a/tests/widget-system/message-center-contract.test.cjs +++ b/tests/widget-system/message-center-contract.test.cjs @@ -89,6 +89,12 @@ test('message centre shows a skeleton during the AJAX load-more cycle', () => { assert.match(script, /content\.querySelector\('\.messages-list > \.post_ajax_loader'\)/); assert.match(script, /skeleton\.classList\.add\('is-loading'\)/); assert.match(script, /loader\.classList\.remove\('is-loading'\)/); + // Teardown must sweep every loader, not just `.messages-list >` children: + // a loader stranded outside a list (empty view / detail) would stay open. + assert.match(script, /content\.querySelectorAll\('\.post_ajax_loader\.is-loading'\)/); + // Full replacement must not carry the old (still is-loading) node across. + assert.doesNotMatch(script, /list\.appendChild\(keep\)/); + assert.doesNotMatch(script, /content\.appendChild\(keep\)/); // Appended rows land before the skeleton so it stays flush under the list. assert.match(script, /currentList\.insertBefore\(item, skeleton\)/);