fix(frontend): align layouts posts and widgets with zibll
This commit is contained in:
+13
-2
@@ -6,8 +6,19 @@
|
||||
# Project planning, internal docs, and local-only development files
|
||||
/.specs/
|
||||
/docs/
|
||||
/tools/
|
||||
/tests/
|
||||
/tools/*
|
||||
/tests/*
|
||||
!/tools/wp-cli-visual-fixtures.php
|
||||
!/tests/fixtures/
|
||||
!/tests/visual/
|
||||
/tests/fixtures/*
|
||||
!/tests/fixtures/page-visual-matrix-v1.json
|
||||
!/tests/widget-system/
|
||||
/tests/widget-system/*
|
||||
!/tests/widget-system/post-visual-contract.test.cjs
|
||||
!/tests/visual/docker-compose.zibll.yml
|
||||
!/tests/visual/zibll-Caddyfile
|
||||
!/tests/visual/README.md
|
||||
/CLAUDE.md
|
||||
ask-*.md
|
||||
!assets/css/routes/docs/
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -57,9 +57,8 @@
|
||||
* body.site-layout-2 = sidebar right
|
||||
* body.site-layout-3 = sidebar left
|
||||
*
|
||||
* Desktop columns use Grid so the sidebar is always a real second column.
|
||||
* The float rules below remain as a narrow-screen compatibility fallback;
|
||||
* below 768px the sidebar is intentionally hidden.
|
||||
* This intentionally uses Zibll's float/negative-margin ownership model.
|
||||
* Below 992px the sidebar is hidden and the content margins collapse.
|
||||
* ============================================================ */
|
||||
|
||||
main.container,
|
||||
@@ -130,14 +129,6 @@ main.content-shell::after,
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Single posts use a theme-specific class. It avoids collisions with legacy
|
||||
* scripts that treat a generic .sidebar node as disposable page chrome. */
|
||||
.aksrt-article-sidebar {
|
||||
display: none;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content-layout__aside {
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -198,100 +189,6 @@ body.site-layout-3 .sidebar,
|
||||
position: unset;
|
||||
}
|
||||
|
||||
/* Desktop dual-column geometry.
|
||||
*
|
||||
* A full-width floated .content-wrap plus a negatively-margined sidebar can
|
||||
* leave the sidebar outside the visible painting area when a parent clips
|
||||
* horizontal overflow. Put the two direct children in explicit grid columns
|
||||
* instead, so widgets cannot be positioned off-canvas. */
|
||||
@media (min-width: 768px) {
|
||||
body.site-layout-2 main.container,
|
||||
body.site-layout-2 main.content-shell,
|
||||
body.site-layout-2 .site-main.container {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) var(--sidebar-width, 311px);
|
||||
column-gap: 15px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
body.site-layout-3 main.container,
|
||||
body.site-layout-3 main.content-shell,
|
||||
body.site-layout-3 .site-main.container {
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-width, 311px) minmax(0, 1fr);
|
||||
column-gap: 15px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
body.site-layout-2 main.container::after,
|
||||
body.site-layout-2 main.content-shell::after,
|
||||
body.site-layout-2 .site-main.container::after,
|
||||
body.site-layout-3 main.container::after,
|
||||
body.site-layout-3 main.content-shell::after,
|
||||
body.site-layout-3 .site-main.container::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
body.site-layout-2 main.container > .content-wrap,
|
||||
body.site-layout-2 main.content-shell > .content-wrap,
|
||||
body.site-layout-2 .site-main.container > .content-wrap {
|
||||
float: none;
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
body.site-layout-3 main.container > .content-wrap,
|
||||
body.site-layout-3 main.content-shell > .content-wrap,
|
||||
body.site-layout-3 .site-main.container > .content-wrap {
|
||||
float: none;
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
body.site-layout-2 main.container > .content-wrap > .content-layout,
|
||||
body.site-layout-2 main.content-shell > .content-wrap > .content-layout,
|
||||
body.site-layout-2 .site-main.container > .content-wrap > .content-layout,
|
||||
body.site-layout-3 main.container > .content-wrap > .content-layout,
|
||||
body.site-layout-3 main.content-shell > .content-wrap > .content-layout,
|
||||
body.site-layout-3 .site-main.container > .content-wrap > .content-layout {
|
||||
width: auto;
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
body.site-layout-2 main.container > .sidebar,
|
||||
body.site-layout-2 main.content-shell > .sidebar,
|
||||
body.site-layout-2 .site-main.container > .sidebar,
|
||||
body.site-layout-2 main.container > .aksrt-article-sidebar,
|
||||
body.site-layout-2 main.content-shell > .aksrt-article-sidebar,
|
||||
body.site-layout-2 .site-main.container > .aksrt-article-sidebar {
|
||||
display: block;
|
||||
float: none;
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
width: var(--sidebar-width, 311px);
|
||||
margin-left: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body.site-layout-3 main.container > .sidebar,
|
||||
body.site-layout-3 main.content-shell > .sidebar,
|
||||
body.site-layout-3 .site-main.container > .sidebar,
|
||||
body.site-layout-3 main.container > .aksrt-article-sidebar,
|
||||
body.site-layout-3 main.content-shell > .aksrt-article-sidebar,
|
||||
body.site-layout-3 .site-main.container > .aksrt-article-sidebar {
|
||||
display: block;
|
||||
float: none;
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
width: var(--sidebar-width, 311px);
|
||||
margin-left: 0;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sticky widgets inside sidebar */
|
||||
@media (min-width: 992px) {
|
||||
body.site-layout-2 .sidebar .posts-nav-box,
|
||||
|
||||
@@ -43,14 +43,13 @@ article.posts-item,
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Custom element <posts> is inline by default — force block layout here only,
|
||||
scoped so it never overrides .posts-item.card inline-block grid (Phase 1 fix). */
|
||||
/* Custom element <posts> is inline by default. */
|
||||
posts.posts-item {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.posts-item.list {
|
||||
display: block;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
font-size: 1rem;
|
||||
}
|
||||
@@ -97,6 +96,40 @@ body.dark-theme .posts-item:hover {
|
||||
margin: 5px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.posts-item:not(.card) .item-thumbnail {
|
||||
width: 138px;
|
||||
}
|
||||
|
||||
.posts-mini .item-thumbnail,
|
||||
.posts-item.list.style2 .item-thumbnail {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.posts-item .post-graphic:not(.order1) {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.posts-item .order1.post-graphic {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.posts-item .item-heading {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.posts-item:not(.no-thumb) .item-excerpt {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.posts-item.mult-thumb .thumb-items > span {
|
||||
width: calc(33.333% - 6px);
|
||||
padding-bottom: calc(var(--posts-list-scale, 70%) / 3);
|
||||
}
|
||||
|
||||
.posts-item.mult-thumb .thumb-items > span:nth-child(4) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -113,10 +146,17 @@ body.dark-theme .posts-item:hover {
|
||||
width: 190px;
|
||||
height: 0;
|
||||
padding-bottom: var(--posts-list-scale, 70%);
|
||||
float: left;
|
||||
}
|
||||
|
||||
.posts-item .post-graphic:not(.order1) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.posts-item .order1.post-graphic {
|
||||
margin-right: 0;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
/* 卡片模式缩略图 */
|
||||
.posts-item.card .item-thumbnail {
|
||||
width: 100%;
|
||||
@@ -334,12 +374,6 @@ body.dark-theme .posts-item:hover {
|
||||
/* ============================================================
|
||||
* 列表模式 clearfix — 对齐 zibll float 布局
|
||||
* ============================================================ */
|
||||
.posts-item:not(.card)::after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Mini 文章列表 — 对齐 zibll .posts-mini line 7903
|
||||
* ============================================================ */
|
||||
|
||||
+33
-22
@@ -1,7 +1,7 @@
|
||||
.aksrt-widget {
|
||||
--aksrt-widget-bg-light: transparent;
|
||||
--aksrt-widget-bg-dark: transparent;
|
||||
--aksrt-widget-radius: 12px;
|
||||
--aksrt-widget-radius: var(--main-radius, 8px);
|
||||
margin: var(--aksrt-widget-space-top, 0) var(--aksrt-widget-space-right, 0) var(--aksrt-widget-space-bottom, 20px) var(--aksrt-widget-space-left, 0);
|
||||
background-color: var(--aksrt-widget-bg-light);
|
||||
background-image: var(--aksrt-widget-bg-image-light, none);
|
||||
@@ -9,38 +9,48 @@
|
||||
background-size: cover;
|
||||
color: var(--theme-color, #2d2d35);
|
||||
}
|
||||
.aksrt-widget.zib-widget {
|
||||
clear: both;
|
||||
padding: 15px;
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background-color: var(--main-bg-color, #fff);
|
||||
box-shadow: 0 0 10px var(--main-shadow, rgba(116, 116, 116, .08));
|
||||
}
|
||||
.aksrt-widget__inner.container { position: relative; }
|
||||
.dark-theme .aksrt-widget,
|
||||
[data-theme="dark"] .aksrt-widget { background-color: var(--aksrt-widget-bg-dark); background-image: var(--aksrt-widget-bg-image-dark, none); color: #e7e7ea; }
|
||||
.aksrt-widget *, .aksrt-widget *::before, .aksrt-widget *::after { box-sizing: border-box; }
|
||||
.aksrt-widget img, .aksrt-widget video { max-width: 100%; }
|
||||
.aksrt-widget a { color: inherit; text-decoration: none; }
|
||||
.aksrt-widget a { text-decoration: none; }
|
||||
.aksrt-widget button, .aksrt-widget input { font: inherit; }
|
||||
.aksrt-widget :where(a, button, input, select, textarea, [tabindex]):focus-visible { outline: 3px solid var(--focus-color, #ff5473) !important; outline-offset: 3px; }
|
||||
.dark-theme .aksrt-widget :where(a, button, input, select, textarea, [tabindex]):focus-visible,
|
||||
[data-theme="dark"] .aksrt-widget :where(a, button, input, select, textarea, [tabindex]):focus-visible { outline-color: #ffd166 !important; }
|
||||
.aksrt-widget__header { display: flex; align-items: flex-end; justify-content: space-between; gap: 18px; margin-bottom: 16px; }
|
||||
.aksrt-widget__header { display: flex; align-items: flex-end; justify-content: space-between; gap: 18px; margin-bottom: 10px; }
|
||||
.aksrt-widget__header--big { display: block; text-align: center; margin-bottom: 30px; }
|
||||
.aksrt-widget__title { margin: 0; font-size: 20px; line-height: 1.35; font-weight: 700; }
|
||||
.aksrt-widget__header--big .aksrt-widget__title { font-size: clamp(27px, 4vw, 42px); }
|
||||
.aksrt-widget__title { position: relative; margin: 0; padding-bottom: 8px; font-size: 15px; line-height: 1.35; font-weight: 700; }
|
||||
.aksrt-widget__title::before { position:absolute; bottom:3px; left:0; width:40px; height:3px; border-radius:5px; background:var(--theme-color,var(--focus-color,#ff5473)); box-shadow:1px 1px 3px -1px var(--theme-color,var(--focus-color,#ff5473)); content:""; }
|
||||
.aksrt-widget__header--big .aksrt-widget__title { padding:0; font-size: clamp(28px, 4vw, 42px); }
|
||||
.aksrt-widget__header--big .aksrt-widget__title::before { content:none; }
|
||||
.aksrt-widget__title mark { padding: 0; background: transparent; color: var(--focus-color, #ff5473); }
|
||||
.aksrt-widget__subtitle { margin: 7px 0 0; color: var(--muted-color, #8a8c98); }
|
||||
.aksrt-widget__title-links { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
|
||||
.aksrt-widget__title-links a { padding: 5px 11px; border-radius: 999px; background: #f2f3f5; font-size: 13px; }
|
||||
.aksrt-widget__title-links a { padding: 5px 11px; border-radius: 4px; background: var(--muted-border-color, #f2f3f5); color:var(--muted-color,#777); font-size: 13px; }
|
||||
.dark-theme .aksrt-widget__title-links a, [data-theme="dark"] .aksrt-widget__title-links a { background: #292a30; }
|
||||
.aksrt-widget-grid { display: grid; grid-template-columns: repeat(var(--aksrt-cols, 3), minmax(0, 1fr)); gap: 16px; }
|
||||
.aksrt-widget-grid { display: grid; grid-template-columns: repeat(var(--aksrt-cols, 3), minmax(0, 1fr)); gap: 15px; }
|
||||
.aksrt-widget__empty, .aksrt-widget-request-error { padding: 28px 18px; border: 1px dashed rgba(127,127,127,.25); border-radius: var(--aksrt-widget-radius); color: var(--muted-color, #8a8c98); text-align: center; }
|
||||
.aksrt-widget-request-error button { margin-left: 10px; padding: 5px 12px; border: 0; border-radius: 999px; cursor: pointer; }
|
||||
.aksrt-widget--affix { position: sticky; top: 76px; }
|
||||
.aksrt-widget--only-mobile { display: none; }
|
||||
|
||||
.aksrt-widget-search { display: flex; min-height: 46px; padding: 5px; border: 1px solid rgba(127,127,127,.18); border-radius: 10px; background: var(--main-bg, #fff); box-shadow: 0 8px 25px rgba(31,35,48,.06); }
|
||||
.aksrt-widget-search { display: flex; min-height: 42px; padding: 4px; border: 1px solid var(--main-border-color,rgba(127,127,127,.18)); border-radius: var(--main-radius,8px); background: var(--main-bg-color, #fff); }
|
||||
.aksrt-widget-search label { flex: 1; display: flex; }
|
||||
.aksrt-widget-search input { width: 100%; padding: 8px 12px; border: 0; outline: 0; background: transparent; color: inherit; }
|
||||
.aksrt-widget-search button { display: flex; align-items: center; gap: 7px; padding: 8px 16px; border: 0; border-radius: 8px; background: var(--focus-color, #ff5473); color: #fff; cursor: pointer; }
|
||||
.aksrt-widget-search button { display: flex; align-items: center; gap: 7px; padding: 7px 14px; border: 0; border-radius: var(--mini-radius,4px); background: var(--focus-color, #ff5473); color: #fff; cursor: pointer; }
|
||||
.aksrt-widget-search--compact button span { display: none; }
|
||||
.aksrt-widget-search--large { min-height: 58px; border-radius: 14px; }
|
||||
|
||||
.aksrt-widget-toc { padding: 14px 16px; border-radius: var(--aksrt-widget-radius); background: var(--main-bg, #fff); box-shadow: 0 8px 30px rgba(31,35,48,.06); }
|
||||
.aksrt-widget-toc { padding: 0; background: transparent; }
|
||||
.aksrt-widget-toc__toggle { display: flex; width: 100%; align-items: center; justify-content: space-between; padding: 0; border: 0; background: transparent; color: inherit; font-weight: 700; cursor: pointer; }
|
||||
.aksrt-widget-toc__list { margin: 14px 0 0; padding-left: 20px; max-height: 55vh; overflow: auto; }
|
||||
.aksrt-widget-toc__list li { margin: 7px 0; color: var(--muted-color, #737582); }
|
||||
@@ -53,11 +63,12 @@
|
||||
.aksrt-widget-quote blockquote { margin: 12px 0 0; font-size: 16px; line-height: 1.8; }
|
||||
.aksrt-widget-quote button { position: absolute; right: 13px; bottom: 13px; width: 30px; height: 30px; border: 0; border-radius: 50%; background: rgba(255,255,255,.16); color: #fff; cursor: pointer; }
|
||||
|
||||
.aksrt-widget-icon-card, .aksrt-widget-icon-cards .icon-card, .aksrt-widget-icon-cards .icon-cover-card { min-height: 130px; padding: 22px; border-radius: var(--aksrt-widget-radius); background: var(--main-bg, #fff); box-shadow: 0 10px 32px rgba(31,35,48,.07); transition: transform .25s, box-shadow .25s; }
|
||||
.aksrt-widget-icon-card:hover, .aksrt-widget-icon-cards .icon-card:hover, .aksrt-widget-icon-cards .icon-cover-card:hover { transform: translateY(-4px); box-shadow: 0 16px 40px rgba(31,35,48,.12); }
|
||||
.aksrt-widget-icon-card, .aksrt-widget-icon-cards .icon-card, .aksrt-widget-icon-cards .icon-cover-card { min-height: 130px; max-width:280px; margin-right:auto; margin-left:auto; padding: 20px; border-radius: var(--aksrt-widget-radius); background: var(--main-bg-color, #fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); transition: box-shadow .2s; }
|
||||
.aksrt-widget-icon-card:hover, .aksrt-widget-icon-cards .icon-card:hover, .aksrt-widget-icon-cards .icon-cover-card:hover { box-shadow: 0 0 10px rgba(116,116,116,.14); }
|
||||
.aksrt-widget-icon-card { display: flex; flex-direction: column; gap: 12px; text-align: center; }
|
||||
.aksrt-widget-icon-cards--cover .aksrt-widget-icon-card { flex-direction: row; align-items: center; text-align: left; }
|
||||
.aksrt-widget-icon-card > i { font-size: 34px; color: var(--focus-color, #ff5473); }
|
||||
.aksrt-widget-icon-card > i { font-size: 34px; color: var(--focus-color, #ff5473); transition:transform .3s; }
|
||||
.aksrt-widget-icon-card:hover > i { transform:scale(1.05); }
|
||||
.aksrt-widget-icon-card span { display: flex; flex-direction: column; gap: 5px; }
|
||||
.aksrt-widget-icon-card small { color: var(--muted-color, #8a8c98); }
|
||||
|
||||
@@ -68,7 +79,7 @@
|
||||
.aksrt-widget-embed { position: relative; height: 0; padding-bottom: var(--aksrt-embed-ratio, 56.25%); overflow: hidden; border-radius: var(--aksrt-widget-radius); background: #111; }
|
||||
.aksrt-widget-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
|
||||
|
||||
.aksrt-media-card { position: relative; display: block; overflow: hidden; min-height: 180px; border-radius: var(--aksrt-widget-radius); background: var(--main-bg, #fff); box-shadow: 0 8px 28px rgba(31,35,48,.07); }
|
||||
.aksrt-media-card { position: relative; display: block; overflow: hidden; min-height: 180px; border-radius: var(--aksrt-widget-radius); background: var(--main-bg-color, #fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }
|
||||
.aksrt-media-card__media { display: block; aspect-ratio: 16 / 9; overflow: hidden; background: #eceef2; }
|
||||
.aksrt-media-card__media img, .aksrt-media-card__media video { width: 100%; height: 100%; object-fit: cover; transition: transform .35s; }
|
||||
.aksrt-media-card:hover .aksrt-media-card__media img { transform: scale(1.04); }
|
||||
@@ -84,7 +95,7 @@
|
||||
.aksrt-widget-partners .aksrt-media-card__media { width: 64px; aspect-ratio: 1; margin-left: 8px; border-radius: 9px; }
|
||||
.aksrt-widget-partners .aksrt-media-card__body { padding: 10px; }
|
||||
@keyframes aksrt-partners { to { transform: translateX(-50%); } }
|
||||
.aksrt-combo-card { overflow: hidden; border-radius: var(--aksrt-widget-radius); background: var(--main-bg,#fff); box-shadow: 0 8px 28px rgba(31,35,48,.07); }
|
||||
.aksrt-combo-card { overflow: hidden; border-radius: var(--aksrt-widget-radius); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }
|
||||
.aksrt-combo-card .aksrt-media-card { box-shadow: none; }
|
||||
.aksrt-combo-card__tags { display: flex; flex-wrap: wrap; gap: 5px; padding: 0 16px 16px; }
|
||||
.aksrt-combo-card__tags span { padding: 3px 8px; border-radius: 999px; background: rgba(127,127,127,.1); font-size: 12px; }
|
||||
@@ -97,25 +108,25 @@
|
||||
.aksrt-widget-gap { height: var(--aksrt-gap); }
|
||||
.aksrt-widget-tag-cloud { display: flex; flex-wrap: wrap; gap: 8px; }.aksrt-widget-tag-cloud a { padding: 6px 11px; border-radius: 6px; background: rgba(127,127,127,.1); font-size: 13px; transition: background .2s,color .2s; }.aksrt-widget-tag-cloud a:hover { background: var(--focus-color,#ff5473); color: #fff; }.aksrt-widget-tag-cloud small { margin-left: 5px; opacity: .65; }
|
||||
.aksrt-widget-comments { display: grid; gap: 15px; margin: 0; padding: 0; list-style: none; }.aksrt-widget-comments li { display: flex; gap: 11px; }.aksrt-widget-comments__avatar img { width: 42px; height: 42px; border-radius: 50%; }.aksrt-widget-comments p { margin: 3px 0; color: var(--muted-color,#8a8c98); font-size: 13px; line-height: 1.55; }.aksrt-widget-comments__source { font-size: 12px; opacity: .65; }
|
||||
.aksrt-widget-links { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 10px; }.aksrt-widget-links a { display: flex; flex-direction: column; gap: 4px; padding: 14px; border-radius: 9px; background: var(--main-bg,#fff); box-shadow: 0 6px 20px rgba(31,35,48,.06); }.aksrt-widget-links small { color: var(--muted-color,#8a8c98); }.aksrt-widget-links--simple { display: flex; flex-wrap: wrap; }.aksrt-widget-links--simple a { padding: 5px 9px; box-shadow: none; background: transparent; }
|
||||
.aksrt-widget-notice { display: flex; height: 48px; align-items: center; gap: 12px; padding: 0 16px; overflow: hidden; border-radius: 9px; background: var(--main-bg,#fff); box-shadow: 0 7px 22px rgba(31,35,48,.06); }.aksrt-widget-notice .swiper { height: 48px; flex: 1; }.aksrt-widget-notice .swiper-slide, .aksrt-widget-notice .swiper-slide a { display: flex; height: 48px; align-items: center; gap: 10px; }.aksrt-widget-notice .swiper-slide span { overflow: hidden; color: var(--muted-color,#8a8c98); text-overflow: ellipsis; white-space: nowrap; }
|
||||
.aksrt-widget-links { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 10px; }.aksrt-widget-links a { display: flex; flex-direction: column; gap: 4px; padding: 10px 8px; border-radius: var(--main-radius,8px); background: var(--muted-border-color,#f2f3f5); color:var(--key-color,#333); }.aksrt-widget-links small { color: var(--muted-color,#8a8c98); }.aksrt-widget-links--simple { display: flex; flex-wrap: wrap; }.aksrt-widget-links--simple a { padding: 5px 9px; box-shadow: none; background: transparent; }
|
||||
.aksrt-widget-notice { display: flex; height: 48px; align-items: center; gap: 12px; padding: 0 15px; overflow: hidden; border-radius: var(--main-radius,8px); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }.aksrt-widget-notice .swiper { height: 48px; flex: 1; }.aksrt-widget-notice .swiper-slide, .aksrt-widget-notice .swiper-slide a { display: flex; height: 48px; align-items: center; gap: 10px; }.aksrt-widget-notice .swiper-slide span { overflow: hidden; color: var(--muted-color,#8a8c98); text-overflow: ellipsis; white-space: nowrap; }
|
||||
.aksrt-widget-swiper { position: relative; }.aksrt-widget-swiper__pause { position: absolute; z-index: 6; top: 10px; right: 10px; display: inline-flex; width: 34px; height: 34px; align-items: center; justify-content: center; border: 1px solid rgba(255,255,255,.45); border-radius: 50%; background: rgba(20,21,27,.72); color: #fff; cursor: pointer; }.aksrt-widget-notice .aksrt-widget-swiper__pause { top: 7px; right: 4px; width: 30px; height: 30px; }
|
||||
|
||||
.aksrt-term-card { display: block; overflow: hidden; border-radius: var(--aksrt-widget-radius); background: var(--main-bg,#fff); box-shadow: 0 8px 28px rgba(31,35,48,.07); }.aksrt-term-card__media { display: block; aspect-ratio: 16/9; background: linear-gradient(135deg,#667eea,#764ba2); }.aksrt-term-card__media img { width: 100%; height: 100%; object-fit: cover; }.aksrt-term-card__body { display: flex; flex-direction: column; gap: 5px; padding: 14px; }.aksrt-term-card__body small { min-height: 38px; color: var(--muted-color,#8a8c98); }.aksrt-term-card__body em { font-size: 12px; color: var(--focus-color,#ff5473); font-style: normal; }
|
||||
.aksrt-term-aggregation { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 16px; }.aksrt-term-aggregation__card { padding: 16px; border-radius: var(--aksrt-widget-radius); background: var(--main-bg,#fff); box-shadow: 0 8px 28px rgba(31,35,48,.07); }.aksrt-term-aggregation header a { display: flex; justify-content: space-between; }.aksrt-term-aggregation header span { color: var(--muted-color,#8a8c98); font-size: 13px; }.aksrt-term-aggregation ul { display: grid; gap: 10px; margin: 14px 0 0; padding: 0; list-style: none; }.aksrt-term-aggregation li a { display: flex; align-items: center; gap: 10px; }.aksrt-term-aggregation li img { width: 55px; height: 42px; border-radius: 6px; object-fit: cover; }
|
||||
.aksrt-term-card { display: block; overflow: hidden; border-radius: var(--aksrt-widget-radius); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }.aksrt-term-card__media { display: block; aspect-ratio: 16/9; background: var(--muted-border-color,#eee); }.aksrt-term-card__media img { width: 100%; height: 100%; object-fit: cover; }.aksrt-term-card__body { display: flex; flex-direction: column; gap: 5px; padding: 14px; }.aksrt-term-card__body small { min-height: 38px; color: var(--muted-color,#8a8c98); }.aksrt-term-card__body em { font-size: 12px; color: var(--focus-color,#ff5473); font-style: normal; }
|
||||
.aksrt-term-aggregation { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 15px; }.aksrt-term-aggregation__card { padding: 15px; border-radius: var(--aksrt-widget-radius); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }.aksrt-term-aggregation header a { display: flex; justify-content: space-between; }.aksrt-term-aggregation header span { color: var(--muted-color,#8a8c98); font-size: 13px; }.aksrt-term-aggregation ul { display: grid; gap: 10px; margin: 14px 0 0; padding: 0; list-style: none; }.aksrt-term-aggregation li a { display: flex; align-items: center; gap: 10px; }.aksrt-term-aggregation li img { width: 55px; height: 42px; border-radius: 6px; object-fit: cover; }
|
||||
.aksrt-widget-items { position: relative; min-height: 40px; }.aksrt-widget-items.is-loading { opacity: .72; pointer-events: none; }.aksrt-widget-skeleton { height: 180px; border-radius: 10px; background: linear-gradient(90deg,rgba(127,127,127,.08),rgba(127,127,127,.17),rgba(127,127,127,.08)); background-size: 200% 100%; animation: aksrt-shimmer 1.2s infinite; }.aksrt-widget-load-more { display: block; min-width: 120px; margin: 18px auto 0; padding: 9px 18px; border: 0; border-radius: 999px; background: rgba(127,127,127,.12); color: inherit; cursor: pointer; }.aksrt-widget-pagination { display:flex;justify-content:center;gap:6px;margin-top:18px; }.aksrt-widget-pagination button { min-width:34px;height:34px;border:0;border-radius:8px;background:rgba(127,127,127,.1);color:inherit;cursor:pointer; }.aksrt-widget-pagination button[aria-current="page"] { background:var(--focus-color,#ff5473);color:#fff; }@keyframes aksrt-shimmer { to { background-position: -200% 0; } }
|
||||
.aksrt-widget-oneline .swiper-slide { width: min(300px,78vw); }.aksrt-widget-tabs__nav { display: flex; gap: 8px; margin-bottom: 15px; overflow-x: auto; }.aksrt-widget-tabs__nav button { flex: none; padding: 7px 14px; border: 0; border-radius: 999px; background: rgba(127,127,127,.1); color: inherit; cursor: pointer; }.aksrt-widget-tabs__nav button[aria-selected="true"] { background: var(--focus-color,#ff5473); color: #fff; }
|
||||
.aksrt-post-pay { padding: 20px; border-radius: var(--aksrt-widget-radius); text-align: center; }.aksrt-post-pay.is-authorized { background: rgba(34,197,94,.1); }.aksrt-post-pay > i { display: block; margin-bottom: 9px; color: #22c55e; font-size: 30px; }.aksrt-post-pay p { color: var(--muted-color,#8a8c98); }
|
||||
|
||||
.aksrt-pricing-card { position: relative; display: flex; flex-direction: column; padding: 25px; border: 1px solid rgba(127,127,127,.13); border-radius: 16px; background: var(--main-bg,#fff); box-shadow: 0 10px 35px rgba(31,35,48,.06); }.aksrt-pricing-card.is-featured { border-color: var(--focus-color,#ff5473); transform: translateY(-8px); }.aksrt-pricing-card__badge { position: absolute; top: 12px; right: 12px; padding: 4px 9px; border-radius: 999px; background: var(--focus-color,#ff5473); color: #fff; font-size: 12px; }.aksrt-pricing-card h3 { margin: 0 0 12px; }.aksrt-pricing-card__price { font-size: 34px; font-weight: 800; }.aksrt-pricing-card p { color: var(--muted-color,#8a8c98); }.aksrt-pricing-card ul { flex: 1; display: grid; gap: 9px; margin: 15px 0 22px; padding: 0; list-style: none; }.aksrt-pricing-card li i { margin-right: 8px; color: #22c55e; }
|
||||
.aksrt-reviews .swiper-slide { width: min(390px,86vw); height: auto; }.aksrt-review-card { height: 100%; padding: 22px; border-radius: 14px; background: var(--main-bg,#fff); box-shadow: 0 8px 28px rgba(31,35,48,.07); }.aksrt-review-card__rating { color: #f6b73c; }.aksrt-review-card blockquote { min-height: 80px; margin: 15px 0; line-height: 1.75; }.aksrt-review-card footer { display: flex; align-items: center; gap: 11px; }.aksrt-review-card__avatar { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; }.aksrt-review-card footer span { display: flex; flex-direction: column; }.aksrt-review-card footer small { color: var(--muted-color,#8a8c98); }
|
||||
.aksrt-pricing-card { position: relative; display: flex; flex-direction: column; padding: 20px; border: 1px solid var(--main-border-color,rgba(127,127,127,.13)); border-radius: var(--main-radius,8px); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }.aksrt-pricing-card.is-featured { border-color: var(--focus-color,#ff5473); }.aksrt-pricing-card__badge { position: absolute; top: 12px; right: 12px; padding: 4px 9px; border-radius: 999px; background: var(--focus-color,#ff5473); color: #fff; font-size: 12px; }.aksrt-pricing-card h3 { margin: 0 0 12px; }.aksrt-pricing-card__price { font-size: 34px; font-weight: 800; }.aksrt-pricing-card p { color: var(--muted-color,#8a8c98); }.aksrt-pricing-card ul { flex: 1; display: grid; gap: 9px; margin: 15px 0 22px; padding: 0; list-style: none; }.aksrt-pricing-card li i { margin-right: 8px; color: #22c55e; }
|
||||
.aksrt-reviews .swiper-slide { width: min(390px,86vw); height: auto; }.aksrt-review-card { height: 100%; padding: 20px; border-radius: var(--main-radius,8px); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }.aksrt-review-card__rating { color: #f6b73c; }.aksrt-review-card blockquote { min-height: 80px; margin: 15px 0; line-height: 1.75; }.aksrt-review-card footer { display: flex; align-items: center; gap: 11px; }.aksrt-review-card__avatar { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; }.aksrt-review-card footer span { display: flex; flex-direction: column; }.aksrt-review-card footer small { color: var(--muted-color,#8a8c98); }
|
||||
.aksrt-faq { max-width: 880px; margin: auto; }.aksrt-faq__search { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; padding: 10px 14px; border: 1px solid rgba(127,127,127,.16); border-radius: 10px; }.aksrt-faq__search input { width: 100%; border: 0; outline: 0; background: transparent; color: inherit; }.aksrt-faq__items { display: grid; gap: 10px; }.aksrt-faq__item { overflow: hidden; border: 1px solid rgba(127,127,127,.13); border-radius: 10px; background: var(--main-bg,#fff); }.aksrt-faq__item > button { display: flex; width: 100%; justify-content: space-between; padding: 15px 17px; border: 0; background: transparent; color: inherit; text-align: left; cursor: pointer; }.aksrt-faq__item > div { padding: 0 17px 17px; color: var(--muted-color,#8a8c98); line-height: 1.75; }
|
||||
.aksrt-stat { padding: 22px; text-align: center; }.aksrt-stat > strong { display: block; color: var(--focus-color,#ff5473); font-size: clamp(32px,5vw,52px); }.aksrt-stat h3 { margin: 5px 0; }.aksrt-stat p { margin: 0; color: var(--muted-color,#8a8c98); }.aksrt-highlight--large { grid-column: span 2; }.aksrt-tilt-case { transform-style: preserve-3d; }.aksrt-tilt-case .aksrt-media-card__body { transform: translateZ(20px); }
|
||||
.aksrt-feature-intro__tabs { display: flex; justify-content: center; gap: 8px; margin-bottom: 18px; }.aksrt-feature-intro__tabs button { padding: 7px 14px; border: 0; border-radius: 999px; background: rgba(127,127,127,.1); color: inherit; cursor: pointer; }.aksrt-feature-intro__tabs button[aria-selected="true"] { background: var(--focus-color,#ff5473); color: #fff; }.aksrt-feature-intro__panels article { display: grid; grid-template-columns: 1.15fr 1fr; align-items: center; gap: 34px; }.aksrt-feature-intro__copy p, .aksrt-feature-intro__copy li { color: var(--muted-color,#8a8c98); line-height: 1.7; }
|
||||
|
||||
.aksrt-slider { height: var(--aksrt-slider-height); overflow: hidden; border-radius: var(--aksrt-widget-radius); background: #111; }.aksrt-slider .swiper-slide, .aksrt-slider .swiper-slide > a { position: relative; display: block; width: 100%; height: 100%; overflow: hidden; }.aksrt-slider .swiper-slide > img, .aksrt-slider .swiper-slide > video, .aksrt-slider .swiper-slide > a > img, .aksrt-slider .swiper-slide > a > video { width: 100%; height: 100%; object-fit: cover; }.aksrt-slide-copy { position: absolute; z-index: 2; right: 8%; bottom: 12%; left: 8%; color: #fff; text-shadow: 0 2px 12px rgba(0,0,0,.4); }.aksrt-slide-copy.is-center { top: 50%; bottom: auto; text-align: center; transform: translateY(-50%); }.aksrt-slide-copy.is-right-bottom { text-align: right; }.aksrt-slide-copy h3 { margin: 0; font-size: clamp(25px,4vw,44px); }.aksrt-slide-copy p { margin: 8px 0 0; }.aksrt-slide-layer { position: absolute; z-index: 1; inset: 0; width: 100%; height: 100%; object-fit: contain; pointer-events: none; }.aksrt-slider-combo { display: grid; grid-template-columns: minmax(0,2fr) minmax(240px,1fr); gap: 15px; }.aksrt-slider-combo__side { display: grid; gap: 15px; }.aksrt-slider-combo__side .aksrt-media-card { min-height: 0; }.aksrt-slider-combo__side .aksrt-media-card__media { aspect-ratio: 2/1; }
|
||||
|
||||
.aksrt-user-card { position: relative; overflow: hidden; border-radius: 14px; background: var(--main-bg,#fff); box-shadow: 0 9px 30px rgba(31,35,48,.07); text-align: center; }.aksrt-user-card__cover { height: 105px; background: linear-gradient(135deg,#667eea,#764ba2); background-position: center; background-size: cover; }.aksrt-user-card__body { position: relative; padding: 18px; }.aksrt-user-card__avatar { display: inline-block; margin-top: -62px; padding: 4px; border-radius: 50%; background: var(--main-bg,#fff); }.aksrt-user-card__avatar img { display: block; width: 84px; height: 84px; border-radius: 50%; object-fit: cover; }.aksrt-user-card.is-guest .aksrt-user-card__avatar { margin-top: -58px; }.aksrt-user-card h3 { margin: 7px 0; }.aksrt-user-card__identity { margin: 6px 0; }.aksrt-user-card__body > p { color: var(--muted-color,#8a8c98); }.aksrt-user-card__stats { display: flex; justify-content: center; gap: 24px; margin: 16px 0; }.aksrt-user-card__stats span { display: flex; flex-direction: column; font-size: 12px; color: var(--muted-color,#8a8c98); }.aksrt-user-card__stats b { color: inherit; font-size: 17px; }.aksrt-user-card__actions { display: flex; justify-content: center; gap: 9px; }.aksrt-user-card__follow { position: absolute; top: 14px; right: 14px; padding: 5px 12px; border: 0; border-radius: 999px; background: var(--focus-color,#ff5473); color: #fff; cursor: pointer; }.aksrt-user-card__follow.is-following { background: rgba(0,0,0,.5); }.aksrt-user-card__posts { padding: 0 16px 16px; text-align: left; }
|
||||
.aksrt-user-card { position: relative; overflow: hidden; border-radius: var(--main-radius,8px); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); text-align: center; }.aksrt-user-card__cover { height: 105px; background: var(--muted-border-color,#eee); background-position: center; background-size: cover; }.aksrt-user-card__body { position: relative; padding: 15px; }.aksrt-user-card__avatar { display: inline-block; margin-top: -62px; padding: 4px; border-radius: 50%; background: var(--main-bg-color,#fff); }.aksrt-user-card__avatar img { display: block; width: 84px; height: 84px; border-radius: 50%; object-fit: cover; }.aksrt-user-card.is-guest .aksrt-user-card__avatar { margin-top: -58px; }.aksrt-user-card h3 { margin: 7px 0; }.aksrt-user-card__identity { margin: 6px 0; }.aksrt-user-card__body > p { color: var(--muted-color,#8a8c98); }.aksrt-user-card__stats { display: flex; justify-content: center; gap: 24px; margin: 16px 0; }.aksrt-user-card__stats span { display: flex; flex-direction: column; font-size: 12px; color: var(--muted-color,#8a8c98); }.aksrt-user-card__stats b { color: inherit; font-size: 17px; }.aksrt-user-card__actions { display: flex; justify-content: center; gap: 9px; }.aksrt-user-card__follow { position: absolute; top: 14px; right: 14px; padding: 5px 12px; border: 0; border-radius: 999px; background: var(--focus-color,#ff5473); color: #fff; cursor: pointer; }.aksrt-user-card__follow.is-following { background: rgba(0,0,0,.5); }.aksrt-user-card__posts { padding: 0 16px 16px; text-align: left; }
|
||||
.aksrt-user-ranking { display: grid; gap: 11px; margin: 0; padding: 0; list-style: none; }.aksrt-user-ranking li { display: flex; align-items: center; gap: 9px; }.aksrt-user-ranking__index { width: 24px; text-align: center; color: var(--muted-color,#8a8c98); font-weight: 700; }.aksrt-user-ranking__index.is-top { color: #f59e0b; }.aksrt-user-tile { display: flex; min-width: 0; flex: 1; align-items: center; gap: 10px; }.aksrt-user-tile img { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; }.aksrt-user-tile span { display: flex; min-width: 0; flex-direction: column; }.aksrt-user-tile strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.aksrt-user-tile small { color: var(--muted-color,#8a8c98); }.aksrt-user-list { --aksrt-cols: 4; }.aksrt-user-list article { padding: 14px; border-radius: 10px; background: var(--main-bg,#fff); box-shadow: 0 6px 20px rgba(31,35,48,.06); }
|
||||
.aksrt-user-card.no-cover .aksrt-user-card__avatar { margin-top: 0; }.aksrt-user-card__oauth { margin-top: 14px; }
|
||||
|
||||
|
||||
@@ -418,13 +418,11 @@ if (!function_exists('aksrt_archive_open_shell')) :
|
||||
$main_class .= ' container';
|
||||
}
|
||||
$main_class = trim((string) preg_replace('/\bcontent-shell(?:--[a-z0-9-]+)?\b/i', '', $main_class));
|
||||
?>
|
||||
<main id="main-content" class="<?php echo esc_attr($main_class); ?>" tabindex="-1">
|
||||
<?php
|
||||
if (!empty($args['top_fluid']) && is_array($args['top_fluid'])) {
|
||||
aksrt_archive_render_widget_zone($args['top_fluid'], 'fluid-widget-wrap');
|
||||
}
|
||||
?>
|
||||
<main id="main-content" class="<?php echo esc_attr($main_class); ?>" tabindex="-1">
|
||||
<div class="content-wrap">
|
||||
<div class="content-layout">
|
||||
<?php
|
||||
@@ -449,12 +447,10 @@ if (!function_exists('aksrt_archive_close_shell')) :
|
||||
<?php if (!empty($args['has_sidebar'])) : ?>
|
||||
<?php get_sidebar(); ?>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
<?php
|
||||
if (!empty($args['bottom_fluid']) && is_array($args['bottom_fluid'])) {
|
||||
aksrt_archive_render_widget_zone($args['bottom_fluid'], 'fluid-widget-wrap');
|
||||
}
|
||||
?>
|
||||
</main>
|
||||
<?php
|
||||
}
|
||||
endif;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -66,7 +66,7 @@ if (!function_exists('aksrt_single_render_aside')) :
|
||||
|| is_active_sidebar('single_sidebar')
|
||||
|| is_active_sidebar('all_sidebar_bottom');
|
||||
?>
|
||||
<div class="aksrt-article-sidebar" role="complementary" aria-label="<?php esc_attr_e('侧栏', 'aksrt'); ?>">
|
||||
<div class="sidebar aksrt-article-sidebar" role="complementary" aria-label="<?php esc_attr_e('侧栏', 'aksrt'); ?>">
|
||||
<?php if (!empty($ctx['has_download'])) : ?>
|
||||
<?php get_template_part('templates/parts/resource-meta-box'); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -103,15 +103,12 @@ if (!function_exists('aksrt_single_build_context')) :
|
||||
$layout = function_exists('aksrt_get_post_show_layout')
|
||||
? aksrt_get_post_show_layout($post_id)
|
||||
: '';
|
||||
$has_article_widgets = is_active_sidebar('all_sidebar_top')
|
||||
|| is_active_sidebar('single_sidebar')
|
||||
|| is_active_sidebar('all_sidebar_bottom');
|
||||
// Do not use wp_is_mobile() as a layout gate. It is User-Agent based and
|
||||
// can disagree with the actual viewport (for example, desktop-mode mobile
|
||||
// browsers). The responsive stylesheet owns narrow-screen hiding.
|
||||
$show_aside = get_post_type($post_id) === 'post'
|
||||
&& $layout !== 'no_sidebar'
|
||||
&& $has_article_widgets;
|
||||
&& (!function_exists('aksrt_is_show_sidebar') || aksrt_is_show_sidebar());
|
||||
|
||||
return [
|
||||
'post_id' => $post_id,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,386 @@
|
||||
<?php
|
||||
/**
|
||||
* Shared post renderer helpers for archive, widget, and related lists.
|
||||
*
|
||||
* Centralizes card/list/mini rendering to avoid duplicating DOM contracts
|
||||
* across category.php, search.php, widgets, and home modules.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_renderer_defaults')) {
|
||||
/**
|
||||
* Base renderer config defaults.
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
function aksrt_renderer_defaults(): array {
|
||||
return [
|
||||
'context' => 'archive',
|
||||
'mode' => 'list',
|
||||
'columns' => 1,
|
||||
'style' => 'default',
|
||||
'card_style' => 'standard',
|
||||
'heading_bold' => true,
|
||||
'show_excerpt' => true,
|
||||
'excerpt_words' => 36,
|
||||
'show_meta' => true,
|
||||
'show_author' => true,
|
||||
'show_date' => true,
|
||||
'show_views' => true,
|
||||
'show_comments' => true,
|
||||
'show_likes' => false,
|
||||
'show_badges' => true,
|
||||
'show_tags' => true,
|
||||
'tag_limit' => 3,
|
||||
'category_limit' => 1,
|
||||
'show_thumb' => true,
|
||||
'thumb_ratio' => '4:3',
|
||||
'thumb_fit' => 'cover',
|
||||
'thumb_lazy' => true,
|
||||
'thumb_if_has' => false,
|
||||
'target_blank' => false,
|
||||
'title_html' => '',
|
||||
'excerpt_html' => '',
|
||||
'badge_show' => true,
|
||||
'show_resource_badge' => true,
|
||||
'multi_thumb_enabled' => true,
|
||||
'multi_thumb_limit' => 4,
|
||||
'multi_thumb_min' => 2,
|
||||
'list_show_type' => 'separate',
|
||||
'list_img_position' => 'left',
|
||||
'number_offset' => 0,
|
||||
'show_number' => false,
|
||||
'extra_class' => '',
|
||||
'highlight_query' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_config')) {
|
||||
/**
|
||||
* Merge renderer config with context-aware defaults.
|
||||
*
|
||||
* @param array<string,mixed> $config User config.
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
function aksrt_renderer_config(array $config = []): array {
|
||||
$cfg = wp_parse_args($config, aksrt_renderer_defaults());
|
||||
|
||||
$cfg['mode'] = in_array($cfg['mode'], ['list', 'card', 'mini', 'grid-cms'], true)
|
||||
? $cfg['mode']
|
||||
: 'list';
|
||||
|
||||
$cfg['columns'] = max(1, min(6, (int) $cfg['columns']));
|
||||
$cfg['excerpt_words'] = max(0, (int) $cfg['excerpt_words']);
|
||||
$cfg['tag_limit'] = max(0, (int) $cfg['tag_limit']);
|
||||
$cfg['category_limit'] = max(0, (int) $cfg['category_limit']);
|
||||
$cfg['multi_thumb_limit'] = max(1, min(6, (int) $cfg['multi_thumb_limit']));
|
||||
$cfg['multi_thumb_min'] = max(1, min(6, (int) $cfg['multi_thumb_min']));
|
||||
|
||||
if ($cfg['mode'] === 'mini') {
|
||||
$cfg['show_excerpt'] = false;
|
||||
$cfg['show_badges'] = false;
|
||||
$cfg['show_tags'] = false;
|
||||
$cfg['show_author'] = false;
|
||||
$cfg['show_comments'] = false;
|
||||
}
|
||||
|
||||
if (in_array((string) $cfg['style'], ['default', 'null'], true)) {
|
||||
$cfg['style'] = '';
|
||||
}
|
||||
$cfg['list_img_position'] = in_array(sanitize_key((string) ($cfg['list_img_position'] ?? 'left')), ['left', 'right'], true)
|
||||
? sanitize_key((string) ($cfg['list_img_position'] ?? 'left'))
|
||||
: 'left';
|
||||
$cfg['list_show_type'] = in_array(sanitize_key((string) ($cfg['list_show_type'] ?? 'separate')), ['separate', 'no_margin'], true)
|
||||
? sanitize_key((string) ($cfg['list_show_type'] ?? 'separate'))
|
||||
: 'separate';
|
||||
|
||||
return $cfg;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_sanitize_inline_html')) {
|
||||
/**
|
||||
* Sanitize small inline HTML snippets used by render config overrides.
|
||||
*
|
||||
* @param string $html Raw HTML.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_sanitize_inline_html(string $html): string {
|
||||
return wp_kses($html, [
|
||||
'span' => ['class' => true, 'aria-hidden' => true],
|
||||
'em' => ['class' => true],
|
||||
'strong' => ['class' => true],
|
||||
'i' => ['class' => true, 'aria-hidden' => true],
|
||||
'small'=> ['class' => true],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_highlight_text')) {
|
||||
/**
|
||||
* Highlight plain text query matches using a safe span wrapper.
|
||||
*
|
||||
* @param string $text Plain text.
|
||||
* @param string $query Highlight query.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_highlight_text(string $text, string $query): string {
|
||||
$text = wp_strip_all_tags($text);
|
||||
$query = trim($query);
|
||||
|
||||
if ($query === '' || $text === '') {
|
||||
return esc_html($text);
|
||||
}
|
||||
|
||||
$words = preg_split('/\s+/u', $query) ?: [];
|
||||
$words = array_values(array_unique(array_filter(array_map('trim', $words), static function ($word) {
|
||||
return $word !== '' && mb_strlen($word) >= 2;
|
||||
})));
|
||||
|
||||
if (empty($words)) {
|
||||
return esc_html($text);
|
||||
}
|
||||
|
||||
$pattern = '/' . implode('|', array_map(static function ($word) {
|
||||
return preg_quote($word, '/');
|
||||
}, $words)) . '/iu';
|
||||
|
||||
$escaped = esc_html($text);
|
||||
return (string) preg_replace($pattern, '<span class="focus-color">$0</span>', $escaped);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_title_markup')) {
|
||||
/**
|
||||
* Resolve title markup, supporting highlighted search terms.
|
||||
*
|
||||
* @param array<string,mixed> $data Collected post data.
|
||||
* @param array<string,mixed> $cfg Renderer config.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_title_markup(array $data, array $cfg): string {
|
||||
if (!empty($cfg['title_html'])) {
|
||||
return aksrt_renderer_sanitize_inline_html((string) $cfg['title_html']);
|
||||
}
|
||||
|
||||
$title = aksrt_renderer_highlight_text((string) $data['title'], (string) $cfg['highlight_query']);
|
||||
$subtitle = trim((string) ($data['subtitle'] ?? ''));
|
||||
if ($subtitle !== '') {
|
||||
$title .= '<span class="focus-color"> ' . esc_html($subtitle) . '</span>';
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_excerpt_markup')) {
|
||||
/**
|
||||
* Resolve excerpt markup, supporting highlighted search terms.
|
||||
*
|
||||
* @param array<string,mixed> $data Collected post data.
|
||||
* @param array<string,mixed> $cfg Renderer config.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_excerpt_markup(array $data, array $cfg): string {
|
||||
if (!empty($cfg['excerpt_html'])) {
|
||||
return aksrt_renderer_sanitize_inline_html((string) $cfg['excerpt_html']);
|
||||
}
|
||||
|
||||
return aksrt_renderer_highlight_text((string) $data['excerpt'], (string) $cfg['highlight_query']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_curated_images')) {
|
||||
/**
|
||||
* Resolve curated cover images from post meta.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param int $limit Maximum items.
|
||||
* @return string[]
|
||||
*/
|
||||
function aksrt_renderer_curated_images(int $post_id, int $limit = 6): array {
|
||||
$images = [];
|
||||
$limit = max(1, min(8, $limit));
|
||||
|
||||
if (!class_exists('\AKSRT\Core\PostMetaBox')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$cover_images = (string) \AKSRT\Core\PostMetaBox::get_meta_value($post_id, 'cover_images', '');
|
||||
if ($cover_images === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$cover_ids = array_filter(array_map('absint', preg_split('/[\s,]+/', $cover_images) ?: []));
|
||||
foreach ($cover_ids as $attachment_id) {
|
||||
$url = (string) wp_get_attachment_image_url($attachment_id, 'medium_large');
|
||||
if ($url !== '') {
|
||||
$images[] = esc_url_raw($url);
|
||||
}
|
||||
}
|
||||
|
||||
return array_slice(array_values(array_unique($images)), 0, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_attachment_images')) {
|
||||
/**
|
||||
* Resolve gallery/content images for list cards.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param int $limit Maximum items.
|
||||
* @return string[]
|
||||
*/
|
||||
function aksrt_renderer_attachment_images(int $post_id, int $limit = 6): array {
|
||||
$images = [];
|
||||
$limit = max(1, min(8, $limit));
|
||||
|
||||
if (function_exists('aksrt_get_post_gallery_images')) {
|
||||
$images = aksrt_get_post_gallery_images($post_id, $limit);
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_get_first_content_image_url')) {
|
||||
$first_content_image = (string) aksrt_get_first_content_image_url($post_id);
|
||||
if ($first_content_image !== '') {
|
||||
$images[] = esc_url_raw($first_content_image);
|
||||
}
|
||||
}
|
||||
|
||||
return array_slice(array_values(array_unique(array_filter($images))), 0, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_random_fallback_image')) {
|
||||
/**
|
||||
* Pick a deterministic fallback image when "random cover" is enabled.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_random_fallback_image(int $post_id): string {
|
||||
$candidates = [
|
||||
get_template_directory_uri() . '/assets/img/person_background.png',
|
||||
get_template_directory_uri() . '/assets/img/placeholder.svg',
|
||||
];
|
||||
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$thumb = \AKSRT\Settings\Settings::get_post_thumb_settings();
|
||||
$spare = $thumb['spare_images'] ?? [];
|
||||
if (is_array($spare) && !empty($spare)) {
|
||||
foreach ($spare as $url) {
|
||||
if (is_string($url) && $url !== '') {
|
||||
array_unshift($candidates, $url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$candidates = array_values(array_unique(array_filter(array_map('esc_url_raw', $candidates))));
|
||||
if (empty($candidates)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$index = $post_id % count($candidates);
|
||||
return $candidates[$index] ?? $candidates[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_resolve_images')) {
|
||||
/**
|
||||
* Resolve primary + gallery images for a post based on card settings.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param array<string,mixed> $cfg Renderer config.
|
||||
* @return array{primary:string,gallery:string[],has_thumb:bool,has_real:bool}
|
||||
*/
|
||||
function aksrt_renderer_resolve_images(int $post_id, array $cfg): array {
|
||||
$thumb_url = get_the_post_thumbnail_url($post_id, 'medium_large');
|
||||
$curated_images = !empty($cfg['thumb_if_has']) || !empty($cfg['multi_thumb_enabled'])
|
||||
? aksrt_renderer_curated_images($post_id, max(4, (int) $cfg['multi_thumb_limit']))
|
||||
: [];
|
||||
$content_images = !empty($cfg['multi_thumb_enabled'])
|
||||
? aksrt_renderer_attachment_images($post_id, max(4, (int) $cfg['multi_thumb_limit']))
|
||||
: [];
|
||||
|
||||
$gallery = [];
|
||||
if (!empty($curated_images)) {
|
||||
$gallery = array_merge($gallery, $curated_images);
|
||||
}
|
||||
if (!empty($content_images)) {
|
||||
$gallery = array_merge($gallery, $content_images);
|
||||
}
|
||||
$gallery = array_values(array_unique(array_filter($gallery)));
|
||||
|
||||
$primary = is_string($thumb_url) && $thumb_url !== '' ? esc_url_raw($thumb_url) : '';
|
||||
$has_real = $primary !== '';
|
||||
|
||||
if ($primary === '' && !empty($curated_images[0])) {
|
||||
$primary = $curated_images[0];
|
||||
$has_real = true;
|
||||
}
|
||||
if ($primary === '' && !empty($content_images[0])) {
|
||||
$primary = $content_images[0];
|
||||
$has_real = true;
|
||||
}
|
||||
|
||||
$thumb_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_thumb_settings() : [];
|
||||
$allow_fallback = !empty($thumb_settings['rand_thumb']) || !empty($cfg['thumb_if_has']);
|
||||
if ($primary === '' && $allow_fallback) {
|
||||
$primary = aksrt_renderer_random_fallback_image($post_id);
|
||||
}
|
||||
|
||||
return [
|
||||
'primary' => $primary,
|
||||
'gallery' => $gallery,
|
||||
'has_thumb' => $primary !== '',
|
||||
'has_real' => $has_real,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_lazyload_config')) {
|
||||
/**
|
||||
* Small lazyload config subset for post cards.
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
function aksrt_renderer_lazyload_config(): array {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$lazy = \AKSRT\Settings\Settings::get_lazyload_config();
|
||||
return [
|
||||
'enabled' => !empty($lazy['enabled']) && !empty($lazy['posts_thumb_enabled']),
|
||||
'placeholder_url' => (string) ($lazy['placeholder_url'] ?? ''),
|
||||
'blur' => !empty($lazy['blur']),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'enabled' => false,
|
||||
'placeholder_url' => '',
|
||||
'blur' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_thumb_ratio_value')) {
|
||||
/**
|
||||
* Convert ratio tokens like 4:3 to CSS aspect ratio values.
|
||||
*
|
||||
* @param string $ratio Ratio token.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_thumb_ratio_value(string $ratio): string {
|
||||
$ratio = trim($ratio);
|
||||
if (preg_match('/^(\d+)\s*:\s*(\d+)$/', $ratio, $matches)) {
|
||||
$w = max(1, (int) $matches[1]);
|
||||
$h = max(1, (int) $matches[2]);
|
||||
return $w . ' / ' . $h;
|
||||
}
|
||||
|
||||
return '4 / 3';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
<?php
|
||||
/**
|
||||
* Shared post renderer helpers.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_renderer_list')) {
|
||||
/**
|
||||
* Render a horizontal list card.
|
||||
*
|
||||
* @param array<string,mixed> $data Collected post data.
|
||||
* @param array<string,mixed> $cfg Renderer config.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_list(array $data, array $cfg): string {
|
||||
$classes = ['posts-item', 'list', 'ajax-item', 'flex', 'posts-item--' . ($cfg['card_style'] ?: 'standard')];
|
||||
|
||||
if ($cfg['style'] !== '' && $cfg['style'] !== 'default') {
|
||||
$classes[] = $cfg['style'];
|
||||
}
|
||||
if ($cfg['style'] === 'style2') {
|
||||
$classes[] = 'xx';
|
||||
}
|
||||
if (!empty($data['is_mult_thumb'])) {
|
||||
$classes[] = 'mult-thumb';
|
||||
}
|
||||
if ($cfg['context'] === 'home') {
|
||||
$classes[] = 'posts-item--home-list';
|
||||
}
|
||||
if (empty($data['has_img'])) {
|
||||
$classes[] = 'no-thumb';
|
||||
}
|
||||
if (($cfg['list_show_type'] ?? 'separate') === 'no_margin') {
|
||||
$classes[] = 'no-margin';
|
||||
$classes[] = 'no_margin';
|
||||
}
|
||||
|
||||
$thumb = aksrt_renderer_thumb($data, $cfg);
|
||||
$mult = aksrt_renderer_mult_thumb($data);
|
||||
if (!empty($data['is_mult_thumb'])) {
|
||||
// Zibll's multi-image mode replaces the primary graphic; both must
|
||||
// never be emitted for the same list item.
|
||||
$thumb = '';
|
||||
}
|
||||
|
||||
if ($cfg['card_style'] === 'text' || ($cfg['card_style'] === 'thumb_if_has' && empty($data['has_img']))) {
|
||||
$thumb = '';
|
||||
$mult = '';
|
||||
$classes[] = 'no-thumb';
|
||||
$classes[] = 'posts-item--text';
|
||||
}
|
||||
$title_markup = aksrt_renderer_title_markup($data, $cfg);
|
||||
$excerpt_markup = aksrt_renderer_excerpt_markup($data, $cfg);
|
||||
$badges = aksrt_renderer_badges($data, $cfg);
|
||||
$meta = aksrt_renderer_meta($data, $cfg);
|
||||
|
||||
$target_attr = !empty($cfg['target_blank']) ? ' target="_blank"' : '';
|
||||
$heading_cls = !empty($cfg['heading_bold']) ? 'item-heading item-heading--bold' : 'item-heading';
|
||||
$title_html = '<h2 class="' . $heading_cls . '"><a href="' . esc_url($data['permalink']) . '" rel="bookmark"' . $target_attr . '>' . $title_markup . '</a></h2>';
|
||||
$excerpt_html = $cfg['show_excerpt'] && $excerpt_markup !== ''
|
||||
? '<p class="item-excerpt">' . $excerpt_markup . '</p>'
|
||||
: '';
|
||||
|
||||
$html = '<article ' . aksrt_renderer_article_attrs($classes, $data) . '>';
|
||||
|
||||
if ($cfg['style'] === 'style2') {
|
||||
$html .= $title_html;
|
||||
$html .= '<div class="flex">';
|
||||
$html .= $thumb;
|
||||
$html .= '<div class="item-body flex xx flex1 jsb">';
|
||||
$html .= $mult ?: $excerpt_html;
|
||||
$html .= $badges;
|
||||
$html .= $meta;
|
||||
$html .= '</div></div>';
|
||||
} else {
|
||||
$html .= $thumb;
|
||||
$html .= '<div class="item-body flex xx flex1 jsb">';
|
||||
$html .= $title_html;
|
||||
$html .= $mult ?: $excerpt_html;
|
||||
$html .= '<div>' . $badges . $meta . '</div>';
|
||||
$html .= '</div>';
|
||||
}
|
||||
|
||||
$html .= '</article>';
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_card')) {
|
||||
/**
|
||||
* Render a vertical card or CMS resource card.
|
||||
*
|
||||
* @param array<string,mixed> $data Collected post data.
|
||||
* @param array<string,mixed> $cfg Renderer config.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_card(array $data, array $cfg): string {
|
||||
$classes = ['posts-item', 'card', 'ajax-item', 'posts-item--' . ($cfg['card_style'] ?: 'standard')];
|
||||
|
||||
if ($cfg['style'] !== '' && $cfg['style'] !== 'default') {
|
||||
$classes[] = $cfg['style'];
|
||||
}
|
||||
if ($cfg['context'] === 'home') {
|
||||
$classes[] = 'posts-item--home';
|
||||
}
|
||||
if ($cfg['mode'] === 'grid-cms') {
|
||||
$classes[] = 'posts-item--cms';
|
||||
}
|
||||
if (!empty($data['is_mult_thumb'])) {
|
||||
$classes[] = 'mult-thumb';
|
||||
}
|
||||
if (empty($data['has_img'])) {
|
||||
$classes[] = 'no-thumb';
|
||||
}
|
||||
|
||||
$thumb = aksrt_renderer_thumb($data, $cfg);
|
||||
$badges = aksrt_renderer_badges($data, $cfg);
|
||||
$resource_strip = aksrt_renderer_resource_strip($data);
|
||||
$meta = aksrt_renderer_meta($data, $cfg);
|
||||
$title_markup = aksrt_renderer_title_markup($data, $cfg);
|
||||
$html = '<article ' . aksrt_renderer_article_attrs($classes, $data) . '>';
|
||||
$html .= $thumb;
|
||||
$html .= '<div class="item-body">';
|
||||
$target_attr = !empty($cfg['target_blank']) ? ' target="_blank"' : '';
|
||||
$heading_cls = !empty($cfg['heading_bold']) ? 'item-heading item-heading--bold' : 'item-heading';
|
||||
$html .= '<h2 class="' . $heading_cls . '"><a href="' . esc_url($data['permalink']) . '" rel="bookmark"' . $target_attr . '>' . $title_markup . '</a></h2>';
|
||||
|
||||
$html .= $badges;
|
||||
|
||||
if ($cfg['mode'] === 'grid-cms') {
|
||||
$html .= $resource_strip;
|
||||
}
|
||||
|
||||
$html .= $meta;
|
||||
$html .= '</div></article>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_mini')) {
|
||||
/**
|
||||
* Render a compact mini card.
|
||||
*
|
||||
* @param array<string,mixed> $data Collected post data.
|
||||
* @param array<string,mixed> $cfg Renderer config.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_mini(array $data, array $cfg): string {
|
||||
$classes = ['posts-mini'];
|
||||
if (!empty($cfg['extra_class'])) {
|
||||
$classes[] = $cfg['extra_class'];
|
||||
}
|
||||
if (empty($data['has_img'])) {
|
||||
$classes[] = 'posts-mini--no-thumb';
|
||||
}
|
||||
|
||||
$number_html = '';
|
||||
if ($cfg['show_number'] && $cfg['number_offset'] > 0) {
|
||||
$badge_colors = ['c-red', 'c-yellow', 'c-orange', 'c-blue', 'c-green'];
|
||||
$index = $cfg['number_offset'] - 1;
|
||||
$color = $badge_colors[$index] ?? '';
|
||||
$number_html = '<span class="badg badg-sm mr3 ' . esc_attr($color) . '">' . (int) $cfg['number_offset'] . '</span>';
|
||||
}
|
||||
|
||||
$thumb_html = '';
|
||||
$target_attr = !empty($cfg['target_blank']) ? ' target="_blank"' : '';
|
||||
if ($cfg['show_thumb'] && !empty($data['has_img'])) {
|
||||
$lazy = $data['lazy'];
|
||||
$thumb_img = !empty($lazy['enabled']) && !empty($data['has_real_img'])
|
||||
? '<span class="aksrt-skeleton"></span><img class="' . (!empty($cfg['thumb_fit']) && $cfg['thumb_fit'] === 'contain' ? 'fit-contain' : 'fit-cover') . ' aksrt-lazy lazyload" data-src="' . esc_url($data['img_url']) . '" src="' . esc_url($lazy['placeholder_url']) . '" alt="' . esc_attr($data['title']) . '">'
|
||||
: '<img class="fit-cover" src="' . esc_url($data['img_url']) . '" alt="' . esc_attr($data['title']) . '"' . (!empty($data['has_real_img']) ? ' loading="lazy"' : '') . '>';
|
||||
|
||||
$thumb_html = '<a href="' . esc_url($data['permalink']) . '" class="item-thumbnail" aria-label="' . esc_attr($data['title']) . '"' . $target_attr . '>' . $thumb_img . '</a>';
|
||||
}
|
||||
|
||||
$heading_class = 'item-heading' . (!empty($cfg['heading_bold']) ? ' item-heading--bold' : '') . (!empty($data['has_img']) ? ' text-ellipsis-2' : ' text-ellipsis');
|
||||
$title_html = '<h2 class="' . esc_attr($heading_class) . '">' . $number_html . '<a href="' . esc_url($data['permalink']) . '"' . $target_attr . '>' . aksrt_renderer_title_markup($data, $cfg) . '</a></h2>';
|
||||
|
||||
$html = '<div class="' . esc_attr(implode(' ', array_unique($classes))) . '">';
|
||||
$html .= $thumb_html;
|
||||
$html .= '<div class="posts-mini-con flex xx flex1 jsb">';
|
||||
$html .= $title_html;
|
||||
$html .= aksrt_renderer_meta($data, $cfg);
|
||||
$html .= '</div></div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_render_post')) {
|
||||
/**
|
||||
* Render one post item inside a loop.
|
||||
*
|
||||
* @param array<string,mixed> $config Renderer config overrides.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_render_post(array $config = []): string {
|
||||
$cfg = aksrt_renderer_config($config);
|
||||
$data = aksrt_renderer_collect_post_data($cfg);
|
||||
|
||||
if (empty($data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
switch ($cfg['mode']) {
|
||||
case 'card':
|
||||
case 'grid-cms':
|
||||
return aksrt_renderer_card($data, $cfg);
|
||||
case 'mini':
|
||||
return aksrt_renderer_mini($data, $cfg);
|
||||
case 'list':
|
||||
default:
|
||||
return aksrt_renderer_list($data, $cfg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_render_post_list')) {
|
||||
/**
|
||||
* Render a query loop with the unified renderer.
|
||||
*
|
||||
* @param WP_Query|null $query Posts query. Falls back to global loop.
|
||||
* @param array<string,mixed> $config Renderer config overrides.
|
||||
* @param bool $echo Whether to echo output.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_render_post_list(?WP_Query $query = null, array $config = [], bool $echo = true): string {
|
||||
$cfg = aksrt_renderer_config($config);
|
||||
$wrapper_classes = ['posts-row', 'ajaxpager'];
|
||||
|
||||
if ($cfg['context'] === 'home') {
|
||||
$wrapper_classes[] = 'aksrt-homepage__posts';
|
||||
$wrapper_classes[] = 'post-grid';
|
||||
$wrapper_classes[] = 'post-grid--home';
|
||||
} elseif ($cfg['mode'] !== 'mini') {
|
||||
$wrapper_classes[] = 'post-grid';
|
||||
$wrapper_classes[] = 'post-grid--' . $cfg['mode'];
|
||||
}
|
||||
|
||||
if (!empty($config['wrapper_class']) && is_string($config['wrapper_class'])) {
|
||||
foreach (preg_split('/\s+/', trim($config['wrapper_class'])) as $extra_class) {
|
||||
$extra_class = sanitize_html_class($extra_class);
|
||||
if ($extra_class !== '') {
|
||||
$wrapper_classes[] = $extra_class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$wrapper_attr = 'class="' . esc_attr(implode(' ', array_unique($wrapper_classes))) . '"';
|
||||
if ($cfg['columns'] > 1) {
|
||||
$wrapper_attr .= ' style="--aksrt-widget-columns:' . (int) $cfg['columns'] . '"';
|
||||
}
|
||||
|
||||
$data_view = in_array($cfg['mode'], ['card', 'grid-cms'], true) ? 'card' : $cfg['mode'];
|
||||
// Homepage panel already provides .posts-row.ajaxpager — allow callers to skip nesting.
|
||||
$skip_wrapper = !empty($config['skip_wrapper']);
|
||||
$html = '';
|
||||
|
||||
if ($query instanceof WP_Query) {
|
||||
if (!$query->have_posts()) {
|
||||
$html = '<div class="no-results"><p>' . esc_html__('暂无文章', 'aksrt') . '</p></div>';
|
||||
} else {
|
||||
if (!$skip_wrapper) {
|
||||
$html .= '<div ' . $wrapper_attr . ' data-view="' . esc_attr($data_view) . '">';
|
||||
}
|
||||
$number = $cfg['number_offset'];
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
if ($cfg['mode'] === 'mini' && $cfg['show_number']) {
|
||||
$number++;
|
||||
$config['number_offset'] = $number;
|
||||
$cfg = aksrt_renderer_config($config);
|
||||
}
|
||||
$html .= aksrt_render_post($cfg);
|
||||
}
|
||||
if (!$skip_wrapper) {
|
||||
$html .= '</div>';
|
||||
}
|
||||
}
|
||||
wp_reset_postdata();
|
||||
} else {
|
||||
if (!have_posts()) {
|
||||
$html = '<div class="no-results"><p>' . esc_html__('暂无文章', 'aksrt') . '</p></div>';
|
||||
} else {
|
||||
if (!$skip_wrapper) {
|
||||
$html .= '<div ' . $wrapper_attr . ' data-view="' . esc_attr($data_view) . '">';
|
||||
}
|
||||
while (have_posts()) {
|
||||
the_post();
|
||||
$html .= aksrt_render_post($cfg);
|
||||
}
|
||||
if (!$skip_wrapper) {
|
||||
$html .= '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($echo) {
|
||||
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
return '';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_render_mini_list')) {
|
||||
/**
|
||||
* Convenience wrapper for mini lists.
|
||||
*
|
||||
* @param WP_Query $query Posts query.
|
||||
* @param array<string,mixed> $config Renderer config overrides.
|
||||
* @param bool $echo Whether to echo output.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_render_mini_list(WP_Query $query, array $config = [], bool $echo = true): string {
|
||||
$config['mode'] = 'mini';
|
||||
return aksrt_render_post_list($query, $config, $echo);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_render_placeholder')) {
|
||||
/**
|
||||
* Render skeleton placeholders for loading states.
|
||||
*
|
||||
* @param string $mode list|card|mini.
|
||||
* @param int $count Number of placeholders.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_render_placeholder(string $mode = 'list', int $count = 3): string {
|
||||
$count = max(1, min(12, $count));
|
||||
$html = '';
|
||||
|
||||
if ($mode === 'card') {
|
||||
$item = '<div class="posts-item card"><div class="item-thumbnail"><div class="radius8 item-thumbnail placeholder"></div></div>'
|
||||
. '<div class="item-body"><h2 class="item-excerpt placeholder t1 item-heading"></h2>'
|
||||
. '<p class="mt10 placeholder k2"></p>'
|
||||
. '<i class="flex jsb"><i class="placeholder s1"></i><i class="placeholder s1 ml10"></i></i></div></div>';
|
||||
$html = str_repeat($item, $count);
|
||||
} elseif ($mode === 'mini') {
|
||||
$item = '<div class="posts-mini"><div class="item-thumbnail"><div class="radius8 placeholder"></div></div>'
|
||||
. '<div class="posts-mini-con"><div class="placeholder t1"></div>'
|
||||
. '<div class="placeholder k2 mt6"></div></div></div>';
|
||||
$html = str_repeat($item, $count);
|
||||
} else {
|
||||
$item = '<div class="posts-item list flex"><div class="post-graphic"><div class="radius8 item-thumbnail placeholder"></div></div>'
|
||||
. '<div class="item-body flex xx flex1 jsb"><p class="placeholder t1"></p>'
|
||||
. '<h4 class="item-excerpt placeholder k1"></h4><p class="placeholder k2"></p>'
|
||||
. '<i><i class="placeholder s1"></i><i class="placeholder s1 ml10"></i></i></div></div>';
|
||||
$html = str_repeat($item, $count);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
/**
|
||||
* Shared post renderer helpers.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_renderer_collect_post_data')) {
|
||||
/**
|
||||
* Aggregate the runtime data needed by all renderer modes.
|
||||
*
|
||||
* @param array<string,mixed> $cfg Renderer config.
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
function aksrt_renderer_collect_post_data(array $cfg): array {
|
||||
$post_id = (int) get_the_ID();
|
||||
if ($post_id <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$lazy = aksrt_renderer_lazyload_config();
|
||||
$lazy['enabled'] = !empty($lazy['enabled']) && !empty($cfg['thumb_lazy']);
|
||||
|
||||
$images = aksrt_renderer_resolve_images($post_id, $cfg);
|
||||
$gallery_images = array_slice($images['gallery'], 0, $cfg['multi_thumb_limit']);
|
||||
$is_mult_thumb = !empty($cfg['multi_thumb_enabled'])
|
||||
&& count($gallery_images) >= (int) $cfg['multi_thumb_min']
|
||||
&& $cfg['mode'] === 'list';
|
||||
|
||||
$author_id = (int) get_post_field('post_author', $post_id);
|
||||
$categories = get_the_category($post_id) ?: [];
|
||||
$tags = get_the_tags($post_id) ?: [];
|
||||
|
||||
$custom_excerpt = class_exists('\AKSRT\Core\PostMetaBox')
|
||||
? (string) \AKSRT\Core\PostMetaBox::get_meta_value($post_id, 'custom_excerpt', '')
|
||||
: '';
|
||||
$custom_subtitle = (string) get_post_meta($post_id, '_subtitle', true);
|
||||
$excerpt_source = $custom_excerpt !== '' ? $custom_excerpt : (string) get_the_excerpt($post_id);
|
||||
if ($excerpt_source === '') {
|
||||
$excerpt_source = (string) get_post_field('post_content', $post_id, 'raw');
|
||||
}
|
||||
$excerpt = wp_trim_words(wp_strip_all_tags($excerpt_source), $cfg['excerpt_words'], '...');
|
||||
|
||||
$resource_meta = function_exists('aksrt_get_resource_meta') ? aksrt_get_resource_meta($post_id) : [];
|
||||
$resource_strip = function_exists('aksrt_resource_meta_strip') ? aksrt_resource_meta_strip($resource_meta) : [];
|
||||
|
||||
return [
|
||||
'post_id' => $post_id,
|
||||
'permalink' => get_permalink($post_id),
|
||||
'title' => get_the_title($post_id),
|
||||
'subtitle' => $custom_subtitle,
|
||||
'excerpt' => $excerpt,
|
||||
'img_url' => $images['primary'],
|
||||
'has_img' => !empty($images['has_thumb']),
|
||||
'has_real_img' => !empty($images['has_real']),
|
||||
'gallery_images' => $gallery_images,
|
||||
'is_mult_thumb' => $is_mult_thumb,
|
||||
'is_video_cover' => !empty(get_post_meta($post_id, '_aksrt_featured_video', true)) || !empty(get_post_meta($post_id, '_video_url', true)),
|
||||
'author_id' => $author_id,
|
||||
'avatar_url' => get_avatar_url($author_id, ['size' => 40]),
|
||||
'author_name' => get_the_author(),
|
||||
'categories' => $categories,
|
||||
'tags' => $tags,
|
||||
'comment_count' => (int) get_comments_number($post_id),
|
||||
'views' => function_exists('aksrt_get_post_views') ? (int) aksrt_get_post_views($post_id) : 0,
|
||||
'likes' => function_exists('aksrt_get_post_likes') ? (int) aksrt_get_post_likes($post_id) : 0,
|
||||
'time_ago' => human_time_diff((int) get_the_time('U', $post_id), current_time('timestamp')),
|
||||
'resource_badge' => function_exists('aksrt_resource_card_badge_text') ? (string) aksrt_resource_card_badge_text($post_id) : '',
|
||||
'resource_strip' => $resource_strip,
|
||||
'is_resource' => function_exists('aksrt_is_resource_post') ? aksrt_is_resource_post($post_id) : false,
|
||||
'is_sticky' => is_sticky($post_id),
|
||||
'lazy' => $lazy,
|
||||
'thumb_ratio_css' => aksrt_renderer_thumb_ratio_value((string) $cfg['thumb_ratio']),
|
||||
'thumb_ratio_key' => (string) $cfg['thumb_ratio'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_article_attrs')) {
|
||||
/**
|
||||
* Build the article wrapper attributes.
|
||||
*
|
||||
* @param string[] $classes CSS classes.
|
||||
* @param array<string,mixed> $data Collected post data.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_article_attrs(array $classes, array $data): string {
|
||||
$classes[] = 'thumb-ratio--' . str_replace(':', '-', (string) $data['thumb_ratio_key']);
|
||||
|
||||
return sprintf(
|
||||
'class="%s" style="%s"',
|
||||
esc_attr(implode(' ', array_unique(array_filter($classes)))),
|
||||
esc_attr('--aksrt-thumb-ratio:' . $data['thumb_ratio_css'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_thumb')) {
|
||||
/**
|
||||
* Render the primary thumbnail.
|
||||
*
|
||||
* @param array<string,mixed> $data Collected post data.
|
||||
* @param array<string,mixed> $cfg Renderer config.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_thumb(array $data, array $cfg): string {
|
||||
if (!$cfg['show_thumb'] || empty($data['has_img'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$lazy = $data['lazy'];
|
||||
$img = (string) $data['img_url'];
|
||||
$title = (string) $data['title'];
|
||||
$href = (string) $data['permalink'];
|
||||
$is_sticky = !empty($data['is_sticky']);
|
||||
$has_real_img = !empty($data['has_real_img']);
|
||||
|
||||
if ($has_real_img && !empty($lazy['enabled'])) {
|
||||
$img_html = '<span class="aksrt-skeleton"></span>'
|
||||
. '<img class="' . (!empty($cfg['thumb_fit']) && $cfg['thumb_fit'] === 'contain' ? 'fit-contain' : 'fit-cover') . ' aksrt-lazy lazyload" data-src="' . esc_url($img) . '" src="' . esc_url($lazy['placeholder_url']) . '" alt="' . esc_attr($title) . '">';
|
||||
} else {
|
||||
$img_html = '<img class="' . (!empty($cfg['thumb_fit']) && $cfg['thumb_fit'] === 'contain' ? 'fit-contain' : 'fit-cover') . '" src="' . esc_url($img) . '" alt="' . esc_attr($title) . '"' . ($has_real_img ? ' loading="lazy"' : '') . '>';
|
||||
}
|
||||
|
||||
$sticky_html = ($is_sticky && !empty($cfg['badge_show']))
|
||||
? '<badge class="img-badge left jb-red">' . esc_html__('置顶', 'aksrt') . '</badge>'
|
||||
: '';
|
||||
|
||||
$format_badge = '';
|
||||
if (!empty($data['is_mult_thumb']) && !empty($data['gallery_images'])) {
|
||||
$format_badge = '<badge class="b-black opacity8 abs-right">' . esc_html(count($data['gallery_images'])) . '</badge>';
|
||||
} elseif (!empty($data['is_video_cover'])) {
|
||||
$format_badge = '<badge class="b-black opacity8 abs-right"><i class="fas fa-play-circle" aria-hidden="true"></i></badge>';
|
||||
}
|
||||
|
||||
$empty_class = $has_real_img ? '' : ' item-thumbnail--fallback';
|
||||
$target_attr = !empty($cfg['target_blank']) ? ' target="_blank"' : '';
|
||||
|
||||
if ($cfg['mode'] === 'list') {
|
||||
$graphic_class = 'post-graphic';
|
||||
if (($cfg['list_img_position'] ?? 'left') === 'right') {
|
||||
$graphic_class .= ' order1';
|
||||
}
|
||||
return '<div class="' . esc_attr($graphic_class) . '"><div class="item-thumbnail' . esc_attr($empty_class) . '"><a href="' . esc_url($href) . '" rel="bookmark"' . $target_attr . '>'
|
||||
. $img_html . '</a>' . $sticky_html . $format_badge . '</div></div>';
|
||||
}
|
||||
|
||||
return '<div class="item-thumbnail' . esc_attr($empty_class) . '"><a href="' . esc_url($href) . '" rel="bookmark"' . $target_attr . '>'
|
||||
. $img_html . '</a>' . $sticky_html . $format_badge . '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_mult_thumb')) {
|
||||
/**
|
||||
* Render the multi-image strip used by list cards.
|
||||
*
|
||||
* @param array<string,mixed> $data Collected post data.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_mult_thumb(array $data): string {
|
||||
if (empty($data['is_mult_thumb']) || empty($data['gallery_images'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$lazy = $data['lazy'];
|
||||
$html = '<a class="thumb-items contain" href="' . esc_url($data['permalink']) . '" aria-label="' . esc_attr($data['title']) . '">';
|
||||
|
||||
foreach (array_slice($data['gallery_images'], 0, 4) as $image) {
|
||||
$html .= '<span>';
|
||||
if (!empty($lazy['enabled'])) {
|
||||
$html .= '<span class="aksrt-skeleton"></span>'
|
||||
. '<img class="fit-cover aksrt-lazy lazyload" data-src="' . esc_url($image) . '" src="' . esc_url($lazy['placeholder_url']) . '" alt="">';
|
||||
} else {
|
||||
$html .= '<img class="fit-cover" src="' . esc_url($image) . '" alt="" loading="lazy">';
|
||||
}
|
||||
$html .= '</span>';
|
||||
}
|
||||
|
||||
$html .= '</a>';
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_badges')) {
|
||||
/**
|
||||
* Render taxonomy and resource badges.
|
||||
*
|
||||
* @param array<string,mixed> $data Collected post data.
|
||||
* @param array<string,mixed> $cfg Renderer config.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_badges(array $data, array $cfg): string {
|
||||
$items = [];
|
||||
|
||||
if (!empty($cfg['show_resource_badge']) && $data['resource_badge'] !== '') {
|
||||
$items[] = '<a href="' . esc_url($data['permalink']) . '#posts-pay" class="meta-pay but jb-yellow">' . esc_html($data['resource_badge']) . '</a>';
|
||||
}
|
||||
|
||||
if (!empty($cfg['show_badges']) && !empty($cfg['badge_show']) && $cfg['category_limit'] > 0) {
|
||||
foreach (array_slice($data['categories'], 0, $cfg['category_limit']) as $category) {
|
||||
$items[] = '<a href="' . esc_url(get_category_link($category->term_id)) . '" class="but"><i class="far fa-folder-open" aria-hidden="true"></i> ' . esc_html($category->name) . '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($cfg['show_tags']) && $cfg['tag_limit'] > 0) {
|
||||
foreach (array_slice($data['tags'], 0, $cfg['tag_limit']) as $tag) {
|
||||
$items[] = '<a href="' . esc_url(get_tag_link($tag->term_id)) . '" class="but">#' . esc_html($tag->name) . '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['is_resource'])) {
|
||||
$items[] = '<span class="but jb-vip">VIP</span>';
|
||||
}
|
||||
|
||||
if (empty($items)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<div class="item-tags scroll-x no-scrollbar">' . implode('', $items) . '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_resource_strip')) {
|
||||
/**
|
||||
* Render compact resource meta chips.
|
||||
*
|
||||
* @param array<string,mixed> $data Collected post data.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_resource_strip(array $data): string {
|
||||
if (empty($data['resource_strip']) || !is_array($data['resource_strip'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$html = '<ul class="item-cms-strip">';
|
||||
foreach ($data['resource_strip'] as $item) {
|
||||
$html .= '<li>' . esc_html((string) $item) . '</li>';
|
||||
}
|
||||
$html .= '</ul>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_renderer_meta')) {
|
||||
/**
|
||||
* Render the meta bar.
|
||||
*
|
||||
* @param array<string,mixed> $data Collected post data.
|
||||
* @param array<string,mixed> $cfg Renderer config.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_renderer_meta(array $data, array $cfg): string {
|
||||
if (!$cfg['show_meta']) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$format_number = function_exists('aksrt_format_number') ? 'aksrt_format_number' : 'strval';
|
||||
|
||||
if ($cfg['mode'] === 'mini') {
|
||||
$items = [];
|
||||
if (!empty($cfg['show_date'])) {
|
||||
$items[] = '<span class="meta-item">' . esc_html($data['time_ago']) . '</span>';
|
||||
}
|
||||
if (!empty($cfg['show_views'])) {
|
||||
$items[] = '<span class="meta-item"><i class="fas fa-eye" aria-hidden="true"></i> ' . esc_html($format_number($data['views'])) . '</span>';
|
||||
}
|
||||
|
||||
if (empty($items)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<div class="item-meta">' . implode('<span class="meta-dot" aria-hidden="true"></span>', $items) . '</div>';
|
||||
}
|
||||
|
||||
$left = [];
|
||||
if (!empty($cfg['show_author'])) {
|
||||
$left[] = '<item class="meta-author flex ac"><img src="' . esc_url($data['avatar_url']) . '" alt="" class="avatar-mini" width="20" height="20" loading="lazy"><name class="meta-name">' . esc_html($data['author_name']) . '</name></item>';
|
||||
}
|
||||
if (!empty($cfg['show_date'])) {
|
||||
$left[] = '<item class="meta-time">' . esc_html($data['time_ago']) . '</item>';
|
||||
}
|
||||
|
||||
$right = [];
|
||||
if (!empty($cfg['show_comments'])) {
|
||||
$right[] = '<item class="meta-view"><i class="fas fa-comment" aria-hidden="true"></i> ' . esc_html((string) $data['comment_count']) . '</item>';
|
||||
}
|
||||
if (!empty($cfg['show_views'])) {
|
||||
$right[] = '<item class="meta-view"><i class="fas fa-eye" aria-hidden="true"></i> ' . esc_html($format_number($data['views'])) . '</item>';
|
||||
}
|
||||
if (!empty($cfg['show_likes'])) {
|
||||
$right[] = '<item class="meta-view"><i class="fas fa-thumbs-up" aria-hidden="true"></i> ' . esc_html($format_number($data['likes'])) . '</item>';
|
||||
}
|
||||
|
||||
if (empty($left) && empty($right)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$html = '<div class="item-meta muted-2-color flex jsb ac">';
|
||||
if (!empty($left)) {
|
||||
$html .= '<div class="meta-author-group flex ac">' . implode('<span class="meta-dot" aria-hidden="true"></span>', $left) . '</div>';
|
||||
}
|
||||
if (!empty($right)) {
|
||||
$html .= '<div class="meta-right">' . implode('', $right) . '</div>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT template tag helpers.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
// ============================================================
|
||||
// 模板标签:Banner 幻灯片
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_decode_slide_layers')):
|
||||
/**
|
||||
* Decode the layers JSON meta for a slide.
|
||||
*
|
||||
* @param int $post_id
|
||||
* @return array
|
||||
*/
|
||||
function aksrt_decode_slide_layers(int $post_id): array {
|
||||
$json = get_post_meta($post_id, '_layers', true);
|
||||
if (!is_string($json) || $json === '') {
|
||||
return [];
|
||||
}
|
||||
$decoded = json_decode($json, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_slider_config')):
|
||||
/**
|
||||
* Get the global slider config for Hero/Banner rendering.
|
||||
* Falls back to sensible defaults when no config is saved.
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
function aksrt_get_slider_config(): array {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
return \AKSRT\Settings\Settings::get_slider_config();
|
||||
}
|
||||
return [
|
||||
'effect' => 'fade', 'loop' => true, 'autoplay_interval' => 5000,
|
||||
'pagination' => true, 'navigation' => true, 'auto_height' => false,
|
||||
'pc_height' => '', 'mobile_height' => '', 'space_between' => 0,
|
||||
];
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_banners')):
|
||||
/**
|
||||
* 获取已启用的 Hero 幻灯片列表(供首页 Hero 轮播使用)
|
||||
* 数据由后台 "内容配置 → Hero 轮播" 管理
|
||||
*
|
||||
* @param int $limit 最大数量
|
||||
* @return array
|
||||
*/
|
||||
function aksrt_get_banners($limit = 8, $kind = 'hero') {
|
||||
$kind = in_array($kind, array('hero', 'banner'), true) ? $kind : 'hero';
|
||||
$limit = max(1, min(20, (int) $limit));
|
||||
$cache_key = 'aksrt_banners_' . $kind . '_' . $limit;
|
||||
$cached = wp_cache_get($cache_key, 'aksrt');
|
||||
if (is_array($cached)) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'post_type' => 'aksrt_banner',
|
||||
'posts_per_page' => $limit,
|
||||
'post_status' => 'publish',
|
||||
'orderby' => 'menu_order',
|
||||
'order' => 'ASC',
|
||||
'no_found_rows' => true,
|
||||
'meta_query' => 'hero' === $kind
|
||||
? array(
|
||||
'relation' => 'OR',
|
||||
array(
|
||||
'key' => '_aksrt_kind',
|
||||
'value' => 'hero',
|
||||
),
|
||||
array(
|
||||
'key' => '_aksrt_kind',
|
||||
'compare' => 'NOT EXISTS',
|
||||
),
|
||||
)
|
||||
: array(
|
||||
array(
|
||||
'key' => '_aksrt_kind',
|
||||
'value' => 'banner',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$query = new WP_Query($args);
|
||||
$banners = array();
|
||||
|
||||
if ($query->have_posts()) {
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$id = get_the_ID();
|
||||
|
||||
// 只返回启用的
|
||||
$enabled = get_post_meta($id, '_enabled', true);
|
||||
if ($enabled === '0') continue;
|
||||
|
||||
$banners[] = array(
|
||||
'id' => $id,
|
||||
'title' => get_the_title(),
|
||||
'subtitle' => get_post_meta($id, '_subtitle', true),
|
||||
'description' => get_post_meta($id, '_description', true),
|
||||
'image' => get_post_meta($id, '_image_url', true),
|
||||
'video_url' => get_post_meta($id, '_video_url', true),
|
||||
'link' => get_post_meta($id, '_link_url', true),
|
||||
'link_target' => get_post_meta($id, '_link_target', true) ?: '_self',
|
||||
'text_align' => get_post_meta($id, '_text_align', true) ?: 'left',
|
||||
'device_visibility' => get_post_meta($id, '_device_visibility', true) ?: 'all',
|
||||
'kind' => get_post_meta($id, '_aksrt_kind', true) ?: 'hero',
|
||||
'show_text' => get_post_meta($id, '_show_text', true) !== '0',
|
||||
// show_meta: legacy field, read for backward compat but not consumed by frontend
|
||||
'show_meta' => get_post_meta($id, '_show_meta', true) === '1',
|
||||
'text_size_pc' => get_post_meta($id, '_text_size_pc', true) ?: '',
|
||||
'text_size_m' => get_post_meta($id, '_text_size_m', true) ?: '',
|
||||
'text_parallax' => get_post_meta($id, '_text_parallax', true) === '1',
|
||||
'layers' => aksrt_decode_slide_layers($id),
|
||||
);
|
||||
}
|
||||
wp_reset_postdata();
|
||||
}
|
||||
|
||||
wp_cache_set($cache_key, $banners, 'aksrt', 15 * MINUTE_IN_SECONDS);
|
||||
return $banners;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_render_slider_markup')):
|
||||
/**
|
||||
* Render a shared slider block for Hero and Banner sections.
|
||||
* Uses Swiper.js when available, falls back to native JS.
|
||||
*
|
||||
* @param array $slides Slider items.
|
||||
* @param array $args Render options.
|
||||
*/
|
||||
function aksrt_render_slider_markup(array $slides, array $args = array()) {
|
||||
if (empty($slides)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$args = array_merge(
|
||||
array(
|
||||
'section_class' => 'hero hero--slider hero--with-overlay-nav',
|
||||
'section_id' => '',
|
||||
'section_label' => __('轮播', 'aksrt'),
|
||||
'content_class' => 'hero__content hero__content--slide',
|
||||
'with_search' => false,
|
||||
'search_id' => 'hero-search',
|
||||
'search_class' => 'hero-search hero-search--floating hero-search--pill',
|
||||
'search_placeholder' => aksrt_get_search_placeholder(),
|
||||
'show_meta' => false,
|
||||
),
|
||||
$args
|
||||
);
|
||||
|
||||
$slide_count = count($slides);
|
||||
$section_id_attr = $args['section_id'] ? ' id="' . esc_attr($args['section_id']) . '"' : '';
|
||||
|
||||
// Swiper config JSON
|
||||
$swiper_config = array(
|
||||
'effect' => 'fade',
|
||||
'loop' => $slide_count > 1,
|
||||
'speed' => 600,
|
||||
'autoplay' => $slide_count > 1 ? array('delay' => 5000, 'disableOnInteraction' => false) : false,
|
||||
'pagination' => array('type' => 'progressbar'),
|
||||
'keyboard' => array('enabled' => true),
|
||||
'a11y' => array('prevSlideMessage' => __('上一张', 'aksrt'), 'nextSlideMessage' => __('下一张', 'aksrt')),
|
||||
);
|
||||
?>
|
||||
<section class="<?php echo esc_attr($args['section_class']); ?>"<?php echo $section_id_attr; ?> aria-label="<?php echo esc_attr($args['section_label']); ?>">
|
||||
<div class="swiper hero-swiper" data-swiper-config="<?php echo esc_attr(json_encode($swiper_config)); ?>">
|
||||
<div class="swiper-wrapper">
|
||||
<?php foreach ($slides as $index => $slide) : ?>
|
||||
<?php
|
||||
$show_text = ! isset($slide['show_text']) || $slide['show_text'];
|
||||
$show_meta_flag = $args['show_meta'] && (! isset($slide['show_meta']) || $slide['show_meta']);
|
||||
$has_title = ! empty($slide['title']);
|
||||
$has_subtitle = ! empty($slide['subtitle']);
|
||||
?>
|
||||
<div class="swiper-slide"<?php if (! empty($slide['link'])) : ?> data-link="<?php echo esc_url($slide['link']); ?>"<?php endif; ?>>
|
||||
<div class="hero__slide-bg">
|
||||
<?php if (! empty($slide['image'])) : ?>
|
||||
<img
|
||||
src="<?php echo esc_url($slide['image']); ?>"
|
||||
alt="<?php echo esc_attr($slide['title'] ?: get_bloginfo('name')); ?>"
|
||||
loading="<?php echo 0 === $index ? 'eager' : 'lazy'; ?>"
|
||||
decoding="async"
|
||||
>
|
||||
<?php else : ?>
|
||||
<div class="hero__slide-bg-fallback"></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="hero__slide-overlay"></div>
|
||||
<?php if ($show_text && ($has_title || $has_subtitle)) : ?>
|
||||
<div class="<?php echo esc_attr($args['content_class']); ?>">
|
||||
<?php if ($has_title) : ?>
|
||||
<h1 class="hero__title"><?php echo esc_html($slide['title']); ?></h1>
|
||||
<?php endif; ?>
|
||||
<?php if ($has_subtitle) : ?>
|
||||
<p class="hero__subtitle"><?php echo esc_html($slide['subtitle']); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($slide_count > 1) : ?>
|
||||
<div class="hero__controls">
|
||||
<div class="swiper-pagination hero__pagination"></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($args['with_search']) : ?>
|
||||
<div class="<?php echo esc_attr($args['search_class']); ?>">
|
||||
<span class="hero-search__icon" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
</span>
|
||||
<form role="search" method="get" action="<?php echo esc_url(home_url('/')); ?>">
|
||||
<input type="hidden" name="aksrt_search_type" value="<?php echo esc_attr(aksrt_get_search_form_type()); ?>">
|
||||
<label class="screen-reader-text" for="<?php echo esc_attr($args['search_id']); ?>"><?php esc_html_e('搜索', 'aksrt'); ?></label>
|
||||
<input type="search" id="<?php echo esc_attr($args['search_id']); ?>" name="s" placeholder="<?php echo esc_attr($args['search_placeholder']); ?>">
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<?php
|
||||
}
|
||||
endif;
|
||||
|
||||
// ============================================================
|
||||
// 模板标签:图标覆盖卡片(顶部多功能组件叠加层)
|
||||
// 完全模仿 zibll 的 zib_icon_cover_card
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_render_icon_cover_card')):
|
||||
/**
|
||||
* 渲染单个图标覆盖卡片(模仿 zibll 的 zib_icon_cover_card)
|
||||
*
|
||||
* 结构:左侧圆形/方形图标 + 右侧标题和描述
|
||||
*
|
||||
* @param array $card 卡片配置
|
||||
* - icon: string Font Awesome 类名 (如 'fas fa-magic')
|
||||
* - custom_icon: string 自定义 SVG/HTML 图标(优先于 icon)
|
||||
* - icon_class: string 图标颜色类 (c-yellow, c-blue, c-green, c-red, c-orange, c-cyan, c-pink, jb-cyan, jb-green, jb-orange)
|
||||
* - icon_shape: string 图标形状 'circle' | 'square'
|
||||
* - title: string 主标题
|
||||
* - desc: string 描述文字
|
||||
* - url: string 跳转链接
|
||||
* - target: string 链接打开方式 '_blank' | '_self'
|
||||
* @return string HTML
|
||||
*/
|
||||
function aksrt_render_icon_cover_card(array $card): string {
|
||||
// Prefer canonical zibll-aligned renderer.
|
||||
if (function_exists('aksrt_icon_cover_card')) {
|
||||
return aksrt_icon_cover_card($card);
|
||||
}
|
||||
|
||||
$icon = $card['icon'] ?? 'fas fa-star';
|
||||
$custom_icon = $card['custom_icon'] ?? ($card['customize_icon'] ?? '');
|
||||
$icon_class = trim((string) ($card['icon_class'] ?? 'c-blue'));
|
||||
if (($card['icon_shape'] ?? '') === 'square' && !str_contains($icon_class, 'radius4')) {
|
||||
$icon_class .= ' radius4';
|
||||
}
|
||||
$title = $card['title'] ?? '';
|
||||
$desc = $card['desc'] ?? '';
|
||||
$url = $card['url'] ?? ($card['link']['url'] ?? '');
|
||||
$target = $card['target'] ?? ($card['link']['target'] ?? '');
|
||||
$tgt = ($target !== '' && $target !== '_self') ? ' target="' . esc_attr((string) $target) . '"' : '';
|
||||
|
||||
$icon_html = $custom_icon !== ''
|
||||
? wp_kses_post((string) $custom_icon)
|
||||
: '<i class="' . esc_attr((string) $icon) . ' em09" aria-hidden="true"></i>';
|
||||
|
||||
$icon_container = '<div class="icon-cover-icon badg cir ' . esc_attr($icon_class) . '" style="font-size: 25px;">' . $icon_html . '</div>';
|
||||
|
||||
$text_html = '<div class="icon-cover-desc ml10 flex1 px12-sm">';
|
||||
if ($title !== '') {
|
||||
$text_html .= '<div class="em12 text-ellipsis font-bold"> ' . wp_kses_post((string) $title) . '</div>';
|
||||
}
|
||||
if ($desc !== '') {
|
||||
$text_html .= '<div class="muted-color mt6 text-ellipsis"> ' . wp_kses_post((string) $desc) . '</div>';
|
||||
}
|
||||
$text_html .= '</div>';
|
||||
|
||||
$card_inner = $icon_container . $text_html;
|
||||
$cls = 'icon-cover-card flex ac ' . esc_attr((string) ($card['class'] ?? 'zib-widget'));
|
||||
|
||||
if ($url !== '') {
|
||||
return '<a class="main-color" ' . ($tgt !== '' ? ltrim($tgt) . ' ' : '') . 'href="' . esc_url((string) $url) . '"><div class="' . $cls . '">' . $card_inner . '</div></a>';
|
||||
}
|
||||
return '<div class="' . $cls . '">' . $card_inner . '</div>';
|
||||
}
|
||||
endif;
|
||||
|
||||
// ============================================================
|
||||
|
||||
// 模板标签:友情链接
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_get_friend_links')):
|
||||
/**
|
||||
* 获取友情链接列表(已激活的)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function aksrt_get_friend_links() {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
return \AKSRT\Settings\Settings::get_friend_links();
|
||||
}
|
||||
|
||||
$links = get_option('aksrt_friend_links', array());
|
||||
return array_values(array_filter($links, function($l) {
|
||||
return !empty($l['active']);
|
||||
}));
|
||||
}
|
||||
endif;
|
||||
|
||||
// ============================================================
|
||||
// 模板标签:站点 Logo / Favicon / About
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_get_site_logo_url')):
|
||||
/**
|
||||
* 获取站点 LOGO URL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_get_site_logo_url() {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
return \AKSRT\Settings\Settings::logo_url();
|
||||
}
|
||||
|
||||
return (string) aksrt_get_setting('logo_url', '');
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_favicon_url')):
|
||||
/**
|
||||
* 获取 Favicon URL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_get_favicon_url() {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
return \AKSRT\Settings\Settings::favicon_url();
|
||||
}
|
||||
|
||||
$favicon = (string) aksrt_get_setting('favicon_url', '');
|
||||
if ($favicon !== '') {
|
||||
return $favicon;
|
||||
}
|
||||
|
||||
$theme_mod = trim((string) get_theme_mod('aksrt_favicon', ''));
|
||||
if ($theme_mod !== '') {
|
||||
return $theme_mod;
|
||||
}
|
||||
|
||||
$site_icon = get_site_icon_url(512);
|
||||
return $site_icon ? (string) $site_icon : '';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_site_logo_dark_url')):
|
||||
/**
|
||||
* 获取暗色模式 Logo URL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_get_site_logo_dark_url(): string {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
return \AKSRT\Settings\Settings::logo_dark_url();
|
||||
}
|
||||
|
||||
return (string) aksrt_get_setting('site_logo_dark', aksrt_get_setting('logo_url_dark', ''));
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_site_logo_text')):
|
||||
/**
|
||||
* 获取品牌文字
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_get_site_logo_text(): string {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
return \AKSRT\Settings\Settings::logo_text();
|
||||
}
|
||||
|
||||
$text = trim((string) aksrt_get_setting('logo_text', ''));
|
||||
return $text !== '' ? $text : get_bloginfo('name');
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_site_logo_width')):
|
||||
/**
|
||||
* 获取导航 Logo 宽度
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function aksrt_get_site_logo_width(): int {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
return \AKSRT\Settings\Settings::logo_width();
|
||||
}
|
||||
|
||||
$width = (int) aksrt_get_setting('logo_width', 150);
|
||||
return max(50, min(400, $width));
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_adaptive_theme_img')):
|
||||
/**
|
||||
* Build a single light/dark adaptive image (Zibll white-src/dark-src contract).
|
||||
*
|
||||
* theme.js syncThemeImages() swaps src via white-src / dark-src attributes.
|
||||
*
|
||||
* @param string $white_src Light mode image URL.
|
||||
* @param string $dark_src Dark mode image URL.
|
||||
* @param string $alt Alt text.
|
||||
* @param string $more Extra attributes (already escaped by caller if needed).
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_get_adaptive_theme_img(string $white_src = '', string $dark_src = '', string $alt = '', string $more = ''): string {
|
||||
$white_src = trim($white_src);
|
||||
$dark_src = trim($dark_src);
|
||||
|
||||
if ($white_src === '' && $dark_src === '') {
|
||||
return '';
|
||||
}
|
||||
if ($dark_src === '') {
|
||||
$dark_src = $white_src;
|
||||
}
|
||||
if ($white_src === '') {
|
||||
$white_src = $dark_src;
|
||||
}
|
||||
if ($alt === '') {
|
||||
$alt = get_bloginfo('name');
|
||||
}
|
||||
|
||||
$is_dark = false;
|
||||
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'initial_theme_mode')) {
|
||||
$is_dark = \AKSRT\Settings\Settings::initial_theme_mode() === 'dark';
|
||||
}
|
||||
$this_src = $is_dark ? $dark_src : $white_src;
|
||||
|
||||
return '<img src="' . esc_url($this_src) . '" white-src="' . esc_url($white_src) . '" dark-src="' . esc_url($dark_src) . '" alt="' . esc_attr($alt) . '"' . ($more !== '' ? ' ' . $more : '') . ' loading="lazy">';
|
||||
}
|
||||
endif;
|
||||
@@ -0,0 +1,409 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT template tag helpers.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
// ============================================================
|
||||
// 模板标签:文章列表分页渲染
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_render_pagination')) {
|
||||
/**
|
||||
* 渲染文章列表分页(根据后台配置选择模式)。
|
||||
*
|
||||
* Zibll 对齐:numbered = 数字分页按钮;load_more/infinite = AJAX 追加。
|
||||
*
|
||||
* @param WP_Query|null $query 可选,传入自定义查询对象;默认用主查询。
|
||||
* @param array<string,mixed> $args {
|
||||
* @type string $query_context search|archive|home|tag|category... 供 AJAX 路由解析。
|
||||
* @type array<string,mixed> $render_config 透传给 AJAX 渲染器(aksrt_resolve_post_list_render_config)。
|
||||
* }
|
||||
*/
|
||||
function aksrt_render_pagination(?WP_Query $query = null, array $args = []): void {
|
||||
$pagination_mode = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_post_pagination_mode()
|
||||
: 'numbered';
|
||||
|
||||
if ($pagination_mode === 'numbered') {
|
||||
global $wp_query;
|
||||
$q = $query ?? $wp_query;
|
||||
if (!($q instanceof WP_Query) || $q->max_num_pages <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_page = max(1, (int) $q->get('paged'), (int) get_query_var('paged'), (int) get_query_var('page'));
|
||||
$max_pages = (int) $q->max_num_pages;
|
||||
$links = paginate_links([
|
||||
'total' => $max_pages,
|
||||
'current' => $current_page,
|
||||
'mid_size' => 2,
|
||||
'prev_text' => '<span aria-hidden="true">‹</span>',
|
||||
'next_text' => '<span aria-hidden="true">›</span>',
|
||||
'type' => 'plain',
|
||||
]);
|
||||
|
||||
if (empty($links)) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<div class="pagenav ajax-pag aksrt-pagination content-stream__footer-nav">';
|
||||
echo wp_kses_post($links);
|
||||
if ($max_pages > 8) {
|
||||
echo '<span class="pag-jump">';
|
||||
echo '<input type="number" class="jump-input" min="1" max="' . esc_attr((string) $max_pages) . '" value="' . esc_attr((string) $current_page) . '" data-max-pages="' . esc_attr((string) $max_pages) . '" />';
|
||||
echo '<button type="button" class="ajax-next order-ajax-next" data-jump-archive="1">' . esc_html__('跳转', 'aksrt') . '</button>';
|
||||
echo '</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// load_more / infinite 模式
|
||||
global $wp_query;
|
||||
$q = $query ?? $wp_query;
|
||||
if (!($q instanceof WP_Query)) {
|
||||
return;
|
||||
}
|
||||
$max_pages = (int) $q->max_num_pages;
|
||||
$current_page = max(1, (int) $q->get('paged'), (int) get_query_var('paged'), (int) get_query_var('page'));
|
||||
|
||||
if ($current_page >= $max_pages) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query_context = '';
|
||||
if (!empty($args['query_context']) && is_string($args['query_context'])) {
|
||||
$query_context = sanitize_key($args['query_context']);
|
||||
} elseif (is_search()) {
|
||||
$query_context = 'search';
|
||||
} elseif (is_archive()) {
|
||||
$query_context = 'archive';
|
||||
}
|
||||
|
||||
$query_vars = $q->query_vars;
|
||||
if (!is_array($query_vars)) {
|
||||
$query_vars = [];
|
||||
} else {
|
||||
unset($query_vars['paged'], $query_vars['page']);
|
||||
}
|
||||
|
||||
$render_config = !empty($args['render_config']) && is_array($args['render_config'])
|
||||
? $args['render_config']
|
||||
: [];
|
||||
|
||||
$trigger_text = __('加载更多', 'aksrt');
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$list_settings = \AKSRT\Settings\Settings::get_post_list_style_settings();
|
||||
if (!empty($list_settings['ajax_trigger_text'])) {
|
||||
$trigger_text = (string) $list_settings['ajax_trigger_text'];
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="text-center theme-pagination ajax-pag ajax-pagination aksrt-pagination-ajax content-stream__footer-nav"
|
||||
data-mode="<?php echo esc_attr($pagination_mode); ?>"
|
||||
data-next-page="<?php echo esc_attr((string) ($current_page + 1)); ?>"
|
||||
data-max-pages="<?php echo esc_attr((string) $max_pages); ?>"
|
||||
data-query-context="<?php echo esc_attr($query_context); ?>"
|
||||
data-query-vars="<?php echo esc_attr(wp_json_encode($query_vars)); ?>"
|
||||
data-render-config="<?php echo esc_attr(wp_json_encode($render_config)); ?>"
|
||||
data-lazyload-action="ias">
|
||||
<div class="next-page ajax-next aksrt-load-more-btn" lazyload-action="ias" role="button" tabindex="0">
|
||||
<?php echo esc_html($trigger_text); ?>
|
||||
</div>
|
||||
<div class="aksrt-loading-indicator" style="display:none;" aria-hidden="true">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
<span><?php esc_html_e('加载中…', 'aksrt'); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-fill missing alt attributes on images in post content.
|
||||
*
|
||||
* Uses post title as fallback alt text for images missing alt.
|
||||
* Can be enabled/disabled via Settings (auto_image_alt_enabled).
|
||||
*
|
||||
* @param string $content Post content.
|
||||
* @return string Filtered content.
|
||||
*/
|
||||
function aksrt_auto_image_alt(string $content): string {
|
||||
if (is_admin()) return $content;
|
||||
if (!class_exists('\AKSRT\Settings\Settings')) return $content;
|
||||
// schema key = seo_auto_alt; runtime alias = auto_image_alt
|
||||
$auto_alt = \AKSRT\Settings\Settings::get_value('seo_auto_alt', false)
|
||||
?: \AKSRT\Settings\Settings::get_value('auto_image_alt', false);
|
||||
if (!$auto_alt) return $content;
|
||||
|
||||
// Only on singular pages
|
||||
if (!is_singular()) return $content;
|
||||
|
||||
$post_title = get_the_title();
|
||||
if (empty($post_title)) return $content;
|
||||
|
||||
return preg_replace_callback(
|
||||
'/<img\s+([^>]*?)>/i',
|
||||
function ($matches) use ($post_title) {
|
||||
$tag = $matches[0];
|
||||
$attrs = $matches[1];
|
||||
|
||||
// Already has non-empty alt
|
||||
if (preg_match('/alt=["\']([^"\']+)["\']/i', $attrs, $alt_match) && !empty(trim($alt_match[1]))) {
|
||||
return $tag;
|
||||
}
|
||||
|
||||
// Has empty alt — fill it
|
||||
if (preg_match('/alt=["\']["\']/i', $attrs)) {
|
||||
return str_replace(
|
||||
['alt=""', "alt=''"],
|
||||
'alt="' . esc_attr($post_title) . '"',
|
||||
$tag
|
||||
);
|
||||
}
|
||||
|
||||
// No alt at all — add it
|
||||
return '<img ' . $attrs . ' alt="' . esc_attr($post_title) . '">';
|
||||
},
|
||||
$content
|
||||
);
|
||||
}
|
||||
add_filter('the_content', 'aksrt_auto_image_alt', 999);
|
||||
|
||||
/**
|
||||
* 懒加载 per-scope 开关判断
|
||||
*
|
||||
* 检查总开关 + 指定 scope 开关。
|
||||
*
|
||||
* @param string $scope avatar|content|comment|private|slider|cover|other|widget
|
||||
* @return bool
|
||||
*/
|
||||
function aksrt_is_lazyload_enabled(string $scope = 'other'): bool {
|
||||
if (!class_exists('\AKSRT\Settings\Settings')) return true; // 默认开
|
||||
$cfg = \AKSRT\Settings\Settings::get_lazyload_config();
|
||||
if (!$cfg['enabled']) return false;
|
||||
return !empty($cfg[$scope] ?? true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 img 标签的 lazy-loading 属性字符串
|
||||
*
|
||||
* 当 scope 启用懒加载时:class="aksrt-lazy" + data-src(需配合 skeleton 使用)
|
||||
* 当 scope 禁用时:loading="lazy"(浏览器原生)
|
||||
*
|
||||
* @param string $url 图片 URL
|
||||
* @param string $scope 场景名
|
||||
* @param string $extra_class 额外 CSS 类
|
||||
* @return string HTML 属性字符串
|
||||
*/
|
||||
function aksrt_lazy_img_attrs(string $url, string $scope = 'other', string $extra_class = ''): string {
|
||||
if (empty($url)) return '';
|
||||
|
||||
if (aksrt_is_lazyload_enabled($scope)) {
|
||||
$cfg = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_lazyload_config() : [];
|
||||
$placeholder = $cfg['placeholder_url'] ?? '';
|
||||
$animation = $cfg['animation'] ?? 'blur';
|
||||
$cls = trim('fit-cover aksrt-lazy ' . $extra_class);
|
||||
$attrs = sprintf('class="%s" data-src="%s" data-lazy-animation="%s"', esc_attr($cls), esc_url($url), esc_attr($animation));
|
||||
if ($placeholder) {
|
||||
$attrs .= sprintf(' src="%s"', esc_url($placeholder));
|
||||
}
|
||||
return $attrs;
|
||||
}
|
||||
|
||||
// 原生 lazy
|
||||
$cls = trim('fit-cover ' . $extra_class);
|
||||
return sprintf('class="%s" src="%s" loading="lazy"', esc_attr($cls), esc_url($url));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回条件懒加载 loading 属性字符串
|
||||
*
|
||||
* 当指定 scope 启用懒加载时返回 loading="lazy",否则返回空字符串。
|
||||
*
|
||||
* @param string $scope 场景名 (cover|other 等)
|
||||
* @return string HTML 属性字符串
|
||||
*/
|
||||
function aksrt_lazy_loading(string $scope = 'other'): string {
|
||||
if (function_exists('aksrt_is_lazyload_enabled') && aksrt_is_lazyload_enabled($scope)) {
|
||||
return ' loading="lazy"';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// §8.1 Global capability check helper
|
||||
// ================================================================
|
||||
|
||||
if (!function_exists('aksrt_user_can')) :
|
||||
/**
|
||||
* Check if a user has a specific front-end capability from the user_caps matrix.
|
||||
*
|
||||
* Delegates to \AKSRT\Settings\Settings::user_can(). WP admins always pass.
|
||||
*
|
||||
* @param string $cap Capability id (e.g. 'new_post_add', 'comment_edit').
|
||||
* @param int $user_id User ID. Defaults to current user.
|
||||
* @param int|null $context Optional content context for scoped capabilities.
|
||||
* @return bool
|
||||
*/
|
||||
function aksrt_user_can(string $cap, int $user_id = 0, ?int $context = null): bool {
|
||||
if ($user_id <= 0) {
|
||||
$user_id = get_current_user_id();
|
||||
}
|
||||
if (!class_exists('\AKSRT\Settings\Settings')) {
|
||||
return $user_id > 0;
|
||||
}
|
||||
return \AKSRT\Settings\Settings::user_can($cap, $user_id, $context);
|
||||
}
|
||||
endif;
|
||||
|
||||
|
||||
if (!function_exists('aksrt_flush_banner_cache')) :
|
||||
/**
|
||||
* Invalidate banner CPT object-cache keys used by aksrt_get_banners().
|
||||
*/
|
||||
function aksrt_flush_banner_cache($post_id = 0) {
|
||||
if ($post_id && get_post_type($post_id) && get_post_type($post_id) !== 'aksrt_banner') {
|
||||
return;
|
||||
}
|
||||
// Group flush is not available on all object caches; delete common keys.
|
||||
foreach (array('hero', 'banner') as $kind) {
|
||||
for ($n = 1; $n <= 20; $n++) {
|
||||
wp_cache_delete('aksrt_banners_' . $kind . '_' . $n, 'aksrt');
|
||||
}
|
||||
}
|
||||
}
|
||||
add_action('save_post_aksrt_banner', 'aksrt_flush_banner_cache');
|
||||
add_action('deleted_post', 'aksrt_flush_banner_cache');
|
||||
add_action('trashed_post', 'aksrt_flush_banner_cache');
|
||||
endif;
|
||||
|
||||
|
||||
if (!function_exists('aksrt_should_use_footer_tabbar_single_extend')) :
|
||||
/**
|
||||
* Whether single-post extended footer tabbar should replace the default items.
|
||||
* Zibll: footer_tabbar_single.s === extend && is_single(post).
|
||||
*/
|
||||
function aksrt_should_use_footer_tabbar_single_extend(): bool {
|
||||
if (!is_singular('post')) {
|
||||
return false;
|
||||
}
|
||||
if (!class_exists('\AKSRT\Settings\Settings') || !method_exists('\AKSRT\Settings\Settings', 'get_footer_tabbar_single')) {
|
||||
return false;
|
||||
}
|
||||
return \AKSRT\Settings\Settings::get_footer_tabbar_single() === 'extend';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_render_footer_tabbar_single_extend')) :
|
||||
/**
|
||||
* Render single-post extended footer tabbar actions (like / favorite / share / comment).
|
||||
*
|
||||
* @param bool $show_text Whether to show text labels under icons.
|
||||
*/
|
||||
function aksrt_render_footer_tabbar_single_extend(bool $show_text = true): void {
|
||||
$post_id = (int) get_the_ID();
|
||||
if ($post_id <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = function_exists('aksrt_resolve_single_post_display_settings')
|
||||
? aksrt_resolve_single_post_display_settings($post_id)
|
||||
: [];
|
||||
$like_on = !isset($settings['like_enabled']) || !empty($settings['like_enabled']);
|
||||
$favorite_on = !isset($settings['favorite_enabled']) || !empty($settings['favorite_enabled']);
|
||||
$share_on = !isset($settings['share_enabled']) || !empty($settings['share_enabled']);
|
||||
$comments_open = comments_open($post_id)
|
||||
&& !(function_exists('aksrt_are_comments_closed') && aksrt_are_comments_closed());
|
||||
|
||||
$like_count = function_exists('aksrt_get_post_likes') ? (int) aksrt_get_post_likes($post_id) : 0;
|
||||
$favorite_count = function_exists('aksrt_get_post_favorites') ? (int) aksrt_get_post_favorites($post_id) : 0;
|
||||
$is_liked = function_exists('aksrt_is_post_liked') ? aksrt_is_post_liked($post_id) : false;
|
||||
$is_favorited = function_exists('aksrt_is_post_favorited') ? aksrt_is_post_favorited($post_id) : false;
|
||||
$comment_count = (int) get_comments_number($post_id);
|
||||
|
||||
if ($like_on) :
|
||||
?>
|
||||
<button type="button"
|
||||
class="tabbar-item single-action-tabbar action-like<?php echo $is_liked ? ' is-active' : ''; ?>"
|
||||
data-type="like"
|
||||
data-post-id="<?php echo esc_attr((string) $post_id); ?>"
|
||||
data-initial-active="<?php echo $is_liked ? '1' : '0'; ?>"
|
||||
aria-label="<?php esc_attr_e('点赞', 'aksrt'); ?>">
|
||||
<i class="<?php echo $is_liked ? 'fas' : 'far'; ?> fa-heart" aria-hidden="true"></i>
|
||||
<?php if ($show_text) : ?><span data-stat-count="likes" data-base-count="<?php echo esc_attr((string) $like_count); ?>"><?php echo esc_html((string) $like_count); ?></span><?php endif; ?>
|
||||
</button>
|
||||
<?php
|
||||
endif;
|
||||
|
||||
if ($favorite_on) :
|
||||
?>
|
||||
<button type="button"
|
||||
class="tabbar-item single-action-tabbar action-favorite<?php echo $is_favorited ? ' is-active' : ''; ?>"
|
||||
data-type="favorite"
|
||||
data-post-id="<?php echo esc_attr((string) $post_id); ?>"
|
||||
data-initial-active="<?php echo $is_favorited ? '1' : '0'; ?>"
|
||||
aria-label="<?php esc_attr_e('收藏', 'aksrt'); ?>">
|
||||
<i class="<?php echo $is_favorited ? 'fas' : 'far'; ?> fa-star" aria-hidden="true"></i>
|
||||
<?php if ($show_text) : ?><span data-stat-count="favorites" data-base-count="<?php echo esc_attr((string) $favorite_count); ?>"><?php echo esc_html((string) $favorite_count); ?></span><?php endif; ?>
|
||||
</button>
|
||||
<?php
|
||||
endif;
|
||||
|
||||
if ($share_on) :
|
||||
$share_title = get_the_title($post_id);
|
||||
$share_url = get_permalink($post_id) ?: '';
|
||||
$share_image = get_the_post_thumbnail_url($post_id, 'large') ?: '';
|
||||
?>
|
||||
<button type="button"
|
||||
class="tabbar-item single-action-tabbar action-share"
|
||||
data-type="share"
|
||||
data-share-title="<?php echo esc_attr((string) $share_title); ?>"
|
||||
data-share-url="<?php echo esc_url((string) $share_url); ?>"
|
||||
<?php if ($share_image !== '') : ?>data-share-image="<?php echo esc_url((string) $share_image); ?>"<?php endif; ?>
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
aria-label="<?php esc_attr_e('分享', 'aksrt'); ?>">
|
||||
<i class="fas fa-share-alt" aria-hidden="true"></i>
|
||||
<?php if ($show_text) : ?><span><?php esc_html_e('分享', 'aksrt'); ?></span><?php endif; ?>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="tabbar-item single-action-tabbar action-poster"
|
||||
data-type="poster"
|
||||
data-share-platform="poster"
|
||||
data-share-title="<?php echo esc_attr((string) $share_title); ?>"
|
||||
data-share-url="<?php echo esc_url((string) $share_url); ?>"
|
||||
<?php if ($share_image !== '') : ?>data-share-image="<?php echo esc_url((string) $share_image); ?>"<?php endif; ?>
|
||||
aria-label="<?php esc_attr_e('海报', 'aksrt'); ?>">
|
||||
<i class="fas fa-image" aria-hidden="true"></i>
|
||||
<?php if ($show_text) : ?><span><?php esc_html_e('海报', 'aksrt'); ?></span><?php endif; ?>
|
||||
</button>
|
||||
<?php
|
||||
endif;
|
||||
|
||||
if ($share_on && function_exists('aksrt_render_share_dropdown')) {
|
||||
aksrt_render_share_dropdown([
|
||||
'title' => get_the_title($post_id),
|
||||
'url' => get_permalink($post_id) ?: '',
|
||||
'image' => get_the_post_thumbnail_url($post_id, 'large') ?: '',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($comments_open) :
|
||||
?>
|
||||
<a href="#comments"
|
||||
class="tabbar-item single-action-tabbar"
|
||||
data-type="comment"
|
||||
aria-label="<?php esc_attr_e('评论', 'aksrt'); ?>">
|
||||
<i class="far fa-comment" aria-hidden="true"></i>
|
||||
<?php if ($show_text) : ?><span><?php echo esc_html((string) $comment_count); ?></span><?php endif; ?>
|
||||
</a>
|
||||
<?php
|
||||
endif;
|
||||
}
|
||||
endif;
|
||||
|
||||
@@ -0,0 +1,443 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT template tag helpers.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_get_search_placeholder')):
|
||||
/**
|
||||
* 获取搜索占位文案
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_get_search_placeholder(): string {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
return \AKSRT\Settings\Settings::search_placeholder();
|
||||
}
|
||||
|
||||
$placeholder = trim((string) aksrt_get_setting('search_placeholder', ''));
|
||||
return $placeholder !== '' ? $placeholder : __('开启精彩搜索', 'aksrt');
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_search_hot_keywords')):
|
||||
/**
|
||||
* 获取热门搜索关键词
|
||||
*
|
||||
* @return array<int,string>
|
||||
*/
|
||||
function aksrt_get_search_hot_keywords(): array {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
if (!\AKSRT\Settings\Settings::hot_search_enabled()) {
|
||||
return [];
|
||||
}
|
||||
$keywords = \AKSRT\Settings\Settings::hot_search_keywords();
|
||||
// 置顶词前置
|
||||
$sticky = \AKSRT\Settings\Settings::search_popular_sticky();
|
||||
if (!empty($sticky)) {
|
||||
$keywords = array_values(array_unique(array_merge($sticky, $keywords)));
|
||||
}
|
||||
return $keywords;
|
||||
}
|
||||
|
||||
if (!aksrt_get_setting('hot_search_enabled', true)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$raw = aksrt_get_setting('hot_search_keywords', '');
|
||||
$items = is_array($raw) ? $raw : (preg_split('/[\r\n,]+/', (string) $raw) ?: []);
|
||||
$items = array_map(static function ($keyword): string {
|
||||
return trim(sanitize_text_field((string) $keyword));
|
||||
}, $items);
|
||||
$items = array_values(array_filter(array_unique($items), static function (string $keyword): bool {
|
||||
return $keyword !== '';
|
||||
}));
|
||||
|
||||
$limit = max(1, min(50, absint(aksrt_get_setting('search_popular_key_num', 20))));
|
||||
return array_slice($items, 0, $limit);
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_render_search_hot_keywords')):
|
||||
/**
|
||||
* Render hot search keyword chips.
|
||||
*
|
||||
* Used by header drawer, global search panel, search results, and 404.
|
||||
*
|
||||
* @param array<int,string>|null $keywords Optional preloaded keywords.
|
||||
* @param string $class Wrapper class name.
|
||||
*/
|
||||
function aksrt_render_search_hot_keywords($keywords = null, string $class = 'aksrt-search-hot-keywords'): void {
|
||||
if ($keywords === null) {
|
||||
$keywords = aksrt_get_search_hot_keywords();
|
||||
}
|
||||
if (!is_array($keywords)) {
|
||||
$keywords = [];
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ($keywords as $keyword) {
|
||||
$keyword = trim(sanitize_text_field((string) $keyword));
|
||||
if ($keyword !== '') {
|
||||
$items[] = $keyword;
|
||||
}
|
||||
}
|
||||
$items = array_values(array_unique($items));
|
||||
if ($items === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$class = trim(sanitize_html_class(str_replace(' ', '-', $class), 'aksrt-search-hot-keywords'));
|
||||
if ($class === '') {
|
||||
$class = 'aksrt-search-hot-keywords';
|
||||
}
|
||||
|
||||
$label = class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'search_popular_title')
|
||||
? (string) \AKSRT\Settings\Settings::search_popular_title()
|
||||
: __('热门搜索', 'aksrt');
|
||||
if ($label === '') {
|
||||
$label = __('热门搜索', 'aksrt');
|
||||
}
|
||||
|
||||
echo '<div class="' . esc_attr($class) . '">';
|
||||
echo '<span class="' . esc_attr($class . '__label') . '">' . esc_html($label) . '</span>';
|
||||
echo '<div class="' . esc_attr($class . '__list') . '">';
|
||||
foreach ($items as $keyword) {
|
||||
$url = add_query_arg('s', rawurlencode($keyword), home_url('/'));
|
||||
echo '<a class="' . esc_attr($class . '__item') . '" href="' . esc_url($url) . '">' . esc_html($keyword) . '</a>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_search_history_enabled')):
|
||||
/**
|
||||
* 是否启用搜索历史
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function aksrt_search_history_enabled(): bool {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
return \AKSRT\Settings\Settings::search_history_enabled();
|
||||
}
|
||||
|
||||
return !empty(aksrt_get_setting('search_history_enabled', true));
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_search_suggestions_enabled')):
|
||||
/**
|
||||
* 是否启用搜索建议
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function aksrt_search_suggestions_enabled(): bool {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
return \AKSRT\Settings\Settings::search_suggestions_enabled();
|
||||
}
|
||||
|
||||
return aksrt_get_setting('search_type', 'default') === 'ajax'
|
||||
&& !empty(aksrt_get_setting('search_suggestions_enabled', false));
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_search_form_type')):
|
||||
/**
|
||||
* 获取当前搜索类型
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_get_search_form_type(): string {
|
||||
$configured = ['post' => __('文章', 'aksrt')];
|
||||
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$configured = aksrt_get_search_type_options();
|
||||
}
|
||||
|
||||
$allowed_types = array_keys($configured);
|
||||
if (empty($allowed_types)) {
|
||||
$allowed_types = ['post'];
|
||||
}
|
||||
|
||||
$type = sanitize_key((string) get_query_var('aksrt_search_type'));
|
||||
if ($type === '' && isset($_GET['aksrt_search_type'])) {
|
||||
$type = sanitize_key((string) wp_unslash($_GET['aksrt_search_type']));
|
||||
}
|
||||
|
||||
if (in_array($type, $allowed_types, true)) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
return function_exists('aksrt_get_default_search_type') ? aksrt_get_default_search_type() : 'post';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_search_type_options')):
|
||||
/**
|
||||
* 获取前台支持的搜索类型选项
|
||||
*
|
||||
* @return array<string,string>
|
||||
*/
|
||||
function aksrt_get_search_type_options(): array {
|
||||
$label_map = [
|
||||
'post' => __('文章', 'aksrt'),
|
||||
'category' => __('分类', 'aksrt'),
|
||||
'tag' => __('标签', 'aksrt'),
|
||||
];
|
||||
|
||||
if (!class_exists('\AKSRT\Settings\Settings')) {
|
||||
return ['post' => $label_map['post']];
|
||||
}
|
||||
|
||||
$config = \AKSRT\Settings\Settings::get_search_content_types();
|
||||
$types = isset($config['types']) && is_array($config['types']) ? $config['types'] : ['post'];
|
||||
$options = [];
|
||||
|
||||
foreach ($types as $type) {
|
||||
$type = sanitize_key((string) $type);
|
||||
if (isset($label_map[$type])) {
|
||||
$options[$type] = $label_map[$type];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($options)) {
|
||||
$options['post'] = $label_map['post'];
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_default_search_type')):
|
||||
/**
|
||||
* 获取默认搜索类型
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_get_default_search_type(): string {
|
||||
$options = function_exists('aksrt_get_search_type_options') ? aksrt_get_search_type_options() : ['post' => __('文章', 'aksrt')];
|
||||
$allowed = array_keys($options);
|
||||
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$config = \AKSRT\Settings\Settings::get_search_content_types();
|
||||
$default = sanitize_key((string) ($config['default'] ?? 'post'));
|
||||
if (in_array($default, $allowed, true)) {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
return in_array('post', $allowed, true) ? 'post' : (string) reset($allowed);
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_search_default_category_id')):
|
||||
/**
|
||||
* 获取默认搜索分类 ID
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function aksrt_get_search_default_category_id(): int {
|
||||
if (!class_exists('\AKSRT\Settings\Settings') || !\AKSRT\Settings\Settings::search_cat_enabled()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return max(0, absint(\AKSRT\Settings\Settings::search_cat_in()));
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_search_categories')):
|
||||
/**
|
||||
* 获取搜索可选分类列表
|
||||
*
|
||||
* @return WP_Term[]
|
||||
*/
|
||||
function aksrt_get_search_categories(): array {
|
||||
$categories = get_categories([
|
||||
'taxonomy' => 'category',
|
||||
'hide_empty' => true,
|
||||
'number' => 0,
|
||||
'orderby' => 'name',
|
||||
'order' => 'ASC',
|
||||
'fields' => 'all',
|
||||
]);
|
||||
|
||||
if (!is_array($categories)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_filter($categories, static function ($term): bool {
|
||||
return $term instanceof WP_Term;
|
||||
}));
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_search_form_state')):
|
||||
/**
|
||||
* 获取搜索表单运行时状态
|
||||
*
|
||||
* @return array{type:string,types:array<string,string>,category_id:int,category_enabled:bool,categories:array<int,WP_Term>}
|
||||
*/
|
||||
function aksrt_get_search_form_state(): array {
|
||||
$types = function_exists('aksrt_get_search_type_options') ? aksrt_get_search_type_options() : ['post' => __('文章', 'aksrt')];
|
||||
$type = function_exists('aksrt_get_search_form_type') ? aksrt_get_search_form_type() : 'post';
|
||||
$default_category = function_exists('aksrt_get_search_default_category_id') ? aksrt_get_search_default_category_id() : 0;
|
||||
$current_category = isset($_GET['aksrt_category']) ? absint(wp_unslash($_GET['aksrt_category'])) : $default_category;
|
||||
$category_enabled = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::search_cat_enabled() : false;
|
||||
$categories = $category_enabled ? aksrt_get_search_categories() : [];
|
||||
|
||||
return [
|
||||
'type' => isset($types[$type]) ? $type : (function_exists('aksrt_get_default_search_type') ? aksrt_get_default_search_type() : 'post'),
|
||||
'types' => $types,
|
||||
'category_id' => $current_category,
|
||||
'category_enabled' => $category_enabled,
|
||||
'categories' => $categories,
|
||||
];
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_build_search_url')):
|
||||
/**
|
||||
* 构建搜索跳转 URL
|
||||
*
|
||||
* @param string $keyword 搜索关键词
|
||||
* @param array $args 追加参数
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_build_search_url(string $keyword = '', array $args = []): string {
|
||||
$params = [];
|
||||
if ($keyword !== '') {
|
||||
$params['s'] = $keyword;
|
||||
}
|
||||
|
||||
foreach ($args as $key => $value) {
|
||||
if ($value === null || $value === '' || $value === false) {
|
||||
continue;
|
||||
}
|
||||
$params[$key] = $value;
|
||||
}
|
||||
|
||||
return esc_url(add_query_arg($params, home_url('/')));
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_searchable_post_types')):
|
||||
/**
|
||||
* 站内搜索 / 索引 / 推送共用的可检索内容类型。
|
||||
*
|
||||
* 单一数据选择策略:前台搜索建议、MeiliSearch 索引、百度推送都读这里,
|
||||
* 避免「搜索含资源、索引只含 post」之类的不一致。
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
function aksrt_searchable_post_types(): array {
|
||||
$types = ['post'];
|
||||
|
||||
/**
|
||||
* 过滤可检索内容类型。
|
||||
*
|
||||
* @param string[] $types 内容类型 slug 列表。
|
||||
*/
|
||||
$types = apply_filters('aksrt_searchable_post_types', $types);
|
||||
|
||||
return array_values(array_unique(array_filter((array) $types)));
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_time_ago')) :
|
||||
/**
|
||||
* 将日期转为相对时间描述(中文)
|
||||
*
|
||||
* @param string $date 日期字符串(MySQL datetime 格式)
|
||||
* @return string 例如 "19天前"、"3个月前"
|
||||
*/
|
||||
function aksrt_time_ago(string $date): string {
|
||||
$diff = time() - strtotime($date);
|
||||
|
||||
if ($diff < 0) return __('刚刚', 'aksrt');
|
||||
if ($diff < 60) return __('刚刚', 'aksrt');
|
||||
if ($diff < 3600) return sprintf(__('%d分钟前', 'aksrt'), (int) floor($diff / 60));
|
||||
if ($diff < 86400) return sprintf(__('%d小时前', 'aksrt'), (int) floor($diff / 3600));
|
||||
if ($diff < 2592000) return sprintf(__('%d天前', 'aksrt'), (int) floor($diff / 86400));
|
||||
if ($diff < 31536000) return sprintf(__('%d个月前', 'aksrt'), (int) floor($diff / 2592000));
|
||||
|
||||
return sprintf(__('%d年前', 'aksrt'), (int) floor($diff / 31536000));
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_render_comment_pagination')) {
|
||||
/**
|
||||
* 渲染评论列表分页(根据后台配置选择模式)
|
||||
*/
|
||||
function aksrt_render_comment_pagination(): void {
|
||||
$comment_pagination_mode = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_comment_pagination_mode()
|
||||
: 'numbered';
|
||||
|
||||
if ($comment_pagination_mode === 'numbered') {
|
||||
$pagination_links = paginate_comments_links([
|
||||
'echo' => false,
|
||||
'prev_text' => __('上一页', 'aksrt'),
|
||||
'next_text' => __('下一页', 'aksrt'),
|
||||
'type' => 'plain',
|
||||
]);
|
||||
|
||||
if (empty($pagination_links)) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wp_query;
|
||||
$max_pages = get_comment_pages_count();
|
||||
$current_page = max(1, get_query_var('cpage', 1));
|
||||
|
||||
echo '<div class="pagenav ajax-pag aksrt-pagination content-stream__footer-nav">';
|
||||
echo wp_kses_post($pagination_links);
|
||||
if ($max_pages > 8) {
|
||||
echo '<span class="pag-jump">';
|
||||
echo '<input type="number" min="1" max="' . esc_attr((string) $max_pages) . '" value="' . esc_attr((string) $current_page) . '" class="jump-input" data-max-pages="' . esc_attr((string) $max_pages) . '" />';
|
||||
echo '<button type="button" class="ajax-next order-ajax-next" data-jump-comments="1">' . esc_html__('跳转', 'aksrt') . '</button>';
|
||||
echo '</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// load_more / infinite 模式
|
||||
$post_id = get_the_ID();
|
||||
$total = (int) get_comments_number($post_id);
|
||||
$per_page = (int) get_option('comments_per_page', 50);
|
||||
$max_pages = $per_page > 0 ? ceil($total / $per_page) : 1;
|
||||
$current_page = max(1, get_query_var('cpage', 1));
|
||||
$comment_order = (string) get_query_var('aksrt_comment_order', 'newest');
|
||||
if (!in_array($comment_order, ['newest', 'oldest', 'hottest'], true)) {
|
||||
$comment_order = 'newest';
|
||||
}
|
||||
$author_only = isset($_GET['aksrt_author_only']) && (int) $_GET['aksrt_author_only'] === 1;
|
||||
|
||||
if ($current_page >= $max_pages || $total <= $per_page) {
|
||||
return;
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="aksrt-pagination-ajax aksrt-comment-pagination ias-pagenav theme-pagination"
|
||||
data-mode="<?php echo esc_attr($comment_pagination_mode); ?>"
|
||||
data-next-page="<?php echo esc_attr((string) ($current_page + 1)); ?>"
|
||||
data-max-pages="<?php echo esc_attr((string) ((int) $max_pages)); ?>"
|
||||
data-post-id="<?php echo esc_attr((string) $post_id); ?>"
|
||||
data-comment-order="<?php echo esc_attr($comment_order); ?>"
|
||||
data-author-only="<?php echo $author_only ? '1' : '0'; ?>">
|
||||
<?php if ($comment_pagination_mode === 'load_more') : ?>
|
||||
<button type="button" class="aksrt-load-more-btn aksrt-load-more-btn--comment">
|
||||
<span class="aksrt-load-more-btn__text"><?php esc_html_e('加载更多评论', 'aksrt'); ?></span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<div class="aksrt-loading-indicator" aria-hidden="true">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
<span><?php esc_html_e('加载中…', 'aksrt'); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Theme - Template Tags Module
|
||||
*
|
||||
* 模板标签 / 辅助函数集合:
|
||||
* - Banner 幻灯片获取
|
||||
* - 友情链接 / 站点 Logo / Favicon / About 信息
|
||||
* - 社交链接渲染
|
||||
* - 文章分类标签 / 阅读时间 / 评论数
|
||||
* - SEO Head 注入
|
||||
*
|
||||
* @package AKSRT
|
||||
* @version 3.1.0
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_resolve_sidebar_id')):
|
||||
/**
|
||||
* 统一解析当前页面侧栏 ID。
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_resolve_sidebar_id(): string {
|
||||
if (is_single()) {
|
||||
return 'single_sidebar';
|
||||
}
|
||||
|
||||
if (is_category()) {
|
||||
return 'cat_sidebar';
|
||||
}
|
||||
|
||||
if (is_tag()) {
|
||||
return 'tag_sidebar';
|
||||
}
|
||||
|
||||
if (is_search()) {
|
||||
return 'search_sidebar';
|
||||
}
|
||||
|
||||
if (is_author()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (is_page()) {
|
||||
return 'page_sidebar_' . get_queried_object_id();
|
||||
}
|
||||
|
||||
if (is_home() || is_front_page()) {
|
||||
return 'home_sidebar';
|
||||
}
|
||||
|
||||
return 'home_sidebar';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_has_current_sidebar_widgets')):
|
||||
/**
|
||||
* Whether the current page has widgets for its site-sidebar column.
|
||||
*
|
||||
* The global top/bottom zones are rendered alongside the page-specific
|
||||
* sidebar, so any of these zones makes the column meaningful.
|
||||
*/
|
||||
function aksrt_has_current_sidebar_widgets(): bool {
|
||||
$sidebar_id = function_exists('aksrt_resolve_sidebar_id')
|
||||
? aksrt_resolve_sidebar_id()
|
||||
: 'home_sidebar';
|
||||
$sidebar_ids = array_unique(['all_sidebar_top', $sidebar_id, 'all_sidebar_bottom']);
|
||||
|
||||
foreach ($sidebar_ids as $sidebar_id) {
|
||||
if (is_active_sidebar($sidebar_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_post_show_layout')):
|
||||
/**
|
||||
* Zibll-compatible post/page layout override.
|
||||
*
|
||||
* Meta key: `_aksrt_show_layout`
|
||||
* Values: '' (follow) | no_sidebar | sidebar_left | sidebar_right
|
||||
*
|
||||
* @param int|null $post_id Post ID.
|
||||
*/
|
||||
function aksrt_get_post_show_layout($post_id = null): string {
|
||||
$post_id = $post_id ? (int) $post_id : (int) get_queried_object_id();
|
||||
if ($post_id <= 0) {
|
||||
return '';
|
||||
}
|
||||
$value = (string) get_post_meta($post_id, '_aksrt_show_layout', true);
|
||||
return in_array($value, ['no_sidebar', 'sidebar_left', 'sidebar_right'], true) ? $value : '';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_sidebar_config_safe')):
|
||||
/**
|
||||
* @return array{enabled:bool,position:string,width:int,home:bool,category:bool,tag:bool,single:bool,page:bool,search:bool}
|
||||
*/
|
||||
function aksrt_get_sidebar_config_safe(): array {
|
||||
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_sidebar_config')) {
|
||||
$cfg = \AKSRT\Settings\Settings::get_sidebar_config();
|
||||
if (is_array($cfg)) {
|
||||
return $cfg;
|
||||
}
|
||||
}
|
||||
return [
|
||||
'enabled' => true,
|
||||
'position' => 'right',
|
||||
'width' => 311,
|
||||
'home' => true,
|
||||
'category' => true,
|
||||
'tag' => true,
|
||||
'single' => true,
|
||||
'page' => true,
|
||||
'search' => true,
|
||||
];
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_is_show_sidebar')):
|
||||
/**
|
||||
* Zibll-strict sidebar visibility (settings + post meta, not widget presence).
|
||||
*
|
||||
* Filter: aksrt_is_show_sidebar
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function aksrt_is_show_sidebar(): bool {
|
||||
$show = aksrt_is_show_sidebar_filter();
|
||||
return (bool) apply_filters('aksrt_is_show_sidebar', $show);
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_is_show_sidebar_filter')):
|
||||
/**
|
||||
* Core sidebar visibility decision (Zibll zib_is_show_sidebar_filter parity).
|
||||
*/
|
||||
function aksrt_is_show_sidebar_filter(): bool {
|
||||
// Keep the sidebar in the document for every request. wp_is_mobile() is
|
||||
// User-Agent based, so it cannot track a browser viewport changing after
|
||||
// the response is rendered. Responsive CSS controls narrow-screen hiding.
|
||||
|
||||
$cfg = aksrt_get_sidebar_config_safe();
|
||||
if (empty($cfg['enabled'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Single / page: post meta can force show/hide (Zibll show_layout).
|
||||
if (is_singular()) {
|
||||
$layout = aksrt_get_post_show_layout(get_queried_object_id());
|
||||
if ($layout === 'no_sidebar') {
|
||||
return false;
|
||||
}
|
||||
if ($layout === 'sidebar_left' || $layout === 'sidebar_right') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((is_home() || is_front_page()) && empty($cfg['home'])) {
|
||||
return false;
|
||||
}
|
||||
if (is_single() && empty($cfg['single'])) {
|
||||
return false;
|
||||
}
|
||||
if (is_page() && empty($cfg['page'])) {
|
||||
return false;
|
||||
}
|
||||
if (is_category() && empty($cfg['category'])) {
|
||||
return false;
|
||||
}
|
||||
if (is_tag() && empty($cfg['tag'])) {
|
||||
return false;
|
||||
}
|
||||
if (is_search() && empty($cfg['search'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Category/tag term meta can still force off (existing AKSRT contract).
|
||||
if ((is_category() || is_tag()) && class_exists('\AKSRT\Core\TermMeta') && method_exists('\AKSRT\Core\TermMeta', 'resolve_archive_settings')) {
|
||||
$term_id = (int) get_queried_object_id();
|
||||
if ($term_id > 0) {
|
||||
$resolved = \AKSRT\Core\TermMeta::resolve_archive_settings($term_id);
|
||||
if (is_array($resolved) && array_key_exists('show_sidebar', $resolved) && empty($resolved['show_sidebar'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search page display setting can force off.
|
||||
if (is_search() && class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_other_page_settings')) {
|
||||
$other = \AKSRT\Settings\Settings::get_other_page_settings();
|
||||
if (is_array($other) && array_key_exists('page_search_sidebar', $other) && empty($other['page_search_sidebar'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Scene switches above already gated; when enabled, show the column
|
||||
// (widgets may be empty — matches zibll empty sidebar column policy).
|
||||
if (is_home() || is_front_page()) {
|
||||
return !empty($cfg['home']);
|
||||
}
|
||||
if (is_single()) {
|
||||
return !empty($cfg['single']);
|
||||
}
|
||||
if (is_page()) {
|
||||
return !empty($cfg['page']);
|
||||
}
|
||||
if (is_category()) {
|
||||
return !empty($cfg['category']);
|
||||
}
|
||||
if (is_tag()) {
|
||||
return !empty($cfg['tag']);
|
||||
}
|
||||
if (is_search()) {
|
||||
return !empty($cfg['search']);
|
||||
}
|
||||
|
||||
// Author / other archives: follow global enabled.
|
||||
return true;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_site_layout')):
|
||||
/**
|
||||
* Zibll site-layout mode: 1=no sidebar, 2=right, 3=left.
|
||||
*/
|
||||
function aksrt_get_site_layout(): int {
|
||||
// User-center and public-profile routes have their own internal layouts.
|
||||
// They never render the site sidebar, so reserving a site-layout-2/3 column
|
||||
// would leave an empty right/left gutter and compress their content.
|
||||
$is_user_area = (function_exists('aksrt_is_user_center_page') && aksrt_is_user_center_page())
|
||||
|| (function_exists('aksrt_is_user_home_page') && aksrt_is_user_home_page());
|
||||
if ($is_user_area) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (is_single()) {
|
||||
$post_id = (int) get_queried_object_id();
|
||||
$layout = aksrt_get_post_show_layout($post_id);
|
||||
// Keep the sidebar in the document whenever it is configured. CSS handles
|
||||
// narrow viewports; wp_is_mobile() is User-Agent based and can otherwise
|
||||
// suppress widgets on a desktop-sized page.
|
||||
if (get_post_type($post_id) !== 'post' || $layout === 'no_sidebar' || !aksrt_is_show_sidebar()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($layout === 'sidebar_left') {
|
||||
return 3;
|
||||
}
|
||||
if ($layout === 'sidebar_right') {
|
||||
return 2;
|
||||
}
|
||||
|
||||
$cfg = aksrt_get_sidebar_config_safe();
|
||||
return ($cfg['position'] ?? 'right') === 'left' ? 3 : 2;
|
||||
}
|
||||
|
||||
if (!aksrt_is_show_sidebar()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$position = 'right';
|
||||
if (is_singular()) {
|
||||
$meta = aksrt_get_post_show_layout(get_queried_object_id());
|
||||
if ($meta === 'sidebar_left') {
|
||||
$position = 'left';
|
||||
} elseif ($meta === 'sidebar_right') {
|
||||
$position = 'right';
|
||||
} else {
|
||||
$cfg = aksrt_get_sidebar_config_safe();
|
||||
$position = ($cfg['position'] ?? 'right') === 'left' ? 'left' : 'right';
|
||||
}
|
||||
} else {
|
||||
$cfg = aksrt_get_sidebar_config_safe();
|
||||
$position = ($cfg['position'] ?? 'right') === 'left' ? 'left' : 'right';
|
||||
}
|
||||
|
||||
return $position === 'left' ? 3 : 2;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_body_layout_classes')):
|
||||
/**
|
||||
* @return string[] e.g. ['site-layout-2']
|
||||
*/
|
||||
function aksrt_body_layout_classes(): array {
|
||||
$layout = aksrt_get_site_layout();
|
||||
if (!in_array($layout, [1, 2, 3], true)) {
|
||||
$layout = 1;
|
||||
}
|
||||
return ['site-layout-' . $layout];
|
||||
}
|
||||
endif;
|
||||
|
||||
@@ -21,13 +21,9 @@ get_header();
|
||||
|
||||
<?php
|
||||
// Zibll dual-column: sidebar is a sibling of .content-wrap under main.container.
|
||||
// A sidebar column is useful only when visibility is enabled and at least one
|
||||
// of its global/page-specific widget zones has content.
|
||||
$has_sidebar_widgets = function_exists('aksrt_has_current_sidebar_widgets')
|
||||
? aksrt_has_current_sidebar_widgets()
|
||||
: (is_active_sidebar('all_sidebar_top') || is_active_sidebar('home_sidebar') || is_active_sidebar('all_sidebar_bottom'));
|
||||
$has_sidebar = (function_exists('aksrt_is_show_sidebar') ? aksrt_is_show_sidebar() : true)
|
||||
&& $has_sidebar_widgets;
|
||||
// Settings and page metadata own visibility; widget activity never changes
|
||||
// the body layout after render.
|
||||
$has_sidebar = function_exists('aksrt_is_show_sidebar') ? aksrt_is_show_sidebar() : true;
|
||||
?>
|
||||
<main id="main-content" class="site-main container" tabindex="-1">
|
||||
<div class="content-wrap">
|
||||
|
||||
@@ -53,7 +53,7 @@ final class AssetManager {
|
||||
$uri = get_template_directory_uri();
|
||||
$css = $root . '/assets/widgets/frontend.css';
|
||||
$js = $root . '/assets/widgets/frontend.js';
|
||||
wp_register_style(self::STYLE_HANDLE, $uri . '/assets/widgets/frontend.css', [], is_file($css) ? (string) filemtime($css) : null);
|
||||
wp_register_style(self::STYLE_HANDLE, $uri . '/assets/widgets/frontend.css', ['aksrt-style'], is_file($css) ? (string) filemtime($css) : null);
|
||||
wp_register_script(self::SCRIPT_HANDLE, $uri . '/assets/widgets/frontend.js', [], is_file($js) ? (string) filemtime($js) : null, true);
|
||||
if (!wp_style_is('aksrt-swiper', 'registered')) {
|
||||
wp_register_style('aksrt-swiper', $uri . '/assets/css/frontend/swiper-bundle.min.css', [], '11.2.10');
|
||||
|
||||
@@ -23,7 +23,10 @@ final class FrontendRenderer {
|
||||
}
|
||||
|
||||
AssetManager::enqueue_for($definition);
|
||||
$classes = ['aksrt-widget', 'aksrt-widget--' . $definition->slug(), 'aksrt-widget--' . $definition->size()];
|
||||
$classes = ['aksrt-widget', 'zib-widget-wrap', 'aksrt-widget--' . $definition->slug(), 'aksrt-widget--' . $definition->size()];
|
||||
if ($definition->size() === 'sidebar') {
|
||||
$classes[] = 'zib-widget';
|
||||
}
|
||||
$device_class = WidgetVisibility::device_class($instance);
|
||||
if ($device_class !== '') {
|
||||
$classes[] = $device_class;
|
||||
@@ -42,8 +45,14 @@ final class FrontendRenderer {
|
||||
$style = self::style($instance);
|
||||
echo (string) ($args['before_widget'] ?? '<section>'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="' . esc_attr(implode(' ', $classes)) . '" data-aksrt-widget="' . esc_attr($definition->slug()) . '" data-widget-id="' . esc_attr((string) $widget->id) . '"' . ($style !== '' ? ' style="' . esc_attr($style) . '"' : '') . '>';
|
||||
if ($definition->size() !== 'sidebar') {
|
||||
echo '<div class="aksrt-widget__inner container">';
|
||||
}
|
||||
echo self::title($instance); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="aksrt-widget__content">' . $content . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
if ($definition->size() !== 'sidebar') {
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
echo (string) ($args['after_widget'] ?? '</section>'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ final class PostRenderer {
|
||||
'columns' => $mode === 'card' ? 3 : 1,
|
||||
'show_thumb' => $slug !== 'hot_posts' || !empty($instance['show_thumbnail']),
|
||||
'show_meta' => $mode !== 'mini',
|
||||
'show_excerpt' => $mode !== 'mini',
|
||||
'show_excerpt' => $mode === 'list',
|
||||
'show_author' => false,
|
||||
'show_comments' => true,
|
||||
'show_likes' => true,
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"schema": "aksrt.page-visual-matrix.v1",
|
||||
"reference": "https://localhost:9443",
|
||||
"candidate": "https://localhost",
|
||||
"viewports": {
|
||||
"desktop": [1440, 1100],
|
||||
"tablet": [1024, 1000],
|
||||
"mobile": [390, 844]
|
||||
},
|
||||
"themes": ["light", "dark"],
|
||||
"identities": ["guest", "member", "vip"],
|
||||
"routes": [
|
||||
{"id": "home", "path": "/", "states": ["default", "hover", "loading", "error"]},
|
||||
{"id": "archive", "path": "/?post_type=post", "states": ["default", "numbered", "load-more", "infinite"]},
|
||||
{"id": "category", "path": "/category/aksrt_zibll_v1_101/", "states": ["default", "empty"]},
|
||||
{"id": "tag", "path": "/tag/aksrt_zibll_v1_103/", "states": ["default", "empty"]},
|
||||
{"id": "search", "path": "/?s=AKSRT", "states": ["default", "empty", "focus"]},
|
||||
{"id": "single", "path": "/aksrt_zibll_v1_301/", "states": ["default", "hover", "toc-open"]},
|
||||
{"id": "resource", "path": "/aksrt_zibll_v1_303/", "states": ["locked", "authorized", "download"]},
|
||||
{"id": "page", "path": "/sample-page/", "states": ["default"]},
|
||||
{"id": "login", "path": "/wp-login.php", "states": ["default", "focus", "error"]},
|
||||
{"id": "user", "path": "/user/", "states": ["guest", "member", "vip"]},
|
||||
{"id": "orders", "path": "/user/orders/", "states": ["empty", "populated"]},
|
||||
{"id": "vip", "path": "/user/vip/", "states": ["member", "vip"]},
|
||||
{"id": "credits", "path": "/user/credits/", "states": ["member", "loading", "error"]}
|
||||
],
|
||||
"acceptance": {
|
||||
"dimension_delta_px_max": 2,
|
||||
"pixel_difference_percent_max": 1.5,
|
||||
"console_errors_max": 0,
|
||||
"unexpected_network_failures_max": 0,
|
||||
"a11y_critical_max": 0,
|
||||
"a11y_serious_max": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# AKSRT / Zibll visual reference
|
||||
|
||||
The reference stack is isolated from the AKSRT site and exposes Zibll at
|
||||
https://localhost:9443. The local Zibll source remains read-only.
|
||||
|
||||
~~~powershell
|
||||
$env:ZIBLL_THEME_PATH='C:\Users\Xing\Desktop\zibll'
|
||||
docker compose -f tests/visual/docker-compose.zibll.yml up -d
|
||||
docker compose -f tests/visual/docker-compose.zibll.yml run --rm wp core install --url=https://localhost:9443 --title='Zibll Reference' --admin_user=visual_admin --admin_password='AKSRT-Visual-2026!' --admin_email=visual@example.test --skip-email
|
||||
docker compose -f tests/visual/docker-compose.zibll.yml run --rm wp theme activate zibll
|
||||
~~~
|
||||
|
||||
Create or clean AKSRT fixtures inside its WordPress container:
|
||||
|
||||
~~~powershell
|
||||
wp eval-file wp-content/themes/AKSRT-theme/tools/wp-cli-visual-fixtures.php create
|
||||
wp eval-file wp-content/themes/AKSRT-theme/tools/wp-cli-visual-fixtures.php cleanup
|
||||
~~~
|
||||
|
||||
Use tests/fixtures/widget-golden-v1.json for the 222 component captures and
|
||||
tests/fixtures/page-visual-matrix-v1.json for page-level captures. Remove
|
||||
the reference stack with docker compose down -v only after fixture cleanup
|
||||
and screenshot verification.
|
||||
@@ -0,0 +1,70 @@
|
||||
name: aksrt-zibll-visual-reference
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mariadb:11.4
|
||||
environment:
|
||||
MARIADB_DATABASE: zibll_reference
|
||||
MARIADB_USER: zibll
|
||||
MARIADB_PASSWORD: zibll
|
||||
MARIADB_RANDOM_ROOT_PASSWORD: "1"
|
||||
volumes:
|
||||
- zibll_db:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
|
||||
wordpress:
|
||||
image: wordpress:php8.1-apache
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
WORDPRESS_DB_HOST: db
|
||||
WORDPRESS_DB_USER: zibll
|
||||
WORDPRESS_DB_PASSWORD: zibll
|
||||
WORDPRESS_DB_NAME: zibll_reference
|
||||
volumes:
|
||||
- zibll_wp:/var/www/html
|
||||
- type: bind
|
||||
source: ${ZIBLL_THEME_PATH}
|
||||
target: /var/www/html/wp-content/themes/zibll
|
||||
read_only: true
|
||||
|
||||
caddy:
|
||||
image: caddy:2.8-alpine
|
||||
depends_on:
|
||||
- wordpress
|
||||
ports:
|
||||
- "9443:443"
|
||||
volumes:
|
||||
- ./zibll-Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- zibll_caddy_data:/data
|
||||
|
||||
wp:
|
||||
image: wordpress:cli-php8.1
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
wordpress:
|
||||
condition: service_started
|
||||
user: "33:33"
|
||||
environment:
|
||||
WORDPRESS_DB_HOST: db
|
||||
WORDPRESS_DB_USER: zibll
|
||||
WORDPRESS_DB_PASSWORD: zibll
|
||||
WORDPRESS_DB_NAME: zibll_reference
|
||||
volumes:
|
||||
- zibll_wp:/var/www/html
|
||||
- type: bind
|
||||
source: ${ZIBLL_THEME_PATH}
|
||||
target: /var/www/html/wp-content/themes/zibll
|
||||
read_only: true
|
||||
entrypoint: ["wp"]
|
||||
|
||||
volumes:
|
||||
zibll_db:
|
||||
zibll_wp:
|
||||
zibll_caddy_data:
|
||||
@@ -0,0 +1,4 @@
|
||||
https://localhost {
|
||||
tls internal
|
||||
reverse_proxy wordpress:80
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('post list DOM follows the Zibll flex and multi-image contract', () => {
|
||||
const markup = read('inc/helpers/posts-renderer/markup.php');
|
||||
const data = read('inc/helpers/posts-renderer/post-data.php');
|
||||
const cardStart = markup.indexOf('function aksrt_renderer_card');
|
||||
const miniStart = markup.indexOf('function aksrt_renderer_mini');
|
||||
const card = markup.slice(cardStart, miniStart);
|
||||
|
||||
assert.match(markup, /\['posts-item', 'list', 'ajax-item', 'flex'/);
|
||||
assert.match(markup, /if \(!empty\(\$data\['is_mult_thumb'\]\)\)[\s\S]*?\$thumb = '';/);
|
||||
assert.doesNotMatch(card, /item-excerpt|renderer_excerpt_markup/);
|
||||
assert.match(data, /&& \$cfg\['mode'\] === 'list'/);
|
||||
assert.match(data, /<a class=\"thumb-items contain\"/);
|
||||
});
|
||||
|
||||
test('site shell has one float owner and fluid zones sit outside main', () => {
|
||||
const layout = read('assets/css/frontend/layout.css');
|
||||
const archive = read('inc/helpers/aksrt-archive.php');
|
||||
const shell = layout.slice(layout.indexOf('Site shell'), layout.indexOf('页面标题区'));
|
||||
|
||||
assert.doesNotMatch(shell, /display:\s*grid/);
|
||||
assert.match(shell, /width:\s*var\(--sidebar-width, 311px\)/);
|
||||
assert.match(shell, /calc\(var\(--sidebar-width, 311px\) \+ 15px\)/);
|
||||
assert.ok(archive.indexOf("$args['top_fluid']") < archive.indexOf('<main id="main-content"'));
|
||||
assert.ok(archive.lastIndexOf("$args['bottom_fluid']") > archive.indexOf('</main>'));
|
||||
});
|
||||
|
||||
test('widget shell uses Zibll wrappers without overriding post link colors', () => {
|
||||
const renderer = read('src/WidgetSystem/FrontendRenderer.php');
|
||||
const css = read('assets/widgets/frontend.css');
|
||||
|
||||
assert.match(renderer, /'zib-widget-wrap'/);
|
||||
assert.match(renderer, /\$classes\[\] = 'zib-widget'/);
|
||||
assert.match(renderer, /aksrt-widget__inner container/);
|
||||
assert.doesNotMatch(css, /\.aksrt-widget a\s*\{[^}]*color:\s*inherit/);
|
||||
assert.match(css, /\.aksrt-widget\.zib-widget\s*\{/);
|
||||
});
|
||||
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
/**
|
||||
* Reversible WP-CLI visual fixtures for AKSRT/Zibll comparison.
|
||||
*
|
||||
* Usage:
|
||||
* wp eval-file wp-content/themes/AKSRT-theme/tools/wp-cli-visual-fixtures.php create
|
||||
* wp eval-file wp-content/themes/AKSRT-theme/tools/wp-cli-visual-fixtures.php cleanup
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!defined('WP_CLI') || !WP_CLI) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fixture_action = sanitize_key((string) ($args[0] ?? ''));
|
||||
$fixture_option = 'aksrt_visual_fixture_manifest_v1';
|
||||
$fixture_prefix = 'aksrt_zibll_v1_';
|
||||
$fixture_theme_dir = dirname(__DIR__);
|
||||
$fixture_json = $fixture_theme_dir . '/tests/fixtures/widget-golden-v1.json';
|
||||
|
||||
if (!in_array($fixture_action, ['create', 'cleanup'], true)) {
|
||||
WP_CLI::error('Expected create or cleanup.');
|
||||
}
|
||||
|
||||
$cleanup = static function (array $manifest) use ($fixture_option): void {
|
||||
$sidebars = wp_get_sidebars_widgets();
|
||||
foreach ((array) ($manifest['widgets'] ?? []) as $widget) {
|
||||
$id_base = sanitize_key((string) ($widget['id_base'] ?? ''));
|
||||
$number = (int) ($widget['number'] ?? 0);
|
||||
if ($id_base === '' || $number <= 0) {
|
||||
continue;
|
||||
}
|
||||
$widget_id = $id_base . '-' . $number;
|
||||
foreach ($sidebars as $area => $ids) {
|
||||
if (!is_array($ids)) {
|
||||
continue;
|
||||
}
|
||||
$sidebars[$area] = array_values(array_diff($ids, [$widget_id]));
|
||||
}
|
||||
$option_name = 'widget_' . $id_base;
|
||||
$instances = get_option($option_name, []);
|
||||
if (is_array($instances) && array_key_exists($number, $instances)) {
|
||||
unset($instances[$number]);
|
||||
update_option($option_name, $instances, false);
|
||||
}
|
||||
}
|
||||
wp_set_sidebars_widgets($sidebars);
|
||||
|
||||
foreach (array_reverse((array) ($manifest['comments'] ?? [])) as $id) {
|
||||
wp_delete_comment((int) $id, true);
|
||||
}
|
||||
foreach (array_reverse((array) ($manifest['posts'] ?? [])) as $id) {
|
||||
wp_delete_post((int) $id, true);
|
||||
}
|
||||
foreach (array_reverse((array) ($manifest['attachments'] ?? [])) as $id) {
|
||||
wp_delete_attachment((int) $id, true);
|
||||
}
|
||||
foreach (array_reverse((array) ($manifest['users'] ?? [])) as $id) {
|
||||
if (function_exists('wp_delete_user')) {
|
||||
wp_delete_user((int) $id);
|
||||
}
|
||||
}
|
||||
foreach (array_reverse((array) ($manifest['terms'] ?? [])) as $term) {
|
||||
wp_delete_term((int) ($term['id'] ?? 0), (string) ($term['taxonomy'] ?? 'category'));
|
||||
}
|
||||
delete_option($fixture_option);
|
||||
};
|
||||
|
||||
$existing = get_option($fixture_option, []);
|
||||
if ($fixture_action === 'cleanup') {
|
||||
if (!is_array($existing) || empty($existing)) {
|
||||
WP_CLI::success('No AKSRT visual fixtures found.');
|
||||
return;
|
||||
}
|
||||
$cleanup($existing);
|
||||
WP_CLI::success('AKSRT visual fixtures removed.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_array($existing) && !empty($existing)) {
|
||||
WP_CLI::error('Fixtures already exist. Run cleanup first.');
|
||||
}
|
||||
if (!is_file($fixture_json)) {
|
||||
WP_CLI::error('Golden fixture JSON is missing.');
|
||||
}
|
||||
|
||||
$golden = json_decode((string) file_get_contents($fixture_json), true);
|
||||
if (!is_array($golden)) {
|
||||
WP_CLI::error('Golden fixture JSON is invalid.');
|
||||
}
|
||||
|
||||
$manifest = [
|
||||
'prefix' => $fixture_prefix,
|
||||
'created_at' => gmdate('c'),
|
||||
'terms' => [],
|
||||
'users' => [],
|
||||
'attachments' => [],
|
||||
'posts' => [],
|
||||
'comments' => [],
|
||||
'widgets' => [],
|
||||
];
|
||||
update_option($fixture_option, $manifest, false);
|
||||
|
||||
try {
|
||||
$term_map = [];
|
||||
foreach ((array) ($golden['dataset']['terms'] ?? []) as $source) {
|
||||
$taxonomy = (string) ($source['taxonomy'] ?? 'category');
|
||||
$result = wp_insert_term(
|
||||
(string) ($source['name'] ?? 'Fixture'),
|
||||
$taxonomy,
|
||||
['slug' => $fixture_prefix . (int) ($source['id'] ?? 0)]
|
||||
);
|
||||
if (is_wp_error($result)) {
|
||||
throw new RuntimeException($result->get_error_message());
|
||||
}
|
||||
$term_id = (int) $result['term_id'];
|
||||
$term_map[(int) $source['id']] = $term_id;
|
||||
$manifest['terms'][] = ['id' => $term_id, 'taxonomy' => $taxonomy];
|
||||
}
|
||||
|
||||
$user_map = [];
|
||||
foreach ((array) ($golden['dataset']['users'] ?? []) as $index => $source) {
|
||||
$login = $fixture_prefix . 'user_' . ((int) $index + 1);
|
||||
$user_id = wp_insert_user([
|
||||
'user_login' => $login,
|
||||
'user_pass' => 'AKSRT-Visual-2026!',
|
||||
'user_email' => $login . '@example.test',
|
||||
'display_name' => (string) ($source['name'] ?? $login),
|
||||
'role' => 'subscriber',
|
||||
]);
|
||||
if (is_wp_error($user_id)) {
|
||||
throw new RuntimeException($user_id->get_error_message());
|
||||
}
|
||||
$user_id = (int) $user_id;
|
||||
$user_map[(int) ($source['id'] ?? 0)] = $user_id;
|
||||
$manifest['users'][] = $user_id;
|
||||
update_user_meta($user_id, 'aksrt_points', (int) ($source['points'] ?? 0));
|
||||
if ($index === 0) {
|
||||
update_user_meta($user_id, 'aksrt_vip_level', 2);
|
||||
update_user_meta($user_id, 'aksrt_vip_expires', time() + YEAR_IN_SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||
$media_sources = [
|
||||
$fixture_theme_dir . '/assets/img/slider-bg.jpg',
|
||||
$fixture_theme_dir . '/assets/img/user_t.jpg',
|
||||
$fixture_theme_dir . '/assets/img/user-level-7.png',
|
||||
];
|
||||
foreach ($media_sources as $source_path) {
|
||||
if (!is_file($source_path)) {
|
||||
continue;
|
||||
}
|
||||
$upload = wp_upload_dir();
|
||||
$filename = wp_unique_filename($upload['path'], $fixture_prefix . basename($source_path));
|
||||
$target = trailingslashit($upload['path']) . $filename;
|
||||
if (!copy($source_path, $target)) {
|
||||
throw new RuntimeException('Unable to copy fixture media.');
|
||||
}
|
||||
$attachment_id = wp_insert_attachment([
|
||||
'post_mime_type' => wp_check_filetype($target)['type'],
|
||||
'post_title' => $fixture_prefix . pathinfo($filename, PATHINFO_FILENAME),
|
||||
'post_status' => 'inherit',
|
||||
], $target);
|
||||
if (is_wp_error($attachment_id)) {
|
||||
throw new RuntimeException($attachment_id->get_error_message());
|
||||
}
|
||||
$attachment_id = (int) $attachment_id;
|
||||
wp_update_attachment_metadata($attachment_id, wp_generate_attachment_metadata($attachment_id, $target));
|
||||
$manifest['attachments'][] = $attachment_id;
|
||||
}
|
||||
|
||||
$post_map = [];
|
||||
foreach ((array) ($golden['dataset']['posts'] ?? []) as $index => $source) {
|
||||
$post_id = wp_insert_post([
|
||||
'post_title' => $fixture_prefix . (string) ($source['title'] ?? 'Fixture post'),
|
||||
'post_name' => $fixture_prefix . (int) ($source['id'] ?? $index),
|
||||
'post_status' => 'publish',
|
||||
'post_type' => 'post',
|
||||
'post_author' => (int) ($user_map[(int) ($source['author'] ?? 0)] ?? 1),
|
||||
'post_excerpt' => '用于 AKSRT 与 Zibll 同内容视觉比较的固定摘要。',
|
||||
'post_content' => '<h2>视觉基线标题</h2><p>固定正文内容,用于检查排版、目录、付费状态和响应式布局。</p>',
|
||||
], true);
|
||||
if (is_wp_error($post_id)) {
|
||||
throw new RuntimeException($post_id->get_error_message());
|
||||
}
|
||||
$post_id = (int) $post_id;
|
||||
$post_map[(int) ($source['id'] ?? 0)] = $post_id;
|
||||
$manifest['posts'][] = $post_id;
|
||||
$category_id = (int) ($term_map[(int) ($source['category'] ?? 0)] ?? 0);
|
||||
if ($category_id > 0) {
|
||||
wp_set_post_terms($post_id, [$category_id], 'category');
|
||||
}
|
||||
if (!empty($manifest['attachments'])) {
|
||||
set_post_thumbnail($post_id, (int) $manifest['attachments'][$index % count($manifest['attachments'])]);
|
||||
}
|
||||
$pay_type = sanitize_key((string) ($source['pay_type'] ?? 'no'));
|
||||
$pay = class_exists('AKSRT\\Core\\DomainModel') ? \AKSRT\Core\DomainModel::default_pay_meta() : [];
|
||||
$pay['pay_type'] = $pay_type;
|
||||
$pay['price'] = $pay_type === 'no' ? 0 : 19.9 + $index;
|
||||
$pay['pay_limit'] = $pay_type === 'vip' ? 1 : 0;
|
||||
update_post_meta($post_id, '_aksrt_pay', $pay);
|
||||
update_post_meta($post_id, '_aksrt_pay_type', $pay_type);
|
||||
update_post_meta($post_id, '_aksrt_pay_price', $pay['price']);
|
||||
update_post_meta($post_id, 'aksrt_post_views', 1200 + ($index * 137));
|
||||
}
|
||||
|
||||
$no_image = wp_insert_post([
|
||||
'post_title' => $fixture_prefix . '无图长标题文章用于检查纯文字模式和两行截断行为',
|
||||
'post_name' => $fixture_prefix . 'no-image',
|
||||
'post_status' => 'publish',
|
||||
'post_type' => 'post',
|
||||
'post_content' => '<p>无缩略图场景。</p>',
|
||||
], true);
|
||||
if (!is_wp_error($no_image)) {
|
||||
$manifest['posts'][] = (int) $no_image;
|
||||
}
|
||||
if (!empty($manifest['posts'])) {
|
||||
stick_post((int) $manifest['posts'][0]);
|
||||
}
|
||||
|
||||
$components = (array) ($golden['components'] ?? []);
|
||||
$sidebars = wp_get_sidebars_widgets();
|
||||
$sidebars['home_top_content'] = is_array($sidebars['home_top_content'] ?? null) ? $sidebars['home_top_content'] : [];
|
||||
$sidebars['home_sidebar'] = is_array($sidebars['home_sidebar'] ?? null) ? $sidebars['home_sidebar'] : [];
|
||||
$number = 9100;
|
||||
foreach ($components as $slug => $component) {
|
||||
$id_base = sanitize_key((string) ($component['id_base'] ?? ''));
|
||||
if ($id_base === '') {
|
||||
continue;
|
||||
}
|
||||
$number++;
|
||||
$settings = (array) ($component['settings'] ?? []);
|
||||
$settings['title'] = $fixture_prefix . (string) ($settings['title'] ?? $slug);
|
||||
$option_name = 'widget_' . $id_base;
|
||||
$instances = get_option($option_name, []);
|
||||
$instances = is_array($instances) ? $instances : [];
|
||||
$instances[$number] = $settings;
|
||||
$instances['_multiwidget'] = 1;
|
||||
update_option($option_name, $instances, false);
|
||||
$widget_id = $id_base . '-' . $number;
|
||||
$area = (($component['size'] ?? '') === 'sidebar') ? 'home_sidebar' : 'home_top_content';
|
||||
$sidebars[$area][] = $widget_id;
|
||||
$manifest['widgets'][] = ['id_base' => $id_base, 'number' => $number, 'area' => $area];
|
||||
}
|
||||
wp_set_sidebars_widgets($sidebars);
|
||||
update_option($fixture_option, $manifest, false);
|
||||
WP_CLI::success('Created reversible AKSRT visual fixtures. Login password: AKSRT-Visual-2026!');
|
||||
} catch (Throwable $error) {
|
||||
$cleanup($manifest);
|
||||
WP_CLI::error('Fixture creation rolled back: ' . $error->getMessage());
|
||||
}
|
||||
Reference in New Issue
Block a user