fix(pagination): align the load-more skeleton width with the cards
首页「加载更多」的骨架比列表本身宽出一截:.posts-row 用 margin-left/right:-8px 抵消卡片的水平边距,而卡片边距规则是 `.posts-row > *:not(.post_ajax_loader)`,把骨架排除在补偿链之外, 于是骨架左右各宽出 8px(合计 16px)。移动端的 5px 覆盖块同理。 小工具的 .widget-ajaxpager / tab pane 也是一条独立的负边距补偿链 (margin: -8px 抵消 .posts-item 的 8px),骨架同样溢出。 修法是给骨架补上与卡片等同的水平边距,而不是写 inline display (会盖过 .is-loading 类规则)。改了 main.css、post-card.css 的 8px / 5px 两组规则,用户中心面板另加一条归零规则,因为那里的 容器 margin 与卡片边距都已清零,不能再叠加补偿。 顺带补一条契约测试:删掉任一 gutter 规则即失败,防止回归。 Verification: - node --test tests/**/*.test.cjs 471 passed / 0 failed - php tests/widget-system/run.php 136 assertions - php tools/audit-widget-system.php - php tools/audit-admin-contracts.php errors=0 (56 既有 baseline 警告) - node tools/build-public-assets.mjs CSS/JS 产物已重建 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,13 @@
|
|||||||
margin-left: 8px !important;
|
margin-left: 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 骨架与真实卡片同宽:.posts-row 以 -8px 抵消卡片边距,loader 被上面的
|
||||||
|
:not() 排除在补偿外,会宽出 16px;这里补上等同的水平边距。 */
|
||||||
|
.posts-row > .post_ajax_loader {
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.posts-item {
|
.posts-item {
|
||||||
margin: 15px 0;
|
margin: 15px 0;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@@ -448,6 +455,15 @@
|
|||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 骨架与卡片同宽同轴:.widget-ajaxpager / .tab-pane 用负边距抵消卡片的水平
|
||||||
|
边距,骨架被排除在补偿链外,于是左右各宽出一个 gutter。 */
|
||||||
|
.widget-main-post.style-list .widget-ajaxpager > .post_ajax_loader,
|
||||||
|
.widget-main-post.style-card .widget-ajaxpager > .post_ajax_loader,
|
||||||
|
.widget-tab-post .tab-content > .tab-pane > .widget-ajaxpager > .post_ajax_loader {
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.posts-mini:hover img,
|
.posts-mini:hover img,
|
||||||
.posts-item:hover .item-thumbnail img,
|
.posts-item:hover .item-thumbnail img,
|
||||||
.posts-item.mult-thumb .thumb-items > span > img:hover {
|
.posts-item.mult-thumb .thumb-items > span > img:hover {
|
||||||
@@ -518,6 +534,11 @@
|
|||||||
margin-left: 5px !important;
|
margin-left: 5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.posts-row > .post_ajax_loader {
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.posts-item .item-thumbnail {
|
.posts-item .item-thumbnail {
|
||||||
width: 138px;
|
width: 138px;
|
||||||
}
|
}
|
||||||
@@ -564,6 +585,13 @@
|
|||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.widget-main-post.style-list .widget-ajaxpager > .post_ajax_loader,
|
||||||
|
.widget-main-post.style-card .widget-ajaxpager > .post_ajax_loader,
|
||||||
|
.widget-tab-post .tab-content > .tab-pane > .widget-ajaxpager > .post_ajax_loader {
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.swiper-scroll .posts-item {
|
.swiper-scroll .posts-item {
|
||||||
margin-right: 10px !important;
|
margin-right: 10px !important;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -14,6 +14,13 @@
|
|||||||
margin-left: 8px !important;
|
margin-left: 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 骨架与真实卡片同宽:.posts-row 以 -8px 抵消卡片边距,loader 被上面的
|
||||||
|
:not() 排除在补偿外,会宽出 16px;这里补上等同的水平边距。 */
|
||||||
|
.posts-row > .post_ajax_loader {
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.posts-item {
|
.posts-item {
|
||||||
margin: 15px 0;
|
margin: 15px 0;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@@ -447,6 +454,15 @@
|
|||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 骨架与卡片同宽同轴:.widget-ajaxpager / .tab-pane 用负边距抵消卡片的水平
|
||||||
|
边距,骨架被排除在补偿链外,于是左右各宽出一个 gutter。 */
|
||||||
|
.widget-main-post.style-list .widget-ajaxpager > .post_ajax_loader,
|
||||||
|
.widget-main-post.style-card .widget-ajaxpager > .post_ajax_loader,
|
||||||
|
.widget-tab-post .tab-content > .tab-pane > .widget-ajaxpager > .post_ajax_loader {
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.posts-mini:hover img,
|
.posts-mini:hover img,
|
||||||
.posts-item:hover .item-thumbnail img,
|
.posts-item:hover .item-thumbnail img,
|
||||||
.posts-item.mult-thumb .thumb-items > span > img:hover {
|
.posts-item.mult-thumb .thumb-items > span > img:hover {
|
||||||
@@ -517,6 +533,11 @@
|
|||||||
margin-left: 5px !important;
|
margin-left: 5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.posts-row > .post_ajax_loader {
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.posts-item .item-thumbnail {
|
.posts-item .item-thumbnail {
|
||||||
width: 138px;
|
width: 138px;
|
||||||
}
|
}
|
||||||
@@ -563,6 +584,13 @@
|
|||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.widget-main-post.style-list .widget-ajaxpager > .post_ajax_loader,
|
||||||
|
.widget-main-post.style-card .widget-ajaxpager > .post_ajax_loader,
|
||||||
|
.widget-tab-post .tab-content > .tab-pane > .widget-ajaxpager > .post_ajax_loader {
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.swiper-scroll .posts-item {
|
.swiper-scroll .posts-item {
|
||||||
margin-right: 10px !important;
|
margin-right: 10px !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7486,6 +7486,13 @@ badge.top,
|
|||||||
margin-right: 8px !important;
|
margin-right: 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 骨架与真实卡片同宽同轴:.posts-row 用 -8px 抵消卡片边距,原 :not() 把骨架
|
||||||
|
排除在补偿外,骨架于是比卡片左右各宽 8px。补上等同边距。 */
|
||||||
|
.posts-row > .post_ajax_loader {
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.posts-item {
|
.posts-item {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin: 15px 0;
|
margin: 15px 0;
|
||||||
@@ -7890,6 +7897,16 @@ badge.top,
|
|||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 骨架与卡片同宽同轴:.widget-ajaxpager / .tab-pane 用负边距抵消卡片的水平
|
||||||
|
边距,骨架被排除在补偿链外,于是左右各宽出一个 gutter。这里补回等宽的
|
||||||
|
边距,让骨架与真实卡片对齐。tab pane 的 style-list / index-tab 都适用。 */
|
||||||
|
.widget-main-post.style-list .widget-ajaxpager > .post_ajax_loader,
|
||||||
|
.widget-main-post.style-card .widget-ajaxpager > .post_ajax_loader,
|
||||||
|
.widget-tab-post .tab-content > .tab-pane > .widget-ajaxpager > .post_ajax_loader {
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.widget-tab-post.index-tab .list-inline {
|
.widget-tab-post.index-tab .list-inline {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
@@ -11213,6 +11230,13 @@ body.error404 {
|
|||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.widget-main-post.style-list .widget-ajaxpager > .post_ajax_loader,
|
||||||
|
.widget-main-post.style-card .widget-ajaxpager > .post_ajax_loader,
|
||||||
|
.widget-tab-post .tab-content > .tab-pane > .widget-ajaxpager > .post_ajax_loader {
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.posts-row {
|
.posts-row {
|
||||||
margin-left: -5px;
|
margin-left: -5px;
|
||||||
margin-right: -5px;
|
margin-right: -5px;
|
||||||
@@ -11223,6 +11247,11 @@ body.error404 {
|
|||||||
margin-right: 5px !important;
|
margin-right: 5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.posts-row > .post_ajax_loader {
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.posts-item.card {
|
.posts-item.card {
|
||||||
width: calc(50% - 10px);
|
width: calc(50% - 10px);
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -390,6 +390,13 @@
|
|||||||
width: auto; /* 覆盖 vendored site-layout-1 的 width: calc(50% - 16px),让卡片填满网格单元 */
|
width: auto; /* 覆盖 vendored site-layout-1 的 width: calc(50% - 16px),让卡片填满网格单元 */
|
||||||
margin: 0 !important; /* 抵消 vendored .posts-row > * 的 8px !important 边距 */
|
margin: 0 !important; /* 抵消 vendored .posts-row > * 的 8px !important 边距 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 骨架跟随网格走:容器 margin 已归零、卡片边距已清零,
|
||||||
|
不能再叠加 .posts-row > .post_ajax_loader 的 8px 补偿。 */
|
||||||
|
.aksrt-user-home .aksrt-user-home__post-list[data-view="list"] > .post_ajax_loader {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
|
|||||||
@@ -92,6 +92,64 @@ test('skeleton loading ships one contract and no dead spinner stylesheets', () =
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('skeleton blocks share the card gutter instead of overflowing it', () => {
|
||||||
|
// .posts-row pulls the container in with a negative margin and hands every
|
||||||
|
// card a matching gutter. The card rule is scoped
|
||||||
|
// `.posts-row > *:not(.post_ajax_loader)`, so the loader is excluded from the
|
||||||
|
// compensation chain and ends up wider than the cards by both gutters.
|
||||||
|
// Each gutter block therefore needs a sibling rule that gives the loader the
|
||||||
|
// same horizontal margin — otherwise 加载更多 shows a skeleton that spills
|
||||||
|
// outside the content column.
|
||||||
|
const blocks = [
|
||||||
|
{
|
||||||
|
file: 'assets/css/main.css',
|
||||||
|
selector: '.posts-row > .post_ajax_loader',
|
||||||
|
gutters: [8, 5],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'assets/css/frontend/post-card.css',
|
||||||
|
selector: '.posts-row > .post_ajax_loader',
|
||||||
|
gutters: [8, 5],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const block of blocks) {
|
||||||
|
const css = fs.readFileSync(path.join(root, block.file), 'utf8')
|
||||||
|
const rules = (css.match(
|
||||||
|
new RegExp(`${block.selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*\\{[^}]*\\}`, 'g')
|
||||||
|
) || []).map((rule) => rule.replace(/\s+/g, ' '))
|
||||||
|
|
||||||
|
// One rule per gutter value in that file (desktop 8px, mobile 5px).
|
||||||
|
for (const gutter of block.gutters) {
|
||||||
|
const matched = rules.some(
|
||||||
|
(rule) =>
|
||||||
|
rule.includes(`margin-left: ${gutter}px`) &&
|
||||||
|
rule.includes(`margin-right: ${gutter}px`)
|
||||||
|
)
|
||||||
|
assert.ok(
|
||||||
|
matched,
|
||||||
|
`${block.file} must give .post_ajax_loader the ${gutter}px gutter in "${block.selector}"`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The widget pagers apply their own negative-margin compensation chain
|
||||||
|
// (.widget-ajaxpager / .tab-pane), so the loader needs the same relief there.
|
||||||
|
for (const file of ['assets/css/main.css', 'assets/css/frontend/post-card.css']) {
|
||||||
|
const css = fs.readFileSync(path.join(root, file), 'utf8')
|
||||||
|
// Cover every style class the renderer can emit (.style-list / .style-card,
|
||||||
|
// and the tab pane which carries style-list or index-tab).
|
||||||
|
assert.match(css, /\.widget-main-post\.style-list \.widget-ajaxpager > \.post_ajax_loader/)
|
||||||
|
assert.match(css, /\.widget-main-post\.style-card \.widget-ajaxpager > \.post_ajax_loader/)
|
||||||
|
assert.match(css, /\.widget-tab-post \.tab-content > \.tab-pane > \.widget-ajaxpager > \.post_ajax_loader/)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The user-home panel zeroes the container margin and the card gutters, so the
|
||||||
|
// loader must not pick up any leftover compensation there.
|
||||||
|
const userHome = fs.readFileSync(path.join(root, 'assets/css/routes/user-home.css'), 'utf8')
|
||||||
|
assert.match(userHome, /\.aksrt-user-home__post-list\[data-view="list"\] > \.post_ajax_loader \{\s*margin-right: 0;\s*margin-left: 0;/)
|
||||||
|
})
|
||||||
|
|
||||||
test('asset tables are InnoDB and operation keys are unique', () => {
|
test('asset tables are InnoDB and operation keys are unique', () => {
|
||||||
const schema = fs.readFileSync(path.join(root, 'src', 'Core', 'DBMigratorPart2.php'), 'utf8')
|
const schema = fs.readFileSync(path.join(root, 'src', 'Core', 'DBMigratorPart2.php'), 'utf8')
|
||||||
for (const table of ['asset_account', 'asset_transaction', 'asset_entry']) {
|
for (const table of ['asset_account', 'asset_transaction', 'asset_entry']) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"generated_at": "2026-10-03T14:21:05+00:00",
|
"generated_at": "2026-10-03T16:24:36+00:00",
|
||||||
"errors": [],
|
"errors": [],
|
||||||
"warnings": [
|
"warnings": [
|
||||||
"customPage tools-update section 'tools/update' not in PHP schema (ok if pure operational page)",
|
"customPage tools-update section 'tools/update' not in PHP schema (ok if pure operational page)",
|
||||||
|
|||||||
Reference in New Issue
Block a user