fix(theme): align reference layouts and restore passing contracts
Align homepage and category discovery, modern widgets, admin controls, quotes, and comments with the reference theme. Remove superseded paths and add rendering and behavior coverage. Update stale asset and finance contracts; all 760 Node tests and 139 widget assertions pass.
This commit is contained in:
1 parent
b520d29368
commit
8aa116ec43
63 files changed
+1936
-2255
No files matched your search
@@ -146,7 +146,7 @@ export const pageSchema = {
|
||||
key: 'yiyan_intro',
|
||||
type: 'content',
|
||||
ui_only: true,
|
||||
content: '<p class="aksrt-hint"><b>精彩一言功能:</b>随机显示中英文的文案,在此支持一言内容插入到文章页位置,同时可以使用小工具模块调用。<br/>主题默认调用的文件为:<code>themes/aksrt/yiyan/qv-yiyan.txt</code><br/>您可以将以上文件复制到 <code>wp-content</code> 文件夹,并重命名为 <code>yiyan.txt</code>,再进行修改或添加内容,则今后更新主题就无需二次修改</p>',
|
||||
content: '<p class="aksrt-hint"><b>精彩一言功能:</b>随机显示中英文文案,可插入文章页或添加“一言”小工具。默认句库位于 <code>themes/aksrt/yiyan/qv-yiyan.txt</code>。您也可以将自定义句库保存为 <code>wp-content/yiyan.txt</code>,更新主题时即可保留自定义内容。</p>',
|
||||
},
|
||||
{ key: 'yiyan_single_content_header', label: '文章内容头部显示一言', type: 'switch', default: false },
|
||||
{ key: 'yiyan_single_content_footer', label: '文章内容尾部显示一言', type: 'switch', default: false },
|
||||
|
||||
@@ -886,336 +886,16 @@ ul.breadcrumb > li:last-child,
|
||||
}
|
||||
|
||||
/* source: assets/css/frontend/homepage-fixed/base.css */
|
||||
/**
|
||||
* Homepage Fixed Layout — legacy Visual Parity
|
||||
*
|
||||
* Implements legacy's homepage visual language:
|
||||
* - System font stack with Chinese fallbacks
|
||||
* - 1200px container, 311px sidebar
|
||||
* - Post cards: list mode (190px thumb) + card mode (3-col grid)
|
||||
* - legacy shadow/radius/spacing values
|
||||
* - scale(1.02) thumbnail hover
|
||||
* - Lazy-load animations (blur/fade/scale)
|
||||
* - Full responsive: 1200/991/767/640px breakpoints
|
||||
*
|
||||
* Tab nav / tab panes / sort menu use the legacy class contract
|
||||
* (.index-tab, .ajax-option, .ajaxpager) styled by assets/css/main.css.
|
||||
*/
|
||||
|
||||
/* ================================================================
|
||||
1. HOMEPAGE CONTAINER & TYPOGRAPHY
|
||||
================================================================ */
|
||||
|
||||
/** Homepage inherits the reference navigation, widgets and pagination from main.css. */
|
||||
.aksrt-homepage {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
line-height: 1.42857143;
|
||||
color: var(--main-color, #4e5358);
|
||||
background: transparent;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/* Slightly looser than stock legacy short pages: air between stream and footer */
|
||||
padding-bottom: 28px;
|
||||
}
|
||||
|
||||
.aksrt-homepage a[href] {
|
||||
transition: color 0.2s, background 0.3s, opacity 0.3s, box-shadow 0.3s, transform 0.3s;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
2. LAYOUT SHELL
|
||||
================================================================ */
|
||||
|
||||
.aksrt-homepage__fluid-zone.content-shell,
|
||||
.aksrt-homepage__shell.content-shell {
|
||||
max-width: min(100%, var(--container, 1200px));
|
||||
}
|
||||
|
||||
.aksrt-homepage__fluid-zone.content-shell {
|
||||
padding-top: 18px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.aksrt-homepage__shell.content-shell {
|
||||
padding-top: 0;
|
||||
/* 36px + a bit more breathing room before footer */
|
||||
padding-bottom: 56px;
|
||||
}
|
||||
|
||||
/* Bottom fluid zone (if any) keeps a little gap above footer */
|
||||
.aksrt-homepage__fluid-zone--bottom {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.aksrt-homepage {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.aksrt-homepage__shell.content-shell {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dual-column geometry is owned by body.site-layout-* float rules in layout.css. */
|
||||
.aksrt-homepage__content-layout {
|
||||
display: block;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.aksrt-homepage__fluid-zone.content-shell,
|
||||
.aksrt-homepage__shell.content-shell {
|
||||
max-width: 95%;
|
||||
}
|
||||
color: var(--main-color);
|
||||
}
|
||||
|
||||
.aksrt-homepage__content-layout,
|
||||
.aksrt-homepage__content-zone,
|
||||
.aksrt-homepage__fluid-zone,
|
||||
.aksrt-homepage__aside {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aksrt-homepage__content-zone .widget-card:last-child,
|
||||
.aksrt-homepage__aside .widget:last-child,
|
||||
.aksrt-homepage__fluid-zone .widget-fluid:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
3. FLUID WIDGETS
|
||||
================================================================ */
|
||||
|
||||
.aksrt-homepage__fluid-zone .widget-fluid {
|
||||
margin-bottom: 20px;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.aksrt-homepage .widget-fluid.widget_aksrt_tab_posts,
|
||||
.aksrt-homepage .widget-fluid.widget_aksrt_category_block,
|
||||
.aksrt-homepage .widget-fluid.widget_aksrt_category_tabs,
|
||||
.aksrt-homepage .widget-fluid.widget_aksrt_category_recommend {
|
||||
padding: 15px;
|
||||
background: var(--surface-1, #fff);
|
||||
border: none;
|
||||
border-radius: var(--main-radius, var(--radius, 8px));
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.aksrt-homepage__content-zone .widget-card,
|
||||
.aksrt-homepage__aside .widget {
|
||||
background: var(--surface-1, #fff);
|
||||
padding: 15px;
|
||||
border: none;
|
||||
border-radius: var(--main-radius, var(--radius, 8px));
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
4. STREAM TITLE BOX (legacy title-theme / 第N页)
|
||||
================================================================ */
|
||||
|
||||
.title-theme {
|
||||
position: relative;
|
||||
padding-left: 1.2em;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.title-theme::before {
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
bottom: 10%;
|
||||
left: 2px;
|
||||
width: 4px;
|
||||
content: "";
|
||||
background: var(--color-primary);
|
||||
border-radius: 5px;
|
||||
box-shadow: 1px 1px 3px -1px var(--color-primary);
|
||||
}
|
||||
|
||||
.flex {
|
||||
/* No !important: the site-layout-1 / fluid-widget-wrap wide-mode rule
|
||||
(.posts-item:not(.card) { display: inline-flex }) must out-rank this
|
||||
utility or the no-sidebar two-column pairing collapses back to
|
||||
half-width stacked rows. */
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex.xx,
|
||||
.xx {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex1,
|
||||
.flex-1 {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.jsb,
|
||||
.jb {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ac {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
5. PAGINATION
|
||||
================================================================ */
|
||||
|
||||
.aksrt-homepage .navigation.pagination .nav-links,
|
||||
.aksrt-homepage .pagination {
|
||||
gap: 0;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
.aksrt-homepage .navigation.pagination .page-numbers,
|
||||
.aksrt-homepage .pagination .page-numbers {
|
||||
min-width: 34px;
|
||||
height: 34px;
|
||||
padding: 0 10px;
|
||||
margin: 3px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-xs, 3px);
|
||||
background: var(--surface-4, #eee);
|
||||
color: var(--color-ink-3, #777);
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.aksrt-homepage .navigation.pagination .page-numbers:hover,
|
||||
.aksrt-homepage .pagination .page-numbers:hover {
|
||||
opacity: 1;
|
||||
background: var(--color-primary-soft);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.aksrt-homepage .navigation.pagination .current,
|
||||
.aksrt-homepage .pagination .current {
|
||||
background: var(--color-primary, var(--color-primary)) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
6. SIDEBAR
|
||||
================================================================ */
|
||||
|
||||
.aksrt-homepage__aside > .aksrt-panel,
|
||||
.aksrt-homepage__aside > .widget {
|
||||
margin-bottom: 15px;
|
||||
padding: 15px;
|
||||
background: var(--surface-1, #fff);
|
||||
border: 0;
|
||||
border-radius: var(--main-radius, var(--radius, 8px));
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside > .aksrt-panel:last-child,
|
||||
.aksrt-homepage__aside > .widget:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget-list li,
|
||||
.aksrt-homepage__aside .widget_recent_entries li,
|
||||
.aksrt-homepage__aside .recent-posts-row {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget-title,
|
||||
.aksrt-homepage__content-zone .widget-title {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget-title {
|
||||
position: relative;
|
||||
margin: 0 0 10px;
|
||||
padding-left: 1.2em;
|
||||
padding-bottom: 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget-title::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
bottom: 10%;
|
||||
left: 2px;
|
||||
width: 4px;
|
||||
height: auto;
|
||||
background: var(--color-primary, var(--color-primary));
|
||||
border-radius: 5px;
|
||||
box-shadow: 1px 1px 3px -1px var(--color-primary, var(--color-primary));
|
||||
transition: transform 0.4s ease;
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget_recent_entries li a,
|
||||
.aksrt-homepage__aside .widget-list li a {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget_recent_entries li {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget_search .search-field {
|
||||
height: 40px;
|
||||
border-radius: 30px;
|
||||
background: var(--surface-2, #eee);
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget_search .search-submit {
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border-radius: 30px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .tag-cloud {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .tag-cloud a {
|
||||
padding: 4px 10px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
border: 0;
|
||||
background: var(--surface-2, #eee);
|
||||
transition: background 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .tag-cloud a:hover {
|
||||
background: var(--color-primary, var(--color-primary));
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.aksrt-homepage .widget.notop,
|
||||
.aksrt-homepage .box-body.notop {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.aksrt-homepage .widget.nobottom,
|
||||
.aksrt-homepage .box-body.nobottom {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.aksrt-homepage .widget.widget_custom_html {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
.posts-row{margin-right:-8px;margin-bottom:15px;margin-left:-8px}.posts-row>*:not(.post_ajax_loader){margin-right:8px!important;margin-left:8px!important}.posts-row>.post_ajax_loader .posts-item,.posts-row>.post_ajax_loader .posts-mini{margin-right:8px;margin-left:8px}.posts-item{margin:15px 0;padding:20px;overflow:hidden;background:var(--main-bg-color, #fff);border-radius:var(--main-radius, 8px);box-shadow:0 0 10px var(--main-shadow, rgba(116, 116, 116, .08));transition:.2s}.posts-item.flex,.posts-item .flex,.posts-mini .flex{display:flex}.posts-item .flex.xx,.posts-mini .flex.xx{flex-direction:column}.posts-item .flex.ac,.posts-mini .flex.ac{align-items:center}.posts-item .flex.jsb,.posts-mini .flex.jsb{justify-content:space-between}.posts-item .flex1,.posts-mini .flex1{flex:1;overflow:hidden}.posts-item .post-graphic:not(.order1){margin-right:20px}.posts-item .order1.post-graphic{margin-right:0;margin-left:20px}.posts-item .item-thumbnail{position:relative;display:block;width:190px;height:0;padding-bottom:var(--posts-list-scale, 70%);overflow:hidden;border-radius:var(--main-radius, 8px)}.item-thumbnail.contain img{object-fit:contain}.swiper-slide.gradient-bg .dplayer-poster img,.swiper-slide.gradient-bg>*>img{object-fit:contain!important;border-radius:0!important}.item-thumbnail .swiper-wrapper{height:100%!important}.list .item-thumbnail .aksrt-slider,.list .item-thumbnail.new-swiper{position:absolute;width:100%;height:100%;margin:0!important}.item-thumbnail .dplayer.dplayer-thumb{position:absolute;inset:0;z-index:1;padding-bottom:var(--posts-list-scale, 70%);background:#000;opacity:0;transition:opacity .3s}.card .item-thumbnail .dplayer.dplayer-thumb{padding-bottom:var(--posts-card-scale, 70%)}.item-thumbnail .dplayer-thumb video,.item-thumbnail .swiper-wrapper,.item-thumbnail img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}.item-thumbnail .swiper-pagination{right:0!important;bottom:0!important;left:0!important;padding:0 2px!important;text-align:center!important}.posts-item.thumb-dplayer-playing .dplayer-thumb{opacity:1}.posts-item.thumb-dplayer-playing .abs-center{opacity:0}.posts-item .item-heading{display:-webkit-box;max-height:2.8em;margin-top:0;margin-bottom:5px;overflow:hidden;font-size:18px;line-height:1.4em;white-space:normal;-webkit-box-orient:vertical;-webkit-line-clamp:2}.font-bold{font-weight:700}.list.style2 .item-thumbnail{width:130px}.list.style2 .item-heading{display:unset;max-height:1.4em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.posts-item .item-heading>a{color:var(--key-color, #333)}.posts-item .item-heading>a:hover{color:var(--focus-color, #f04494)}.posts-item .item-excerpt{margin:0 0 6px;overflow:hidden;color:var(--muted-color, #777);text-overflow:ellipsis;white-space:nowrap}.posts-item .item-meta item{margin-right:8px;font-size:13px}.posts-item .item-meta svg.icon,.posts-mini .item-meta svg.icon{display:inline-block;width:1em;height:1em;margin-right:2px;fill:currentColor;overflow:hidden;vertical-align:-.15em}.posts-item .meta-right item:last-child{margin-right:0}.item-meta .avatar-mini{width:20px;height:20px;border-radius:50%;transform:translateY(-1px)}.item-meta .meta-pay{padding:0 4px}.item-meta .meta-pay img{width:1em;height:1em;object-fit:contain;vertical-align:-.15em}.posts-item .item-meta a,.posts-mini .item-meta a{color:inherit}.posts-item .item-tags a{margin-right:5px;padding:2px 5px;font-size:11px}.posts-item .item-tags a .fa{font-size:1em}.posts-item.card{display:inline-block;width:calc(33.333% - 16px);margin:8px;padding:10px;vertical-align:top;transition:.3s}.posts-item.card:last-child{margin-bottom:20px}.posts-item.card .item-body{width:100%;margin:10px 0 0;padding:0}.posts-item.card .item-meta item{font-size:12px}.posts-mini-con .item-heading,.posts-item.card .item-heading{font-size:14px}.posts-item.card .item-heading{min-height:2.8em}.posts-item.card .item-tags{min-height:23px}.posts-item.card .item-thumbnail{width:100%;padding-bottom:var(--posts-card-scale, 70%)}.posts-item.card.style2{background:transparent;box-shadow:none}.posts-item.card.style2:hover{background:var(--main-bg-color, #fff);box-shadow:0 0 10px var(--main-shadow, rgba(116, 116, 116, .08))}.posts-item.card.style3{padding:0}.posts-item.card.style3 .item-thumbnail,.posts-item.card.style3 .item-thumbnail img{border-radius:var(--main-radius, 8px) var(--main-radius, 8px) 0 0}.posts-item.card.style3 .item-body{margin:0;padding:10px}.posts-item.mult-thumb .thumb-items{margin:0 -3px 6px}.posts-item.mult-thumb .thumb-items>span{position:relative;display:inline-block;width:calc(25% - 6px);height:0;margin:0 3px;padding-bottom:calc(var(--posts-list-scale, 70%) / 4)}.posts-item.mult-thumb .thumb-items>span>img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}.posts-item.mult-thumb .thumb-items.contain>span{overflow:hidden;background:var(--main-border-color, rgba(50, 50, 50, .06));border-radius:var(--main-radius, 8px)}.posts-item.mult-thumb .thumb-items.contaiLine truncated
|
||||
.posts-row{margin-right:-8px;margin-bottom:15px;margin-left:-8px}.posts-row>*:not(.post_ajax_loader){margin-right:8px!important;margin-left:8px!important}.posts-row>.post_ajax_loader .posts-item,.posts-row>.post_ajax_loader .posts-mini{margin-right:8px;margin-left:8px}.posts-item{margin:15px 0;padding:20px;overflow:hidden;background:var(--main-bg-color, #fff);border-radius:var(--main-radius, 8px);box-shadow:0 0 10px var(--main-shadow, rgba(116, 116, 116, .08));transition:.2s}.posts-item.flex,.posts-item .flex,.posts-mini .flex{display:flex}.posts-item .flex.xx,.posts-mini .flex.xx{flex-direction:column}.posts-item .flex.ac,.posts-mini .flex.ac{align-items:center}.posts-item .flex.jsb,.posts-mini .flex.jsb{justify-content:space-between}.posts-item .flex1,.posts-mini .flex1{flex:1;overflow:hidden}.posts-item .post-graphic:not(.order1){margin-right:20px}.posts-item .order1.post-graphic{margin-right:0;margin-left:20px}.posts-item .item-thumbnail{position:relative;display:block;width:190px;height:0;padding-bottom:var(--posts-list-scale, 70%);overflow:hidden;border-radius:var(--main-radius, 8px)}.item-thumbnail.contain img{object-fit:contain}.swiper-slide.gradient-bg .dplayer-poster img,.swiper-slide.gradient-bg>*>img{object-fit:contain!important;border-radius:0!important}.item-thumbnail .swiper-wrapper{height:100%!important}.list .item-thumbnail .aksrt-slider,.list .item-thumbnail.new-swiper{position:absolute;width:100%;height:100%;margin:0!important}.item-thumbnail .dplayer.dplayer-thumb{position:absolute;inset:0;z-index:1;padding-bottom:var(--posts-list-scale, 70%);background:#000;opacity:0;transition:opacity .3s}.card .item-thumbnail .dplayer.dplayer-thumb{padding-bottom:var(--posts-card-scale, 70%)}.item-thumbnail .dplayer-thumb video,.item-thumbnail .swiper-wrapper,.item-thumbnail img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}.item-thumbnail .swiper-pagination{right:0!important;bottom:0!important;left:0!important;padding:0 2px!important;text-align:center!important}.posts-item.thumb-dplayer-playing .dplayer-thumb{opacity:1}.posts-item.thumb-dplayer-playing .abs-center{opacity:0}.posts-item .item-heading{display:-webkit-box;max-height:2.8em;margin-top:0;margin-bottom:5px;overflow:hidden;font-size:18px;line-height:1.4em;white-space:normal;-webkit-box-orient:vertical;-webkit-line-clamp:2}.font-bold{font-weight:700}.list.style2 .item-thumbnail{width:130px}.list.style2 .item-heading{display:unset;max-height:1.4em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.posts-item .item-heading>a{color:var(--key-color, #333)}.posts-item .item-heading>a:hover{color:var(--focus-color, #f04494)}.posts-item .item-excerpt{margin:0 0 6px;overflow:hidden;color:var(--muted-color, #777);text-overflow:ellipsis;white-space:nowrap}.posts-item .item-meta item{margin-right:8px;font-size:13px}.posts-item .item-meta svg.icon,.posts-mini .item-meta svg.icon{display:inline-block;width:1em;height:1em;margin-right:2px;fill:currentColor;overflow:hidden;vertical-align:-.15em}.posts-item .meta-right item:last-child{margin-right:0}.item-meta .avatar-mini{width:20px;height:20px;border-radius:50%;transform:translateY(-1px)}.item-meta .meta-pay{padding:0 4px}.item-meta .meta-pay img{width:1em;height:1em;object-fit:contain;vertical-align:-.15em}.posts-item .item-meta a,.posts-mini .item-meta a{color:inherit}.posts-item .item-tags a{margin-right:5px;padding:2px 5px;font-size:11px}.posts-item .item-tags a .fa{font-size:1em}.posts-item.card{display:inline-block;width:calc(33.333% - 16px);margin:8px;padding:10px;vertical-align:top;transition:.3s}.posts-item.card:last-child{margin-bottom:20px}.posts-item.card .item-body{width:100%;margin:10px 0 0;padding:0}.posts-item.card .item-meta item{font-size:12px}.posts-mini-con .item-heading,.posts-item.card .item-heading{font-size:14px}.posts-item.card .item-heading{min-height:2.8em}.posts-item.card .item-tags{min-height:23px}.posts-item.card .item-thumbnail{width:100%;padding-bottom:var(--posts-card-scale, 70%)}.posts-item.card.style2{background:transparent;box-shadow:none}.posts-item.card.style2:hover{background:var(--main-bg-color, #fff);box-shadow:0 0 10px var(--main-shadow, rgba(116, 116, 116, .08))}.posts-item.card.style3{padding:0}.posts-item.card.style3 .item-thumbnail,.posts-item.card.style3 .item-thumbnail img{border-radius:var(--main-radius, 8px) var(--main-radius, 8px) 0 0}.posts-item.card.style3 .item-body{margin:0;padding:10px}.posts-item.mult-thumb .thumb-items{margin:0 -3px 6px}.posts-item.mult-thumb .thumb-items>span{position:relative;display:inline-block;width:calc(25% - 6px);height:0;margin:0 3px;padding-bottom:calc(var(--posts-list-scale, 70%) / 4)}.posts-item.mult-thumb .thumb-items>span>img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}.posts-item.mult-thumb .thumb-items.contain>span{overflow:hidden;background:var(--main-border-color, rgba(50, 50, 50, .06));border-radius:var(--main-radius, 8px)}.posts-item.mult-thumb .thumb-items.contaiLine truncated
|
||||
+12
-188
@@ -2464,6 +2464,12 @@ a[href] {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* FA5's later .fa rule sets 900; keep outline glyphs at their regular weight. */
|
||||
.fa.far {
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
@@ -3322,104 +3328,13 @@ body.site-layout-3 .sidebar,
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.term-page-header {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.08));
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--surface-1);
|
||||
box-shadow: var(--shadow-sm, 0 1px 6px rgba(15, 23, 42, 0.04));
|
||||
/* Reference cover ratio must outrank the shared panel padding. */
|
||||
body.category .site-main {
|
||||
line-height: 1.42857143;
|
||||
}
|
||||
|
||||
.term-page-header__body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.term-page-header__eyebrow {
|
||||
margin: 0 0 8px;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.term-page-header__desc {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.term-page-header__cover {
|
||||
min-height: 140px;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
border-top: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.08));
|
||||
}
|
||||
|
||||
.term-page-header--simple .term-page-header__cover {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Card style: accent top border, slightly more padding */
|
||||
.term-page-header--card {
|
||||
border-top: 3px solid var(--color-primary, #f04494);
|
||||
}
|
||||
|
||||
.term-page-header--card .term-page-header__body {
|
||||
padding: 28px 24px;
|
||||
}
|
||||
|
||||
/* Cover style: full-width hero with background image */
|
||||
.term-page-header--cover {
|
||||
border: none;
|
||||
border-radius: var(--radius-lg);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
min-height: 220px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.term-page-header__cover-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.term-page-header__cover-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, rgba(0, 0, 0, 0.55) 0%, rgba(0, 0, 0, 0.25) 100%);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.term-page-header--cover .term-page-header__body {
|
||||
flex: 1;
|
||||
align-items: flex-end;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.term-page-header__body--overlay .entry-title,
|
||||
.term-page-header__body--overlay .term-page-header__desc,
|
||||
.term-page-header__body--overlay .term-page-header__eyebrow {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.term-page-header__body--overlay .term-page-header__eyebrow {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.term-page-header--cover {
|
||||
min-height: 160px;
|
||||
}
|
||||
.page-cover.aksrt-panel {
|
||||
padding: 0 0 calc(25% + 80px);
|
||||
}
|
||||
|
||||
.aksrt-admin-quicklink {
|
||||
@@ -3444,103 +3359,18 @@ body.site-layout-3 .sidebar,
|
||||
background: var(--color-primary-soft, rgba(59, 130, 246, 0.08));
|
||||
}
|
||||
|
||||
.archive-filter-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
margin: -8px 0 24px;
|
||||
padding: 16px 18px;
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.06));
|
||||
}
|
||||
|
||||
.archive-filter-bar__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.archive-filter-bar__label {
|
||||
color: var(--color-ink-4);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.archive-filter-bar__chips {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.archive-filter-bar__chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 34px;
|
||||
padding: 0 12px;
|
||||
border-radius: var(--radius-pill, 999px);
|
||||
background: var(--surface-1);
|
||||
color: var(--color-ink-2);
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.archive-filter-bar__chip.is-active,
|
||||
.archive-filter-bar__chip:hover {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-on-primary, #fff);
|
||||
}
|
||||
|
||||
.archive-filter-bar__meta {
|
||||
color: var(--color-ink-4);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.archive-filter-bar__sort {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.archive-filter-bar__main--tags {
|
||||
border-top: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.06));
|
||||
padding-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* §3.4 custom_filter dimension rows */
|
||||
.archive-filter-bar__main--custom {
|
||||
border-top: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.06));
|
||||
padding-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
.content-stream {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.content-main--archive,
|
||||
.content-main--tag,
|
||||
.content-main--category,
|
||||
.content-main--search,
|
||||
.content-main--single {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.content-stream__list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content-stream__empty {
|
||||
padding: 28px 24px;
|
||||
border-radius: var(--radius-lg);
|
||||
@@ -3558,12 +3388,6 @@ body.site-layout-3 .sidebar,
|
||||
全站只有一套加载语言。 */
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.term-page-header__body {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.aksrt-admin-quicklink {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
:root{--color-primary: #f04494;--color-primary-hover: #e0307e;--color-primary-active: #c9206d;--color-primary-soft: rgba(240, 68, 148, .1);--color-primary-softer: rgba(240, 68, 148, .05);--color-on-primary: #FFFFFF;--color-ink: #333333;--color-ink-2: #4e5358;--color-ink-3: #777777;--color-ink-4: #999999;--surface-page: #f5f6f7;--surface-1: #FFFFFF;--surface-2: #eeeeee;--surface-3: #e9ecef;--surface-4: #f8f8f8;--color-line: rgba(50, 50, 50, .06);--color-line-soft: rgba(0, 0, 0, .03);--color-line-stronger: rgba(50, 50, 50, .12);--color-success: #10B981;--color-warning: #F59E0B;--color-error: #EF4444;--color-info: #3B82F6;--color-accent: #18a058;--color-accent-soft: rgba(24, 160, 88, .12);--color-accent-shadow: rgba(24, 160, 88, .28);--font-display: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;--text-xs: .75rem;--text-sm: .875rem;--text-base: .875rem;--text-md: 1.125rem;--text-lg: 1.25rem;--text-xl: 1.5rem;--text-2xl: 1.875rem;--text-3xl: 2.25rem;--text-4xl: 3rem;--text-5xl: 3.75rem;--leading-tight: 1.2;--leading-snug: 1.4;--leading-normal: 1.6;--leading-relaxed: 1.6;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-7: 32px;--space-8: 40px;--space-9: 48px;--space-10: 64px;--space-11: 80px;--space-12: 96px;--radius-xs: 3px;--radius-sm: 4px;--radius: 8px;--radius-lg: 8px;--radius-xl: 12px;--radius-2xl: 16px;--radius-pill: 100px;--radius-avatar: 50%;--shadow-xs: 0 1px 2px rgba(0, 0, 0, .03);--shadow-sm: 0 0 10px rgba(116, 116, 116, .08);--shadow: 0 0 10px rgba(116, 116, 116, .12);--shadow-lg: 0 12px 32px rgba(18, 18, 18, .1);--shadow-drawer: 0 18px 60px rgba(0, 0, 0, .2);--nav-height: 60px;--container: 1200px;--container-wide: 1440px;--container-read: 760px;--container-page: 880px;--sidebar-width: 311px;--content-gap: 24px;--section-gap: 24px;--sidebar-bp: 992px;--ease: cubic-bezier(.4, 0, .2, 1);--ease-out: cubic-bezier(.16, 1, .3, 1);--ease-spring: cubic-bezier(.34, 1.56, .64, 1);--dur-fast: .15s;--dur: .25s;--dur-slow: .4s;--focus-ring: 0 0 2px 3px rgba(253, 83, 161, .4);--z-dropdown: 100;--z-sticky: 200;--z-fixed: 300;--z-overlay: 400;--z-modal: 500;--z-popover: 600;--z-toast: 700;--z-tooltip: 800;--radius-btn: 4px;--btn-sm-h: 28px;--btn-md-h: 34px;--btn-lg-h: 44px;--card-padding: 15px;--card-padding-sm: 10px;--card-gap: 16px;--card-gap-lg: 20px;--color-primary-light: var(--color-primary-soft);--color-surface: var(--surface-1);--color-line-strong: var(--color-line);--color-bg-hover: var(--surface-2);--color-border-light: var(--color-line-soft);--color-fill: var(--surface-2);--color-warning-bg: #fffbe6;--color-warning-text: #ad6800;--text-secondary: var(--color-ink-3);--text-tertiary: var(--color-ink-4);--surface-active: var(--surface-2);--surface-card: var(--surface-1);--surface-hover: var(--surface-2);--surface-secondary: var(--surface-2);--radius-code: var(--radius);--radius-full: 100px;--shadow-xl: 0 0 10px rgba(116, 116, 116, .12);--space-16: 64px;--z-floating: 900;--transition-fast: .15s ease;--transition-base: .25s ease;--header-bg: var(--blur-bg);--header-color: var(--color-ink-2);--footer-bg: var(--surface-1);--footer-color: var(--color-ink-4);--blur-bg: rgba(255, 255, 255, .8);--float-btn-bg: rgba(200, 200, 200, .4);--font-heading: var(--font-ui);--radius-card: var(--radius);--radius-md: var(--radius);--color-primary-50: var(--color-primary-soft);--color-primary-dark: var(--color-primary-active);--shadow-md: var(--shadow);--mini-radius: calc(var(--radius) / 1.4);--aksrt-avatar-size-xs: 24px;--aksrt-avatar-size-sm: 32px;--aksrt-avatar-size: 40px;--aksrt-avatar-size-lg: 56px;--aksrt-avatar-size-xl: 80px;--aksrt-card-thumb-scale: 56.25%;--aksrt-rose: #f43f5e;--aksrt-rose-soft: rgba(244, 63, 94, .12);--aksrt-blue: #3b82f6;--aksrt-blue-soft: rgba(59, 130, 246, .12);--aksrt-green: #10b981;--aksrt-green-soft: rgba(16, 185, 129, .12);--aksrt-orange: #f97316;--aksrt-orange-soft:rgba(249, 115, 22, .12);--aksrt-purple: #8b5cf6;--aksrt-purple-soft:rgba(139, 92, 246, .12);--aksrt-cyan: #06b6d4;--aksrt-cyan-soft: rgba(6, 182, 212, .12);--aksrt-yellow: #eab308;--aksrt-yellow-soft:rgba(234, 179, 8, .12);--aksrt-red: #ef4444;--aksrt-red-soft: rgba(239, 68, 68, .12);--aksrt-teal: #14b8a6;--aksrt-teal-soft: rgba(20, 184, 166, .12);--aksrt-indigo: #6366f1;--aksrt-indigo-soft:rgba(99, 102, 241, .12);--aksrt-pink: #ec4899;--aksrt-pink-soft: rgba(236, 72, 153, .12);--aksrt-vip-gold-1: #f6d365;--aksrt-vip-gold-2: #fda085;--aksrt-grad-sunset-1: #f093fb;--aksrt-grad-sunset-2: #f5576c;--aksrt-graLine truncated
|
||||
:root{--color-primary: #f04494;--color-primary-hover: #e0307e;--color-primary-active: #c9206d;--color-primary-soft: rgba(240, 68, 148, .1);--color-primary-softer: rgba(240, 68, 148, .05);--color-on-primary: #FFFFFF;--color-ink: #333333;--color-ink-2: #4e5358;--color-ink-3: #777777;--color-ink-4: #999999;--surface-page: #f5f6f7;--surface-1: #FFFFFF;--surface-2: #eeeeee;--surface-3: #e9ecef;--surface-4: #f8f8f8;--color-line: rgba(50, 50, 50, .06);--color-line-soft: rgba(0, 0, 0, .03);--color-line-stronger: rgba(50, 50, 50, .12);--color-success: #10B981;--color-warning: #F59E0B;--color-error: #EF4444;--color-info: #3B82F6;--color-accent: #18a058;--color-accent-soft: rgba(24, 160, 88, .12);--color-accent-shadow: rgba(24, 160, 88, .28);--font-display: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;--text-xs: .75rem;--text-sm: .875rem;--text-base: .875rem;--text-md: 1.125rem;--text-lg: 1.25rem;--text-xl: 1.5rem;--text-2xl: 1.875rem;--text-3xl: 2.25rem;--text-4xl: 3rem;--text-5xl: 3.75rem;--leading-tight: 1.2;--leading-snug: 1.4;--leading-normal: 1.6;--leading-relaxed: 1.6;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-7: 32px;--space-8: 40px;--space-9: 48px;--space-10: 64px;--space-11: 80px;--space-12: 96px;--radius-xs: 3px;--radius-sm: 4px;--radius: 8px;--radius-lg: 8px;--radius-xl: 12px;--radius-2xl: 16px;--radius-pill: 100px;--radius-avatar: 50%;--shadow-xs: 0 1px 2px rgba(0, 0, 0, .03);--shadow-sm: 0 0 10px rgba(116, 116, 116, .08);--shadow: 0 0 10px rgba(116, 116, 116, .12);--shadow-lg: 0 12px 32px rgba(18, 18, 18, .1);--shadow-drawer: 0 18px 60px rgba(0, 0, 0, .2);--nav-height: 60px;--container: 1200px;--container-wide: 1440px;--container-read: 760px;--container-page: 880px;--sidebar-width: 311px;--content-gap: 24px;--section-gap: 24px;--sidebar-bp: 992px;--ease: cubic-bezier(.4, 0, .2, 1);--ease-out: cubic-bezier(.16, 1, .3, 1);--ease-spring: cubic-bezier(.34, 1.56, .64, 1);--dur-fast: .15s;--dur: .25s;--dur-slow: .4s;--focus-ring: 0 0 2px 3px rgba(253, 83, 161, .4);--z-dropdown: 100;--z-sticky: 200;--z-fixed: 300;--z-overlay: 400;--z-modal: 500;--z-popover: 600;--z-toast: 700;--z-tooltip: 800;--radius-btn: 4px;--btn-sm-h: 28px;--btn-md-h: 34px;--btn-lg-h: 44px;--card-padding: 15px;--card-padding-sm: 10px;--card-gap: 16px;--card-gap-lg: 20px;--color-primary-light: var(--color-primary-soft);--color-surface: var(--surface-1);--color-line-strong: var(--color-line);--color-bg-hover: var(--surface-2);--color-border-light: var(--color-line-soft);--color-fill: var(--surface-2);--color-warning-bg: #fffbe6;--color-warning-text: #ad6800;--text-secondary: var(--color-ink-3);--text-tertiary: var(--color-ink-4);--surface-active: var(--surface-2);--surface-card: var(--surface-1);--surface-hover: var(--surface-2);--surface-secondary: var(--surface-2);--radius-code: var(--radius);--radius-full: 100px;--shadow-xl: 0 0 10px rgba(116, 116, 116, .12);--space-16: 64px;--z-floating: 900;--transition-fast: .15s ease;--transition-base: .25s ease;--header-bg: var(--blur-bg);--header-color: var(--color-ink-2);--footer-bg: var(--surface-1);--footer-color: var(--color-ink-4);--blur-bg: rgba(255, 255, 255, .8);--float-btn-bg: rgba(200, 200, 200, .4);--font-heading: var(--font-ui);--radius-card: var(--radius);--radius-md: var(--radius);--color-primary-50: var(--color-primary-soft);--color-primary-dark: var(--color-primary-active);--shadow-md: var(--shadow);--mini-radius: calc(var(--radius) / 1.4);--aksrt-avatar-size-xs: 24px;--aksrt-avatar-size-sm: 32px;--aksrt-avatar-size: 40px;--aksrt-avatar-size-lg: 56px;--aksrt-avatar-size-xl: 80px;--aksrt-card-thumb-scale: 56.25%;--aksrt-rose: #f43f5e;--aksrt-rose-soft: rgba(244, 63, 94, .12);--aksrt-blue: #3b82f6;--aksrt-blue-soft: rgba(59, 130, 246, .12);--aksrt-green: #10b981;--aksrt-green-soft: rgba(16, 185, 129, .12);--aksrt-orange: #f97316;--aksrt-orange-soft:rgba(249, 115, 22, .12);--aksrt-purple: #8b5cf6;--aksrt-purple-soft:rgba(139, 92, 246, .12);--aksrt-cyan: #06b6d4;--aksrt-cyan-soft: rgba(6, 182, 212, .12);--aksrt-yellow: #eab308;--aksrt-yellow-soft:rgba(234, 179, 8, .12);--aksrt-red: #ef4444;--aksrt-red-soft: rgba(239, 68, 68, .12);--aksrt-teal: #14b8a6;--aksrt-teal-soft: rgba(20, 184, 166, .12);--aksrt-indigo: #6366f1;--aksrt-indigo-soft:rgba(99, 102, 241, .12);--aksrt-pink: #ec4899;--aksrt-pink-soft: rgba(236, 72, 153, .12);--aksrt-vip-gold-1: #f6d365;--aksrt-vip-gold-2: #fda085;--aksrt-grad-sunset-1: #f093fb;--aksrt-grad-sunset-2: #f5576c;--aksrt-graLine truncated
|
||||
@@ -49,6 +49,12 @@ a[href] {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* FA5's later .fa rule sets 900; keep outline glyphs at their regular weight. */
|
||||
.fa.far {
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
|
||||
@@ -1,333 +1,13 @@
|
||||
/**
|
||||
* Homepage Fixed Layout — legacy Visual Parity
|
||||
*
|
||||
* Implements legacy's homepage visual language:
|
||||
* - System font stack with Chinese fallbacks
|
||||
* - 1200px container, 311px sidebar
|
||||
* - Post cards: list mode (190px thumb) + card mode (3-col grid)
|
||||
* - legacy shadow/radius/spacing values
|
||||
* - scale(1.02) thumbnail hover
|
||||
* - Lazy-load animations (blur/fade/scale)
|
||||
* - Full responsive: 1200/991/767/640px breakpoints
|
||||
*
|
||||
* Tab nav / tab panes / sort menu use the legacy class contract
|
||||
* (.index-tab, .ajax-option, .ajaxpager) styled by assets/css/main.css.
|
||||
*/
|
||||
|
||||
/* ================================================================
|
||||
1. HOMEPAGE CONTAINER & TYPOGRAPHY
|
||||
================================================================ */
|
||||
|
||||
/** Homepage inherits the reference navigation, widgets and pagination from main.css. */
|
||||
.aksrt-homepage {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
line-height: 1.42857143;
|
||||
color: var(--main-color, #4e5358);
|
||||
background: transparent;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/* Slightly looser than stock legacy short pages: air between stream and footer */
|
||||
padding-bottom: 28px;
|
||||
}
|
||||
|
||||
.aksrt-homepage a[href] {
|
||||
transition: color 0.2s, background 0.3s, opacity 0.3s, box-shadow 0.3s, transform 0.3s;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
2. LAYOUT SHELL
|
||||
================================================================ */
|
||||
|
||||
.aksrt-homepage__fluid-zone.content-shell,
|
||||
.aksrt-homepage__shell.content-shell {
|
||||
max-width: min(100%, var(--container, 1200px));
|
||||
}
|
||||
|
||||
.aksrt-homepage__fluid-zone.content-shell {
|
||||
padding-top: 18px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.aksrt-homepage__shell.content-shell {
|
||||
padding-top: 0;
|
||||
/* 36px + a bit more breathing room before footer */
|
||||
padding-bottom: 56px;
|
||||
}
|
||||
|
||||
/* Bottom fluid zone (if any) keeps a little gap above footer */
|
||||
.aksrt-homepage__fluid-zone--bottom {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.aksrt-homepage {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.aksrt-homepage__shell.content-shell {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dual-column geometry is owned by body.site-layout-* float rules in layout.css. */
|
||||
.aksrt-homepage__content-layout {
|
||||
display: block;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.aksrt-homepage__fluid-zone.content-shell,
|
||||
.aksrt-homepage__shell.content-shell {
|
||||
max-width: 95%;
|
||||
}
|
||||
color: var(--main-color);
|
||||
}
|
||||
|
||||
.aksrt-homepage__content-layout,
|
||||
.aksrt-homepage__content-zone,
|
||||
.aksrt-homepage__fluid-zone,
|
||||
.aksrt-homepage__aside {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aksrt-homepage__content-zone .widget-card:last-child,
|
||||
.aksrt-homepage__aside .widget:last-child,
|
||||
.aksrt-homepage__fluid-zone .widget-fluid:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
3. FLUID WIDGETS
|
||||
================================================================ */
|
||||
|
||||
.aksrt-homepage__fluid-zone .widget-fluid {
|
||||
margin-bottom: 20px;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.aksrt-homepage .widget-fluid.widget_aksrt_tab_posts,
|
||||
.aksrt-homepage .widget-fluid.widget_aksrt_category_block,
|
||||
.aksrt-homepage .widget-fluid.widget_aksrt_category_tabs,
|
||||
.aksrt-homepage .widget-fluid.widget_aksrt_category_recommend {
|
||||
padding: 15px;
|
||||
background: var(--surface-1, #fff);
|
||||
border: none;
|
||||
border-radius: var(--main-radius, var(--radius, 8px));
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.aksrt-homepage__content-zone .widget-card,
|
||||
.aksrt-homepage__aside .widget {
|
||||
background: var(--surface-1, #fff);
|
||||
padding: 15px;
|
||||
border: none;
|
||||
border-radius: var(--main-radius, var(--radius, 8px));
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
4. STREAM TITLE BOX (legacy title-theme / 第N页)
|
||||
================================================================ */
|
||||
|
||||
.title-theme {
|
||||
position: relative;
|
||||
padding-left: 1.2em;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.title-theme::before {
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
bottom: 10%;
|
||||
left: 2px;
|
||||
width: 4px;
|
||||
content: "";
|
||||
background: var(--color-primary);
|
||||
border-radius: 5px;
|
||||
box-shadow: 1px 1px 3px -1px var(--color-primary);
|
||||
}
|
||||
|
||||
.flex {
|
||||
/* No !important: the site-layout-1 / fluid-widget-wrap wide-mode rule
|
||||
(.posts-item:not(.card) { display: inline-flex }) must out-rank this
|
||||
utility or the no-sidebar two-column pairing collapses back to
|
||||
half-width stacked rows. */
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex.xx,
|
||||
.xx {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex1,
|
||||
.flex-1 {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.jsb,
|
||||
.jb {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ac {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
5. PAGINATION
|
||||
================================================================ */
|
||||
|
||||
.aksrt-homepage .navigation.pagination .nav-links,
|
||||
.aksrt-homepage .pagination {
|
||||
gap: 0;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
.aksrt-homepage .navigation.pagination .page-numbers,
|
||||
.aksrt-homepage .pagination .page-numbers {
|
||||
min-width: 34px;
|
||||
height: 34px;
|
||||
padding: 0 10px;
|
||||
margin: 3px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-xs, 3px);
|
||||
background: var(--surface-4, #eee);
|
||||
color: var(--color-ink-3, #777);
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.aksrt-homepage .navigation.pagination .page-numbers:hover,
|
||||
.aksrt-homepage .pagination .page-numbers:hover {
|
||||
opacity: 1;
|
||||
background: var(--color-primary-soft);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.aksrt-homepage .navigation.pagination .current,
|
||||
.aksrt-homepage .pagination .current {
|
||||
background: var(--color-primary, var(--color-primary)) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
6. SIDEBAR
|
||||
================================================================ */
|
||||
|
||||
.aksrt-homepage__aside > .aksrt-panel,
|
||||
.aksrt-homepage__aside > .widget {
|
||||
margin-bottom: 15px;
|
||||
padding: 15px;
|
||||
background: var(--surface-1, #fff);
|
||||
border: 0;
|
||||
border-radius: var(--main-radius, var(--radius, 8px));
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside > .aksrt-panel:last-child,
|
||||
.aksrt-homepage__aside > .widget:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget-list li,
|
||||
.aksrt-homepage__aside .widget_recent_entries li,
|
||||
.aksrt-homepage__aside .recent-posts-row {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget-title,
|
||||
.aksrt-homepage__content-zone .widget-title {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget-title {
|
||||
position: relative;
|
||||
margin: 0 0 10px;
|
||||
padding-left: 1.2em;
|
||||
padding-bottom: 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget-title::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
bottom: 10%;
|
||||
left: 2px;
|
||||
width: 4px;
|
||||
height: auto;
|
||||
background: var(--color-primary, var(--color-primary));
|
||||
border-radius: 5px;
|
||||
box-shadow: 1px 1px 3px -1px var(--color-primary, var(--color-primary));
|
||||
transition: transform 0.4s ease;
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget_recent_entries li a,
|
||||
.aksrt-homepage__aside .widget-list li a {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget_recent_entries li {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget_search .search-field {
|
||||
height: 40px;
|
||||
border-radius: 30px;
|
||||
background: var(--surface-2, #eee);
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .widget_search .search-submit {
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border-radius: 30px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .tag-cloud {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .tag-cloud a {
|
||||
padding: 4px 10px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
border: 0;
|
||||
background: var(--surface-2, #eee);
|
||||
transition: background 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.aksrt-homepage__aside .tag-cloud a:hover {
|
||||
background: var(--color-primary, var(--color-primary));
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.aksrt-homepage .widget.notop,
|
||||
.aksrt-homepage .box-body.notop {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.aksrt-homepage .widget.nobottom,
|
||||
.aksrt-homepage .box-body.nobottom {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.aksrt-homepage .widget.widget_custom_html {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
@@ -253,104 +253,13 @@ body.site-layout-3 .sidebar,
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.term-page-header {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.08));
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--surface-1);
|
||||
box-shadow: var(--shadow-sm, 0 1px 6px rgba(15, 23, 42, 0.04));
|
||||
/* Reference cover ratio must outrank the shared panel padding. */
|
||||
body.category .site-main {
|
||||
line-height: 1.42857143;
|
||||
}
|
||||
|
||||
.term-page-header__body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.term-page-header__eyebrow {
|
||||
margin: 0 0 8px;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.term-page-header__desc {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.term-page-header__cover {
|
||||
min-height: 140px;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
border-top: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.08));
|
||||
}
|
||||
|
||||
.term-page-header--simple .term-page-header__cover {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Card style: accent top border, slightly more padding */
|
||||
.term-page-header--card {
|
||||
border-top: 3px solid var(--color-primary, #f04494);
|
||||
}
|
||||
|
||||
.term-page-header--card .term-page-header__body {
|
||||
padding: 28px 24px;
|
||||
}
|
||||
|
||||
/* Cover style: full-width hero with background image */
|
||||
.term-page-header--cover {
|
||||
border: none;
|
||||
border-radius: var(--radius-lg);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
min-height: 220px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.term-page-header__cover-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.term-page-header__cover-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, rgba(0, 0, 0, 0.55) 0%, rgba(0, 0, 0, 0.25) 100%);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.term-page-header--cover .term-page-header__body {
|
||||
flex: 1;
|
||||
align-items: flex-end;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.term-page-header__body--overlay .entry-title,
|
||||
.term-page-header__body--overlay .term-page-header__desc,
|
||||
.term-page-header__body--overlay .term-page-header__eyebrow {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.term-page-header__body--overlay .term-page-header__eyebrow {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.term-page-header--cover {
|
||||
min-height: 160px;
|
||||
}
|
||||
.page-cover.aksrt-panel {
|
||||
padding: 0 0 calc(25% + 80px);
|
||||
}
|
||||
|
||||
.aksrt-admin-quicklink {
|
||||
@@ -375,103 +284,18 @@ body.site-layout-3 .sidebar,
|
||||
background: var(--color-primary-soft, rgba(59, 130, 246, 0.08));
|
||||
}
|
||||
|
||||
.archive-filter-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
margin: -8px 0 24px;
|
||||
padding: 16px 18px;
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.06));
|
||||
}
|
||||
|
||||
.archive-filter-bar__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.archive-filter-bar__label {
|
||||
color: var(--color-ink-4);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.archive-filter-bar__chips {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.archive-filter-bar__chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 34px;
|
||||
padding: 0 12px;
|
||||
border-radius: var(--radius-pill, 999px);
|
||||
background: var(--surface-1);
|
||||
color: var(--color-ink-2);
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.archive-filter-bar__chip.is-active,
|
||||
.archive-filter-bar__chip:hover {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-on-primary, #fff);
|
||||
}
|
||||
|
||||
.archive-filter-bar__meta {
|
||||
color: var(--color-ink-4);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.archive-filter-bar__sort {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.archive-filter-bar__main--tags {
|
||||
border-top: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.06));
|
||||
padding-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* §3.4 custom_filter dimension rows */
|
||||
.archive-filter-bar__main--custom {
|
||||
border-top: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.06));
|
||||
padding-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
.content-stream {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.content-main--archive,
|
||||
.content-main--tag,
|
||||
.content-main--category,
|
||||
.content-main--search,
|
||||
.content-main--single {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.content-stream__list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content-stream__empty {
|
||||
padding: 28px 24px;
|
||||
border-radius: var(--radius-lg);
|
||||
@@ -489,12 +313,6 @@ body.site-layout-3 .sidebar,
|
||||
全站只有一套加载语言。 */
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.term-page-header__body {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.aksrt-admin-quicklink {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,33 @@
|
||||
box-shadow: var(--card-shadow, 0 2px 8px rgba(0, 0, 0, .06));
|
||||
}
|
||||
|
||||
.aksrt-yiyan[data-aksrt-yiyan] {
|
||||
cursor: pointer;
|
||||
transition: box-shadow .2s ease, opacity .2s ease;
|
||||
}
|
||||
|
||||
.aksrt-yiyan[data-aksrt-yiyan]:hover,
|
||||
.aksrt-yiyan[data-aksrt-yiyan]:focus-visible {
|
||||
box-shadow: inset 0 0 5px 0 var(--focus-shadow-color, rgba(253, 83, 161, .3));
|
||||
}
|
||||
|
||||
.aksrt-yiyan .yiyan {
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.aksrt-yiyan .yiyan .en {
|
||||
font-size: .9em;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.aksrt-yiyan.is-loading {
|
||||
opacity: .65;
|
||||
}
|
||||
|
||||
.aksrt-yiyan.is-error .cn {
|
||||
color: var(--muted-color, #777);
|
||||
}
|
||||
|
||||
/* ---- Front / After content insertion ---- */
|
||||
.article-front-content,
|
||||
.article-after-content {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 400 240"><defs><linearGradient id="g" x1="-674.76" x2="-674.76" y1="-2991.46" y2="-2991.96" gradientTransform="matrix(390 0 0 -196 263353 -586207.5)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#a8a8a8" stop-opacity=".15"/><stop offset="1" stop-color="#a8a8a8" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="123" x2="194" y1="119.5" y2="119.5" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2f2f2"/><stop offset=".13" stop-color="#ededed"/><stop offset=".59" stop-color="#e0e0e0"/><stop offset="1" stop-color="#dbdbdb"/></linearGradient><linearGradient id="a" x1="225.5" x2="225.5" y1="170" y2="63" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fafafa"/><stop offset="1" stop-color="#dbdbdb"/></linearGradient><linearGradient id="j" x1="123" x2="257" y1="68.5" y2="68.5" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#c9c9c9"/><stop offset=".57" stop-color="#dadada"/><stop offset="1" stop-color="#ebebeb"/></linearGradient><linearGradient xlink:href="#a" id="k" x1="123" x2="218.86" y1="63"/><linearGradient xlink:href="#a" id="l" x1="204.49" x2="204.49" y1="88" y2="54.05"/><linearGradient id="b" x1="142.55" x2="226.6" y1="66.08" y2="66.08" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fafafa"/><stop offset="1" stop-color="#ececec"/></linearGradient><linearGradient xlink:href="#b" id="m" x1="142.55" x2="161.04" y1="82.49" y2="82.49"/><linearGradient id="n" x1="-694.55" x2="-694.58" y1="-3120.44" y2="-3121.15" gradientTransform="matrix(-17 0 0 -23 -11666 -71655)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff6ec"/><stop offset="1" stop-color="#fdead6"/></linearGradient><linearGradient id="o" x1="-693.58" x2="-693.58" y1="-3151.07" y2="-3151.89" gradientTransform="matrix(-18 0 0 -19 -12342 -59761)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#494b80"/><stop offset="1" stop-color="#272756"/></linearGradient><linearGradient id="p" x1="-683.18" x2="-683.18" y1="-3044.31" y2="-3045.11" gradientTransform="matrix(-46 0 0 -48 -31283 -145999.5)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#faa35e"/><stop offset="1" stop-color="#f66357"/></linearGradient><linearGradient id="q" x1="-682.78" x2="-682.78" y1="-3042.82" y2="-3043.64" gradientTransform="matrix(-48.98 0 0 -49 -33299.76 -148958.01)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#8caff7"/><stop offset="1" stop-color="#6a95f5"/></linearGradient><linearGradient id="c" x1="-692.52" x2="-692.96" y1="-3232.02" y2="-3231.86" gradientTransform="matrix(-19 0 0 -13 -13045 -41835.98)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1f1f49"/><stop offset="1" stop-color="#424776"/></linearGradient><linearGradient xlink:href="#c" id="r" x1="-692.45" x2="-693.07" y1="-3231.86" y2="-3231.73" gradientTransform="matrix(-19 0 0 -13 -13038 -41828.98)"/><linearGradient xlink:href="#d" id="s" x1="151.92" x2="219.58" y1="65.75" y2="65.75"/><linearGradient id="e" x1="213.5" x2="213.5" y1="39" y2="21.82" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4b82fa" stop-opacity="0"/><stop offset="1" stop-color="#4b82fa" stop-opacity=".6"/></linearGradient><linearGradient xlink:href="#e" id="t" x1="227" x2="227" y1="40" y2="18"/><linearGradient xlink:href="#e" id="u" x1="235.5" x2="235.5" y1="45" y2="29"/><radialGradient id="f" cx="186.19" cy="17834.54" r="114.55" gradientTransform="matrix(1 0 0 .15 8.71 -2479.79)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#808083"/><stop offset=".07" stop-color="#9a9a9c"/><stop offset=".17" stop-color="#b9b9ba"/><stop offset=".28" stop-color="#d2d2d4"/><stop offset=".4" stop-color="#e6e6e7"/><stop offset=".53" stop-color="#f4f4f5"/><stop offset=".7" stop-color="#fdfdfd"/><stop offset=".99" stop-color="#fff"/></radialGradient><radialGradient id="h" cx="-509.96" cy="-2945.9" r="1.3" gradientTransform="matrix(0 21 22.62 0 66882.46 10879.55)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#c1c1c1"/></radialGradient><radialGradient id="i" cx="-464.23" cy="-2925.45" r=".94" gradientTransform="matrix(0 16 17.23 0 50697.21 7516.25)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#d7d7d7"/></radialGradient></defs><g data-name="图层 1" style="isolation:isolate"><ellipse cx="194" cy="167" rx="114" ry="17" style="opacity:.36;mix-blend-mode:multiply;fill:url(#f)"/><ellipse cx="198" cy="217.36" rx="195" ry="98" style="fill:url(#g)"/><circle cx="254.57" cy="174.43" r="14.43" style="opacity:.84;fill:url(#h);isolation:isolate"/><circle cx="285.5" cy="90.86" r="8" style="opacity:.66;fill:url(#i);isolation:isolate"/><path d="m123 69 71 19v82l-66-26-5-75z" style="fill:url(#d)"/><path d="m257 63-63 25v82l61-28 2-79z" style="fill:url(#a)"/><path d="m195 49-72 20 71 19 63-25-62-14z" style="fill:url(#j)"/>Line truncated
|
||||
|
After Width: | Height: | Size: 8.3 KiB |
@@ -1,234 +0,0 @@
|
||||
/**
|
||||
* AKSRT Archive AJAX Filter
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function initArchiveFilter(root) {
|
||||
var scope = root || document;
|
||||
var bar = scope.querySelector(".archive-filter-bar--ajax[data-ajax-archive]");
|
||||
if (!bar || bar.dataset.aksrtArchiveInit === "1") return;
|
||||
bar.dataset.aksrtArchiveInit = "1";
|
||||
|
||||
var archiveType = bar.dataset.ajaxArchive || "category";
|
||||
var termId = parseInt(bar.dataset.termId, 10) || 0;
|
||||
var wrap = scope.querySelector("[data-ajax-wrap]");
|
||||
if (!wrap) return;
|
||||
|
||||
// 归档面与首页共用同一套骨架契约:.post_ajax_loader + .is-loading,
|
||||
// 不再使用独立 spinner 遮罩 / opacity 淡出。
|
||||
var loading = wrap.querySelector(".post_ajax_loader");
|
||||
var REST_BASE = "/wp-json/aksrt/v1/archive/posts";
|
||||
var emptyText = archiveType === "tag" ? "该标签下暂无文章" : "该分类下暂无文章";
|
||||
|
||||
var state = {
|
||||
filter_cat: "",
|
||||
filter_tag: "",
|
||||
custom: {},
|
||||
sort: "date",
|
||||
page: 1
|
||||
};
|
||||
|
||||
var activeCat = bar.querySelector("[data-filter-cat].is-active");
|
||||
if (activeCat) state.filter_cat = activeCat.dataset.filterCat || "";
|
||||
var activeTag = bar.querySelector("[data-filter-tag].is-active");
|
||||
if (activeTag) state.filter_tag = activeTag.dataset.filterTag || "";
|
||||
var activeSort = bar.querySelector("[data-filter-sort].is-active");
|
||||
if (activeSort) state.sort = activeSort.dataset.filterSort || "date";
|
||||
// 自定义高级筛选维度:以当前 URL 参数为准(SSR active 状态与 URL 一致)。
|
||||
if (bar.querySelector("[data-filter-dim]")) {
|
||||
new URLSearchParams(window.location.search).forEach(function (value, key) {
|
||||
if (bar.querySelector('[data-filter-dim="' + key + '"]')) {
|
||||
state.custom[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildUrl() {
|
||||
var params = new URLSearchParams();
|
||||
params.set("archive_type", archiveType);
|
||||
params.set("term_id", String(termId));
|
||||
if (state.filter_cat) params.set("filter_cat", state.filter_cat);
|
||||
if (state.filter_tag) params.set("filter_tag", state.filter_tag);
|
||||
if (Object.keys(state.custom).length) params.set("custom_filter", JSON.stringify(state.custom));
|
||||
|
||||
params.set("sort", state.sort || "date");
|
||||
params.set("page", String(state.page));
|
||||
return REST_BASE + "?" + params.toString();
|
||||
}
|
||||
|
||||
function updateActiveChips() {
|
||||
bar.querySelectorAll("[data-filter-cat]").forEach(function (el) {
|
||||
el.classList.toggle("is-active", (el.dataset.filterCat || "") === state.filter_cat);
|
||||
});
|
||||
bar.querySelectorAll("[data-filter-tag]").forEach(function (el) {
|
||||
el.classList.toggle("is-active", (el.dataset.filterTag || "") === state.filter_tag);
|
||||
});
|
||||
bar.querySelectorAll("[data-filter-sort]").forEach(function (el) {
|
||||
el.classList.toggle("is-active", (el.dataset.filterSort || "date") === state.sort);
|
||||
});
|
||||
bar.querySelectorAll("[data-filter-dim]").forEach(function (el) {
|
||||
var current = state.custom[el.dataset.filterDim] || "";
|
||||
el.classList.toggle("is-active", (el.dataset.filterDimValue || "") !== "" && (el.dataset.filterDimValue || "") === current);
|
||||
});
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
if (loading) loading.classList.add("is-loading");
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
if (loading) loading.classList.remove("is-loading");
|
||||
}
|
||||
|
||||
function rebuildEmptyState() {
|
||||
var noResults = document.createElement("div");
|
||||
noResults.className = "no-results content-stream__empty";
|
||||
noResults.innerHTML = "<p>" + emptyText + "</p>";
|
||||
wrap.appendChild(noResults);
|
||||
}
|
||||
|
||||
function fetchPosts(pushState) {
|
||||
showLoading();
|
||||
fetch(buildUrl(), {
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" },
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error("HTTP " + r.status);
|
||||
return r.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
hideLoading();
|
||||
if (data.html !== undefined) {
|
||||
// 只清真实内容;骨架(.post_ajax_loader)与分页控件保留复用 ——
|
||||
// 与参考主题 post_ajax success 只 hide 不删的生命周期一致。
|
||||
var keep = function (node) {
|
||||
return node === loading ||
|
||||
node.classList.contains("post_ajax_loader") ||
|
||||
node.classList.contains("ajax-pag");
|
||||
};
|
||||
Array.from(wrap.children).forEach(function (child) {
|
||||
if (!keep(child)) wrap.removeChild(child);
|
||||
});
|
||||
if (data.html) {
|
||||
var temp = document.createElement("div");
|
||||
temp.innerHTML = data.html;
|
||||
while (temp.firstChild) wrap.appendChild(temp.firstChild);
|
||||
} else {
|
||||
rebuildEmptyState();
|
||||
}
|
||||
// legacy 事件顺序:先 post_ajax.ed,再全局 auto_fun 重扫
|
||||
wrap.dispatchEvent(new CustomEvent("post_ajax.ed", { bubbles: true }));
|
||||
if (typeof window.auto_fun === "function") window.auto_fun();
|
||||
}
|
||||
if (pushState !== false) {
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.delete("orderby");
|
||||
url.searchParams.delete("order");
|
||||
url.searchParams.delete("paged");
|
||||
url.searchParams.delete("aksrt_filter_cat");
|
||||
url.searchParams.delete("aksrt_filter_tag");
|
||||
bar.querySelectorAll("[data-filter-dim]").forEach(function (el) {
|
||||
url.searchParams.delete(el.dataset.filterDim);
|
||||
});
|
||||
if (state.filter_cat) url.searchParams.set("aksrt_filter_cat", state.filter_cat);
|
||||
if (state.filter_tag) url.searchParams.set("aksrt_filter_tag", state.filter_tag);
|
||||
Object.keys(state.custom).forEach(function (key) {
|
||||
url.searchParams.set(key, state.custom[key]);
|
||||
});
|
||||
if (state.sort) url.searchParams.set("orderby", state.sort);
|
||||
if (state.page > 1) url.searchParams.set("paged", String(state.page));
|
||||
history.pushState({ aksrtArchive: true, state: Object.assign({}, state) }, "", url.toString());
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
hideLoading();
|
||||
});
|
||||
}
|
||||
|
||||
bar.addEventListener("click", function (e) {
|
||||
var chip = e.target.closest("[data-filter-cat], [data-filter-tag], [data-filter-sort], [data-filter-dim]");
|
||||
if (!chip) return;
|
||||
e.preventDefault();
|
||||
|
||||
var changed = false;
|
||||
if (chip.dataset.filterCat !== undefined) {
|
||||
var newCat = chip.dataset.filterCat;
|
||||
if (newCat !== state.filter_cat) {
|
||||
state.filter_cat = newCat;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (chip.dataset.filterTag !== undefined) {
|
||||
var newTag = chip.dataset.filterTag;
|
||||
if (newTag !== state.filter_tag) {
|
||||
state.filter_tag = newTag;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (chip.dataset.filterSort !== undefined) {
|
||||
var newSort = chip.dataset.filterSort || "date";
|
||||
if (newSort !== state.sort) {
|
||||
state.sort = newSort;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (chip.dataset.filterDim !== undefined) {
|
||||
var dimKey = chip.dataset.filterDim;
|
||||
var dimValue = chip.dataset.filterDimValue || "";
|
||||
if (dimValue !== (state.custom[dimKey] || "")) {
|
||||
if (dimValue === "") {
|
||||
delete state.custom[dimKey];
|
||||
} else {
|
||||
state.custom[dimKey] = dimValue;
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
state.page = 1;
|
||||
updateActiveChips();
|
||||
fetchPosts(true);
|
||||
}
|
||||
});
|
||||
|
||||
wrap.addEventListener("click", function (e) {
|
||||
var pageLink = e.target.closest(".pagination a, .nav-links a, a.page-numbers");
|
||||
if (!pageLink || !wrap.contains(pageLink)) return;
|
||||
var href = pageLink.getAttribute("href");
|
||||
if (!href) return;
|
||||
|
||||
var match = href.match(/[?&]paged=(\d+)/);
|
||||
if (match) {
|
||||
e.preventDefault();
|
||||
state.page = parseInt(match[1], 10) || 1;
|
||||
fetchPosts(true);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("popstate", function (e) {
|
||||
if (e.state && e.state.aksrtArchive) {
|
||||
state = Object.assign(state, e.state.state);
|
||||
updateActiveChips();
|
||||
fetchPosts(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function init(root) {
|
||||
initArchiveFilter(root);
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", function () { init(); });
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
// AJAX 注入新内容后由 auto_fun 重扫(bar 级 data 标记保证幂等)。
|
||||
if (window.jQuery) {
|
||||
window.jQuery(document).on("auto_fun", function () { init(); });
|
||||
}
|
||||
})();
|
||||
@@ -510,7 +510,7 @@
|
||||
showListLoading(commentsArea);
|
||||
}
|
||||
function applyCommentOrder(order) {
|
||||
if (order !== "newest" && order !== "oldest" && order !== "hottest") {
|
||||
if (order !== "newest" && order !== "hottest") {
|
||||
order = "newest";
|
||||
}
|
||||
renderLoadingState();
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
(function($,window2,document2){"use strict";function closeNotyf($element){$element.addClass("notyn-out"),window2.setTimeout(function(){$element.remove()},1e3)}function notyf2(message,type,time,id){var $body=jQuery(document2.body);jQuery(".notyn").length||$body.append('<div class="notyn"></div>'),type=type||"success",type==="error"&&(type="danger"),time=time||5e3,time=time<100?time*1e3:time;var idAttribute=id?' id="'+id+'"':"",$notification=jQuery('<div class="noty1"'+idAttribute+'><div class="notyf '+type+'">'+message+"</div></div>"),shouldClose=!id;id&&jQuery("#"+id).length?(jQuery("#"+id).find(".notyf").removeClass().addClass("notyf "+type).html(message),$notification=jQuery("#"+id),shouldClose=!0):jQuery(".notyn").append($notification),shouldClose&&window2.setTimeout(function(){closeNotyf($notification)},time)}jQuery(document2.body).on("click",".noty1",function(){closeNotyf(jQuery(this))}),window2.notyf=notyf2})(jQuery,window,document),(function(window2){"use strict";var allowedKinds={identity:!0,badges:!0,orders:!0,vip:!0},allowedEvents={click:!0,shell:!0,"first-content":!0,ready:!0,"action-start":!0,"action-end":!0,unauthorized:!0,error:!0},sessions=Object.create(null),sequence=0,maxSessions=100;function normalize(value,fallback){var normalized=String(value||fallback||"").toLowerCase().replace(/[^a-z0-9-]/g,"");return normalized||fallback}function now(){return window2.performance&&typeof window2.performance.now=="function"?window2.performance.now():0}function markName(session,event){return"aksrt-modal-"+session.kind+"-"+session.id+"-"+event}function emit(session,event){if(!(!window2.performance||typeof window2.performance.mark!="function")){var name=markName(session,event);if(window2.performance.mark(name),session.marks[event]=name,typeof window2.performance.measure=="function"&&session.marks.click&&event!=="click")try{window2.performance.measure("aksrt-modal-"+session.kind+"-"+session.id+"-click-to-"+event,session.marks.click,name)}catch{}}}function start(kind){var safeKind=normalize(kind,"unknown");allowedKinds[safeKind]||(safeKind="unknown"),sequence+=1;var id=String(sequence),session={id,kind:safeKind,startedAt:now(),marks:Object.create(null)};sessions[id]=session,emit(session,"click");for(var ids=Object.keys(sessions);ids.length>maxSessions;)delete sessions[ids.shift()];return id}function mark(id,event){var session=sessions[String(id||"")],safeEvent=normalize(event,"error");!session||!allowedEvents[safeEvent]||emit(session,safeEvent)}function finish(id){mark(id,"ready")}function fail(id){mark(id,"error")}window2.AksrtModalTelemetry={start,mark,finish,error:fail}})(window),(function(window2){"use strict";function data(){return window2.aksrtData||window2.aksrt||{}}function createIdempotencyKey(){if(window2.crypto&&typeof window2.crypto.randomUUID=="function")return window2.crypto.randomUUID();if(!window2.crypto||typeof window2.crypto.getRandomValues!="function")throw new Error("当前浏览器无法生成安全的操作标识");var bytes=new Uint8Array(16);window2.crypto.getRandomValues(bytes),bytes[6]=bytes[6]&15|64,bytes[8]=bytes[8]&63|128;var hex=Array.prototype.map.call(bytes,function(value){return value.toString(16).padStart(2,"0")}).join("");return hex.slice(0,8)+"-"+hex.slice(8,12)+"-"+hex.slice(12,16)+"-"+hex.slice(16,20)+"-"+hex.slice(20)}function restBase(){var config=data(),raw=config.restUrl||config.apiUrl||config.rest_url||"",siteUrl=config.siteUrl||config.homeUrl||window2.location.origin+"/";return window2.location.protocol==="https:"&&siteUrl.substring(0,5)==="http:"&&(siteUrl="https:"+siteUrl.substring(5)),raw?String(raw).replace(/\/$/,""):String(siteUrl).replace(/\/$/,"")+"/index.php?rest_route=/aksrt/v1"}function appendQuery(url2,query){if(!query)return url2;var parts=[];return typeof query=="string"?parts=query.replace(/^\?/,"").split("&").filter(Boolean):typeof query=="object"&&Object.keys(query).forEach(function(key){query[key]===void 0||query[key]===null||parts.push(encodeURIComponent(key)+"="+encodeURIComponent(String(query[key])))}),parts.length?url2+(url2.indexOf("?")>=0?"&":"?")+parts.join("&"):url2}function url(path,query){path=String(path||"");var inlineQuery="",queryIndex=path.indexOf("?");if(queryIndex>=0&&(inlineQuery=path.slice(queryIndex+1),path=path.slice(0,queryIndex)),/^https?:\/\//i.test(path))return appendQuery(path,query||inlineQuery);path.charAt(0)!=="/"&&(path="/"+path);var base=restBase(),result=(base.indexOf("rest_route=")>=0,base+path);return appendQuery(result,query||inlineQuery)}function headers(options){var config=data(),source=options&&options.headers||{},result={},nonce=config.restNonce||config.nonce||"";return Object.keys(source).forEach(function(key){result[key]=source[key]}),result.Accept||(result.Accept="application/json, text/html;q=0.9, */*;q=0.8"),nonce&&!result["X-WP-Nonce"]&&(result["X-WP-Nonce"]=nonce),options&&options.body!==void 0&&options.body!==null&&!(window2.FormData&&options.body instanceof window2.FormData)&&!result["Content-Type"]&Line truncated
|
||||
(function($,window2,document2){"use strict";function closeNotyf($element){$element.addClass("notyn-out"),window2.setTimeout(function(){$element.remove()},1e3)}function notyf2(message,type,time,id){var $body=jQuery(document2.body);jQuery(".notyn").length||$body.append('<div class="notyn"></div>'),type=type||"success",type==="error"&&(type="danger"),time=time||5e3,time=time<100?time*1e3:time;var idAttribute=id?' id="'+id+'"':"",$notification=jQuery('<div class="noty1"'+idAttribute+'><div class="notyf '+type+'">'+message+"</div></div>"),shouldClose=!id;id&&jQuery("#"+id).length?(jQuery("#"+id).find(".notyf").removeClass().addClass("notyf "+type).html(message),$notification=jQuery("#"+id),shouldClose=!0):jQuery(".notyn").append($notification),shouldClose&&window2.setTimeout(function(){closeNotyf($notification)},time)}jQuery(document2.body).on("click",".noty1",function(){closeNotyf(jQuery(this))}),window2.notyf=notyf2})(jQuery,window,document),(function(window2){"use strict";var allowedKinds={identity:!0,badges:!0,orders:!0,vip:!0},allowedEvents={click:!0,shell:!0,"first-content":!0,ready:!0,"action-start":!0,"action-end":!0,unauthorized:!0,error:!0},sessions=Object.create(null),sequence=0,maxSessions=100;function normalize(value,fallback){var normalized=String(value||fallback||"").toLowerCase().replace(/[^a-z0-9-]/g,"");return normalized||fallback}function now(){return window2.performance&&typeof window2.performance.now=="function"?window2.performance.now():0}function markName(session,event){return"aksrt-modal-"+session.kind+"-"+session.id+"-"+event}function emit(session,event){if(!(!window2.performance||typeof window2.performance.mark!="function")){var name=markName(session,event);if(window2.performance.mark(name),session.marks[event]=name,typeof window2.performance.measure=="function"&&session.marks.click&&event!=="click")try{window2.performance.measure("aksrt-modal-"+session.kind+"-"+session.id+"-click-to-"+event,session.marks.click,name)}catch{}}}function start(kind){var safeKind=normalize(kind,"unknown");allowedKinds[safeKind]||(safeKind="unknown"),sequence+=1;var id=String(sequence),session={id,kind:safeKind,startedAt:now(),marks:Object.create(null)};sessions[id]=session,emit(session,"click");for(var ids=Object.keys(sessions);ids.length>maxSessions;)delete sessions[ids.shift()];return id}function mark(id,event){var session=sessions[String(id||"")],safeEvent=normalize(event,"error");!session||!allowedEvents[safeEvent]||emit(session,safeEvent)}function finish(id){mark(id,"ready")}function fail(id){mark(id,"error")}window2.AksrtModalTelemetry={start,mark,finish,error:fail}})(window),(function(window2){"use strict";function data(){return window2.aksrtData||window2.aksrt||{}}function createIdempotencyKey(){if(window2.crypto&&typeof window2.crypto.randomUUID=="function")return window2.crypto.randomUUID();if(!window2.crypto||typeof window2.crypto.getRandomValues!="function")throw new Error("当前浏览器无法生成安全的操作标识");var bytes=new Uint8Array(16);window2.crypto.getRandomValues(bytes),bytes[6]=bytes[6]&15|64,bytes[8]=bytes[8]&63|128;var hex=Array.prototype.map.call(bytes,function(value){return value.toString(16).padStart(2,"0")}).join("");return hex.slice(0,8)+"-"+hex.slice(8,12)+"-"+hex.slice(12,16)+"-"+hex.slice(16,20)+"-"+hex.slice(20)}function restBase(){var config=data(),raw=config.restUrl||config.apiUrl||config.rest_url||"",siteUrl=config.siteUrl||config.homeUrl||window2.location.origin+"/";return window2.location.protocol==="https:"&&siteUrl.substring(0,5)==="http:"&&(siteUrl="https:"+siteUrl.substring(5)),raw?String(raw).replace(/\/$/,""):String(siteUrl).replace(/\/$/,"")+"/index.php?rest_route=/aksrt/v1"}function appendQuery(url2,query){if(!query)return url2;var parts=[];return typeof query=="string"?parts=query.replace(/^\?/,"").split("&").filter(Boolean):typeof query=="object"&&Object.keys(query).forEach(function(key){query[key]===void 0||query[key]===null||parts.push(encodeURIComponent(key)+"="+encodeURIComponent(String(query[key])))}),parts.length?url2+(url2.indexOf("?")>=0?"&":"?")+parts.join("&"):url2}function url(path,query){path=String(path||"");var inlineQuery="",queryIndex=path.indexOf("?");if(queryIndex>=0&&(inlineQuery=path.slice(queryIndex+1),path=path.slice(0,queryIndex)),/^https?:\/\//i.test(path))return appendQuery(path,query||inlineQuery);path.charAt(0)!=="/"&&(path="/"+path);var base=restBase(),result=(base.indexOf("rest_route=")>=0,base+path);return appendQuery(result,query||inlineQuery)}function headers(options){var config=data(),source=options&&options.headers||{},result={},nonce=config.restNonce||config.nonce||"";return Object.keys(source).forEach(function(key){result[key]=source[key]}),result.Accept||(result.Accept="application/json, text/html;q=0.9, */*;q=0.8"),nonce&&!result["X-WP-Nonce"]&&(result["X-WP-Nonce"]=nonce),options&&options.body!==void 0&&options.body!==null&&!(window2.FormData&&options.body instanceof window2.FormData)&&!result["Content-Type"]&Line truncated
|
||||
+59
-229
@@ -1,255 +1,85 @@
|
||||
/**
|
||||
* AKSRT Yiyan — 一言轮播组件
|
||||
*
|
||||
* Fetches random quotes from hitokoto.cn API and auto-rotates.
|
||||
* Public v1 returns one sentence per request; multi-quote carousels
|
||||
* issue parallel fetches. Category `c` must be a letter a–l (or omitted).
|
||||
* Bilingual quote switcher, matching the reference theme's local quote file.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var API_BASE = 'https://v1.hitokoto.cn/';
|
||||
var TYPE_RE = /^[a-l]$/i;
|
||||
var MIN_COUNT = 1;
|
||||
var MAX_COUNT = 10;
|
||||
var DEFAULT_COUNT = 5;
|
||||
var DEFAULT_INTERVAL_SEC = 5;
|
||||
var REFRESH_MS = 30000;
|
||||
|
||||
function normalizeType(raw) {
|
||||
if (raw == null || raw === '') {
|
||||
return '';
|
||||
}
|
||||
var t = String(raw).trim().toLowerCase();
|
||||
return TYPE_RE.test(t) ? t : '';
|
||||
}
|
||||
function updateQuote(el) {
|
||||
if (el.dataset.loading === '1') return;
|
||||
el.dataset.loading = '1';
|
||||
|
||||
function normalizeCount(raw) {
|
||||
var n = parseInt(raw, 10);
|
||||
if (isNaN(n) || n < MIN_COUNT) {
|
||||
return DEFAULT_COUNT;
|
||||
}
|
||||
return Math.min(MAX_COUNT, n);
|
||||
}
|
||||
fetch(el.dataset.endpoint, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||
body: 'action=aksrt_yiyan',
|
||||
})
|
||||
.then(function (response) {
|
||||
if (!response.ok) throw new Error('HTTP ' + response.status);
|
||||
return response.json();
|
||||
})
|
||||
.then(function (response) {
|
||||
var quote = response && response.success && response.data;
|
||||
if (!quote || !quote.cn || !quote.en) throw new Error('empty quote');
|
||||
|
||||
function normalizeIntervalMs(raw) {
|
||||
var sec = parseInt(raw, 10);
|
||||
if (isNaN(sec) || sec < 3) {
|
||||
sec = DEFAULT_INTERVAL_SEC;
|
||||
}
|
||||
return Math.min(60, sec) * 1000;
|
||||
}
|
||||
|
||||
function buildApiUrl(type) {
|
||||
var params = new URLSearchParams();
|
||||
params.set('encode', 'json');
|
||||
if (type) {
|
||||
params.set('c', type);
|
||||
}
|
||||
return API_BASE + '?' + params.toString();
|
||||
}
|
||||
|
||||
function mapQuote(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return null;
|
||||
}
|
||||
var text = data.hitokoto || data.text || '';
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
text: String(text),
|
||||
from: data.from ? String(data.from) : '',
|
||||
};
|
||||
}
|
||||
|
||||
function ensureStructure(el) {
|
||||
var textEl = el.querySelector('.aksrt-yiyan__text');
|
||||
if (textEl) {
|
||||
return {
|
||||
textEl: textEl,
|
||||
fromEl: el.querySelector('.aksrt-yiyan__from'),
|
||||
dotsEl: el.querySelector('.aksrt-yiyan__dots'),
|
||||
};
|
||||
}
|
||||
|
||||
var content = document.createElement('div');
|
||||
content.className = 'aksrt-yiyan__content';
|
||||
|
||||
textEl = document.createElement('p');
|
||||
textEl.className = 'aksrt-yiyan__text';
|
||||
textEl.textContent = '加载中…';
|
||||
content.appendChild(textEl);
|
||||
|
||||
var fromEl = document.createElement('p');
|
||||
fromEl.className = 'aksrt-yiyan__from';
|
||||
content.appendChild(fromEl);
|
||||
|
||||
el.appendChild(content);
|
||||
|
||||
var dotsEl = document.createElement('div');
|
||||
dotsEl.className = 'aksrt-yiyan__dots';
|
||||
dotsEl.setAttribute('aria-hidden', 'true');
|
||||
el.appendChild(dotsEl);
|
||||
|
||||
return { textEl: textEl, fromEl: fromEl, dotsEl: dotsEl };
|
||||
}
|
||||
|
||||
function fetchOneQuote(type) {
|
||||
return fetch(buildApiUrl(type), {
|
||||
credentials: 'omit',
|
||||
mode: 'cors',
|
||||
}).then(function (r) {
|
||||
return r
|
||||
.json()
|
||||
var cn = el.querySelector('.cn');
|
||||
var en = el.querySelector('.en');
|
||||
if (cn) cn.textContent = quote.cn;
|
||||
if (en) en.textContent = quote.en;
|
||||
el.classList.remove('is-loading');
|
||||
el.classList.remove('is-error');
|
||||
el.classList.add('is-ready');
|
||||
})
|
||||
.catch(function () {
|
||||
return null;
|
||||
el.classList.remove('is-loading');
|
||||
el.classList.add('is-error');
|
||||
var cn = el.querySelector('.cn');
|
||||
if (cn && !el.classList.contains('is-ready')) cn.textContent = '加载失败,点击重试';
|
||||
})
|
||||
.then(function (data) {
|
||||
if (!r.ok) {
|
||||
var msg =
|
||||
data && data.message
|
||||
? String(data.message)
|
||||
: 'HTTP ' + r.status;
|
||||
throw new Error(msg);
|
||||
}
|
||||
var quote = mapQuote(data);
|
||||
if (!quote) {
|
||||
throw new Error('empty');
|
||||
}
|
||||
return quote;
|
||||
});
|
||||
.finally(function () {
|
||||
delete el.dataset.loading;
|
||||
});
|
||||
}
|
||||
|
||||
function fetchQuotes(type, count) {
|
||||
var jobs = [];
|
||||
var i;
|
||||
for (i = 0; i < count; i += 1) {
|
||||
jobs.push(
|
||||
fetchOneQuote(type).catch(function () {
|
||||
return null;
|
||||
})
|
||||
);
|
||||
}
|
||||
return Promise.all(jobs).then(function (results) {
|
||||
var quotes = [];
|
||||
var seen = Object.create(null);
|
||||
results.forEach(function (q) {
|
||||
if (!q || !q.text) {
|
||||
return;
|
||||
}
|
||||
// Dedupe identical text from parallel random draws.
|
||||
if (seen[q.text]) {
|
||||
return;
|
||||
}
|
||||
seen[q.text] = 1;
|
||||
quotes.push(q);
|
||||
});
|
||||
return quotes;
|
||||
});
|
||||
}
|
||||
|
||||
function initYiyan(el) {
|
||||
if (el.dataset.yiyanBound) {
|
||||
return;
|
||||
}
|
||||
function init(root) {
|
||||
var scope = root || document;
|
||||
var elements = [];
|
||||
if (scope.matches && scope.matches('[data-aksrt-yiyan]')) elements.push(scope);
|
||||
if (scope.querySelectorAll) elements = elements.concat(Array.prototype.slice.call(scope.querySelectorAll('[data-aksrt-yiyan]')));
|
||||
elements.forEach(function (el) {
|
||||
if (el.dataset.yiyanBound) return;
|
||||
el.dataset.yiyanBound = '1';
|
||||
|
||||
var count = normalizeCount(el.dataset.count);
|
||||
var interval = normalizeIntervalMs(el.dataset.interval);
|
||||
var type = normalizeType(el.dataset.type);
|
||||
var parts = ensureStructure(el);
|
||||
var textEl = parts.textEl;
|
||||
var fromEl = parts.fromEl;
|
||||
var dotsEl = parts.dotsEl;
|
||||
var quotes = [];
|
||||
var currentIndex = 0;
|
||||
var timer = null;
|
||||
|
||||
function showQuote(index) {
|
||||
if (!quotes.length || !textEl) {
|
||||
return;
|
||||
}
|
||||
var q = quotes[index % quotes.length];
|
||||
textEl.style.opacity = '0';
|
||||
setTimeout(function () {
|
||||
textEl.textContent = '"' + q.text + '"';
|
||||
textEl.style.opacity = '1';
|
||||
if (fromEl) {
|
||||
fromEl.textContent = q.from ? '—— ' + q.from : '';
|
||||
}
|
||||
}, 200);
|
||||
|
||||
if (dotsEl) {
|
||||
dotsEl.querySelectorAll('.aksrt-yiyan__dot').forEach(function (dot, i) {
|
||||
dot.classList.toggle('is-active', i === index % quotes.length);
|
||||
el.addEventListener('click', function () {
|
||||
updateQuote(el);
|
||||
});
|
||||
el.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
updateQuote(el);
|
||||
}
|
||||
}
|
||||
|
||||
function nextQuote() {
|
||||
if (quotes.length < 2) {
|
||||
return;
|
||||
}
|
||||
currentIndex = (currentIndex + 1) % quotes.length;
|
||||
showQuote(currentIndex);
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
if (quotes.length > 1) {
|
||||
timer = setInterval(nextQuote, interval);
|
||||
}
|
||||
}
|
||||
|
||||
function buildDots() {
|
||||
if (!dotsEl || quotes.length < 2) {
|
||||
if (dotsEl) {
|
||||
dotsEl.innerHTML = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
dotsEl.innerHTML = '';
|
||||
quotes.forEach(function (_, i) {
|
||||
var dot = document.createElement('span');
|
||||
dot.className = 'aksrt-yiyan__dot' + (i === 0 ? ' is-active' : '');
|
||||
dotsEl.appendChild(dot);
|
||||
});
|
||||
updateQuote(el);
|
||||
window.setInterval(function () {
|
||||
if (!document.hidden) updateQuote(el);
|
||||
}, REFRESH_MS);
|
||||
});
|
||||
}
|
||||
|
||||
el.addEventListener('mouseenter', function () {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
});
|
||||
el.addEventListener('mouseleave', function () {
|
||||
startTimer();
|
||||
});
|
||||
window.AksrtYiyan = { init: init };
|
||||
|
||||
fetchQuotes(type, count)
|
||||
.then(function (list) {
|
||||
quotes = list;
|
||||
if (!quotes.length) {
|
||||
textEl.textContent = '暂无内容';
|
||||
return;
|
||||
}
|
||||
buildDots();
|
||||
showQuote(0);
|
||||
startTimer();
|
||||
})
|
||||
.catch(function () {
|
||||
textEl.textContent = '加载失败,请稍后再试';
|
||||
if (window.jQuery) {
|
||||
window.jQuery(document).off('auto_fun.aksrt-yiyan').on('auto_fun.aksrt-yiyan', function () {
|
||||
init(document);
|
||||
});
|
||||
}
|
||||
|
||||
jQuery(document).off('auto_fun.aksrt-yiyan').on('auto_fun.aksrt-yiyan', function () {
|
||||
document.querySelectorAll('[data-aksrt-yiyan]').forEach(initYiyan);
|
||||
});
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init, { once: true });
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@@ -48,9 +48,17 @@
|
||||
});
|
||||
}
|
||||
|
||||
function resolveWidgetNumber(inputName, widgetId, fallback) {
|
||||
var inputMatch = String(inputName || '').match(/\[(\d+)\]/);
|
||||
var idMatch = String(widgetId || '').match(/-(\d+)$/);
|
||||
var number = inputMatch ? inputMatch[1] : (idMatch ? idMatch[1] : String(fallback || ''));
|
||||
return /^\d+$/.test(number) && Number(number) > 0 ? number : '';
|
||||
}
|
||||
|
||||
return {
|
||||
parseDependency: parseDependency,
|
||||
dependencyVisible: dependencyVisible,
|
||||
replaceRepeaterIndexes: replaceRepeaterIndexes
|
||||
replaceRepeaterIndexes: replaceRepeaterIndexes,
|
||||
resolveWidgetNumber: resolveWidgetNumber
|
||||
};
|
||||
});
|
||||
+80
-19
@@ -1,13 +1,15 @@
|
||||
.aksrt-widget-form {
|
||||
--aksrt-field-border: #dcdcde;
|
||||
--aksrt-field-accent: #2271b1;
|
||||
--aksrt-field-accent: #0991f5;
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
padding: 4px 0 10px;
|
||||
gap: 0;
|
||||
padding: 0 0 8px;
|
||||
}
|
||||
|
||||
.aksrt-widget-field {
|
||||
min-width: 0;
|
||||
padding: 11px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.aksrt-widget-field[hidden] {
|
||||
@@ -18,12 +20,27 @@
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 600;
|
||||
color: #0991f5;
|
||||
}
|
||||
|
||||
.customize-control-widget_form .aksrt-widget-field__label { color: inherit; }
|
||||
|
||||
.aksrt-widget-field .description {
|
||||
margin: -2px 0 7px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.aksrt-widget-field--switcher {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
column-gap: 12px;
|
||||
}
|
||||
|
||||
.aksrt-widget-field--switcher .aksrt-widget-field__label { margin: 0; }
|
||||
.aksrt-widget-field--switcher .description { grid-column: 1 / -1; grid-row: 2; }
|
||||
.aksrt-widget-field--switcher > .aksrt-widget-switch { grid-column: 2; grid-row: 1; }
|
||||
|
||||
.aksrt-widget-switch {
|
||||
display: inline-flex;
|
||||
cursor: pointer;
|
||||
@@ -70,12 +87,12 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.aksrt-widget-choices label span {
|
||||
.aksrt-widget-choices label > span {
|
||||
display: block;
|
||||
padding: 5px 9px;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid var(--aksrt-field-border);
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
background: #f7f8fa;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -85,6 +102,14 @@
|
||||
box-shadow: 0 0 0 1px var(--aksrt-field-accent);
|
||||
}
|
||||
|
||||
.aksrt-widget-choices label input:focus-visible + span { outline: 2px solid #72aee6; outline-offset: 2px; }
|
||||
|
||||
.aksrt-widget-choices--palette { display: grid; grid-template-columns: repeat(auto-fill, minmax(46px, 1fr)); gap: 5px; }
|
||||
.aksrt-widget-choices--palette label > span { padding: 4px; text-align: center; }
|
||||
.aksrt-widget-palette-choice { display: grid; justify-items: center; gap: 3px; }
|
||||
.aksrt-widget-palette-choice i { display: block; width: 100%; height: 18px; border: 1px solid rgba(0, 0, 0, .12); border-radius: 3px; }
|
||||
.aksrt-widget-palette-choice small { font-size: 10px; line-height: 1.2; }
|
||||
|
||||
.aksrt-widget-number,
|
||||
.aksrt-widget-link,
|
||||
.aksrt-widget-background {
|
||||
@@ -130,7 +155,7 @@
|
||||
.aksrt-widget-nested {
|
||||
margin-bottom: 9px;
|
||||
border: 1px solid var(--aksrt-field-border);
|
||||
border-radius: 6px;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
@@ -140,15 +165,42 @@
|
||||
gap: 7px;
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid var(--aksrt-field-border);
|
||||
background: #f6f7f7;
|
||||
background: #f9fafc;
|
||||
}
|
||||
|
||||
.aksrt-widget-repeater__bar::-webkit-details-marker { display: none; }
|
||||
.aksrt-widget-repeater__bar { list-style: none; cursor: pointer; }
|
||||
.aksrt-widget-repeater__bar::after { content: "\f140"; margin-left: auto; font: 20px/1 dashicons; color: #646970; }
|
||||
.aksrt-widget-repeater__row[open] > .aksrt-widget-repeater__bar::after { content: "\f142"; }
|
||||
.aksrt-widget-repeater__bar .dashicons { cursor: move; }
|
||||
.aksrt-widget-repeater__bar strong { flex: 1; }
|
||||
.aksrt-widget-repeater__remove:disabled { visibility: hidden; }
|
||||
.aksrt-widget-repeater__add:disabled { opacity: .55; cursor: not-allowed; }
|
||||
.aksrt-widget-repeater__fields,
|
||||
.aksrt-widget-nested__body { display: grid; gap: 12px; padding: 10px; }
|
||||
.aksrt-widget-nested > summary { padding: 9px 10px; font-weight: 600; cursor: pointer; }
|
||||
|
||||
.aksrt-widget-form input[type="text"],
|
||||
.aksrt-widget-form input[type="url"],
|
||||
.aksrt-widget-form input[type="number"],
|
||||
.aksrt-widget-form select,
|
||||
.aksrt-widget-form textarea {
|
||||
min-height: 32px;
|
||||
border-color: #d5d8dc;
|
||||
border-radius: 3px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.aksrt-widget-form input:focus,
|
||||
.aksrt-widget-form select:focus,
|
||||
.aksrt-widget-form textarea:focus {
|
||||
border-color: #0991f5;
|
||||
box-shadow: 0 0 0 1px #0991f5;
|
||||
}
|
||||
|
||||
.aksrt-widget-form .button { min-height: 32px; line-height: 30px; border-radius: 3px; }
|
||||
.aksrt-widget-form .button-primary { background: #0991f5; border-color: #087bcf; }
|
||||
|
||||
.aksrt-widget-form-lazy {
|
||||
padding: 14px;
|
||||
border: 1px dashed #c3c4c7;
|
||||
@@ -175,20 +227,29 @@
|
||||
color: #614200;
|
||||
}
|
||||
|
||||
.aksrt-widget-transfer { margin: 16px 20px 16px 0; }
|
||||
.aksrt-widget-transfer__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; }
|
||||
.aksrt-widget-transfer form { display: grid; align-content: start; gap: 9px; padding: 12px; border: 1px solid #dcdcde; border-radius: 6px; }
|
||||
.aksrt-widget-transfer form h3 { margin: 0 0 3px; }
|
||||
.aksrt-widget-transfer select,
|
||||
.aksrt-widget-transfer input[type="file"] { width: 100%; }
|
||||
.aksrt-widget-transfer-toolbar { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 12px; }
|
||||
.aksrt-widget-transfer-toolbar .button { margin: 0; }
|
||||
.aksrt-widget-customizer-remove { margin-left: 8px; color: #b32d2e; }
|
||||
.aksrt-widget-transfer-modal[hidden] { display: none !important; }
|
||||
.aksrt-widget-transfer-modal.is-open { position: fixed; inset: 0; z-index: 1000100; display: flex; align-items: center; justify-content: center; padding: 20px; }
|
||||
.aksrt-widget-transfer-modal__backdrop { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; background: rgba(0, 0, 0, .45); }
|
||||
.aksrt-widget-transfer-modal__dialog { position: relative; z-index: 1; box-sizing: border-box; width: min(720px, 96vw); max-height: 90vh; overflow: auto; padding: 18px 20px 16px; border: 1px solid #c3c4c7; border-radius: 6px; background: #fff; box-shadow: 0 8px 32px rgba(0, 0, 0, .14); }
|
||||
.aksrt-widget-transfer-modal__dialog h2 { margin: 0 36px 10px 0; font-size: 16px; }
|
||||
.aksrt-widget-transfer-modal__area { margin: 0 0 12px; }
|
||||
.aksrt-widget-transfer-modal__close { position: absolute; top: 10px; right: 10px; width: 32px; height: 32px; border: 0; background: transparent; color: #646970; font-size: 22px; cursor: pointer; }
|
||||
.aksrt-widget-transfer-modal__tabs { display: flex; gap: 6px; margin-bottom: 14px; padding-bottom: 10px; border-bottom: 1px solid #dcdcde; }
|
||||
.aksrt-widget-transfer-modal__tabs .is-active { border-color: #0991f5; color: #087bcf; box-shadow: inset 0 -2px #0991f5; }
|
||||
.aksrt-widget-transfer-modal__panel form { display: grid; align-content: start; gap: 10px; }
|
||||
.aksrt-widget-transfer-modal__panel select,
|
||||
.aksrt-widget-transfer-modal__panel input[type="file"] { width: 100%; }
|
||||
.aksrt-widget-transfer__actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
.aksrt-widget-template-preview-output { grid-column: 1 / -1; max-height: 520px; overflow: auto; padding: 14px; border: 1px solid #dcdcde; background: #fff; }
|
||||
.aksrt-widget-template-preview-output { max-height: 420px; overflow: auto; margin-top: 14px; padding: 14px; border: 1px solid #dcdcde; background: #fff; }
|
||||
.aksrt-widget-template-preview__area { margin-bottom: 18px; }
|
||||
.aksrt-widget-template-preview__module { margin: 10px 0; padding: 12px; border: 1px dashed #c3c4c7; border-radius: 6px; }
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.aksrt-widget-transfer__grid { grid-template-columns: 1fr; }
|
||||
.aksrt-widget-template-preview-output { grid-column: auto; }
|
||||
@media (max-width: 600px) {
|
||||
.aksrt-widget-transfer-modal.is-open { padding: 10px; }
|
||||
.aksrt-widget-transfer-modal__dialog { width: 100%; padding: 16px; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
+154
-7
@@ -23,6 +23,7 @@
|
||||
evaluateDependencies($root);
|
||||
updateWidgetTitle($root);
|
||||
updatePlacementWarning($root);
|
||||
updateRepeaterControls($root);
|
||||
}
|
||||
|
||||
/* terms 字段:以“已选标签”为唯一真源,同步隐藏输入(选择顺序)与复选框。 */
|
||||
@@ -63,17 +64,91 @@
|
||||
$hint.toggleClass('is-warning', (size === 'wide' && isSidebar) || (size === 'sidebar' && !isSidebar && area !== ''));
|
||||
}
|
||||
|
||||
function injectTransferToolbar($anchor, sidebarId) {
|
||||
if (!$anchor?.length || !sidebarId || $anchor.next('.aksrt-widget-transfer-toolbar').length) return;
|
||||
const $toolbar = $('<div class="aksrt-widget-transfer-toolbar" role="group"></div>');
|
||||
[
|
||||
['export', config.i18n?.export || '导出'],
|
||||
['import', config.i18n?.import || '导入'],
|
||||
['template', config.i18n?.template || '应用模板'],
|
||||
].forEach(([action, label]) => {
|
||||
$('<button type="button" class="button button-small"></button>')
|
||||
.text(label)
|
||||
.attr('data-aksrt-transfer-action', action)
|
||||
.attr('data-sidebar-id', sidebarId)
|
||||
.appendTo($toolbar);
|
||||
});
|
||||
$anchor.after($toolbar);
|
||||
}
|
||||
|
||||
function scanWidgetAreas() {
|
||||
$('.widgets-holder-wrap:not(#available-widgets):not(.inactive-sidebar)').each(function () {
|
||||
const $wrap = $(this);
|
||||
const $sortables = $wrap.find('.widgets-sortables').first();
|
||||
const sidebarId = String($sortables.attr('id') || '');
|
||||
if (!sidebarId) return;
|
||||
const $description = $sortables.find('.sidebar-description .description').first();
|
||||
const $anchor = $description.length ? $description : $sortables.find('.description').first();
|
||||
if ($anchor.length) injectTransferToolbar($anchor, sidebarId);
|
||||
});
|
||||
|
||||
$('.control-section-sidebar').each(function () {
|
||||
const $section = $(this);
|
||||
const sectionId = String($section.attr('id') || '');
|
||||
const prefix = 'sub-accordion-section-sidebar-widgets-';
|
||||
if (!sectionId.startsWith(prefix)) return;
|
||||
const sidebarId = sectionId.slice(prefix.length);
|
||||
const $description = $section.find('.section-meta .description').first();
|
||||
const $anchor = $description.length ? $description : $section.find('.accordion-section-title, .customize-section-title').first();
|
||||
if ($anchor.length) injectTransferToolbar($anchor, sidebarId);
|
||||
});
|
||||
|
||||
$('.customize-control-widget_form').each(function () {
|
||||
const $control = $(this);
|
||||
const $nav = $control.find('.widget-reorder-nav').first();
|
||||
if (!$nav.length || $nav.find('.aksrt-widget-customizer-remove').length) return;
|
||||
$('<button type="button" class="button-link-delete aksrt-widget-customizer-remove" aria-label="移除此小工具">移除</button>').appendTo($nav);
|
||||
});
|
||||
}
|
||||
|
||||
function openTransferModal(action, sidebarId) {
|
||||
const $modal = $('#aksrt-widget-transfer-modal');
|
||||
if (!$modal.length) return;
|
||||
const sidebars = config.sidebars || {};
|
||||
const areaName = sidebars[sidebarId] || sidebarId;
|
||||
$modal.find('.aksrt-widget-transfer-modal__area').text(areaName);
|
||||
$modal.find('input[name="sidebar"], input[name="target_sidebar"]').val(sidebarId);
|
||||
$modal.find('[data-transfer-tab]').removeClass('is-active');
|
||||
$modal.find(`[data-transfer-tab="${action}"]`).addClass('is-active');
|
||||
$modal.find('[data-transfer-panel]').prop('hidden', true);
|
||||
$modal.find(`[data-transfer-panel="${action}"]`).prop('hidden', false);
|
||||
$modal.prop('hidden', false).addClass('is-open').attr('aria-hidden', 'false');
|
||||
$modal.find('[data-transfer-panel="' + action + '"] button, [data-transfer-panel="' + action + '"] input, [data-transfer-panel="' + action + '"] select').filter(':visible').first().trigger('focus');
|
||||
}
|
||||
|
||||
function closeTransferModal() {
|
||||
$('#aksrt-widget-transfer-modal').removeClass('is-open').attr('aria-hidden', 'true').prop('hidden', true);
|
||||
}
|
||||
|
||||
function loadForm(container) {
|
||||
const $container = $(container);
|
||||
if ($container.data('loading') || $container.data('loaded')) return;
|
||||
const $widget = $container.closest('.widget, .customize-control-widget_form');
|
||||
const inputName = String($widget.find('input[name^="widget-"]').first().attr('name') || '');
|
||||
const number = core.resolveWidgetNumber(inputName, $widget.attr('id'), $container.data('widget-number'));
|
||||
// New-widget templates use __i__ until WordPress assigns a real instance number.
|
||||
if (!number) return;
|
||||
$container.data('loading', true).find('.aksrt-widget-form-lazy__status').text(config.i18n?.loading || 'Loading…');
|
||||
$.post(config.ajaxUrl, {
|
||||
action: config.action,
|
||||
nonce: config.nonce,
|
||||
id_base: $container.data('id-base'),
|
||||
widget_number: $container.data('widget-number'),
|
||||
widget_number: number,
|
||||
}).done((response) => {
|
||||
if (!response?.success || !response.data?.html) throw new Error('Invalid response');
|
||||
if (!response?.success || !response.data?.html) {
|
||||
$container.data('loading', false).find('.aksrt-widget-form-lazy__status').text(response?.data?.message || config.i18n?.error || 'Failed to load.');
|
||||
return;
|
||||
}
|
||||
const $form = $(response.data.html);
|
||||
$container.replaceWith($form);
|
||||
initForm($form);
|
||||
@@ -94,6 +169,31 @@
|
||||
if (!rowIndexes.length) return;
|
||||
this.name = core.replaceRepeaterIndexes(this.name, rowIndexes);
|
||||
});
|
||||
updateRepeaterTitles($form);
|
||||
}
|
||||
|
||||
function updateRepeaterTitles($form) {
|
||||
$form.find('.aksrt-widget-repeater__rows').each(function () {
|
||||
$(this).children('.aksrt-widget-repeater__row').each(function (index) {
|
||||
const $row = $(this);
|
||||
const $title = $row.find('.aksrt-widget-repeater__title').first();
|
||||
const prefix = String($title.data('title-prefix') || '项目');
|
||||
const $firstControl = $row.children('.aksrt-widget-repeater__fields').find('input:not([type="hidden"]), select, textarea').first();
|
||||
const value = String($firstControl.val() || '').trim();
|
||||
$title.text(prefix + (index + 1) + (value ? ':' + value : ''));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateRepeaterControls($root) {
|
||||
$root.filter('.aksrt-widget-repeater').add($root.find('.aksrt-widget-repeater')).each(function () {
|
||||
const $repeater = $(this);
|
||||
const count = $repeater.children('.aksrt-widget-repeater__rows').children('.aksrt-widget-repeater__row').length;
|
||||
const min = Number($repeater.data('min-items') || 0);
|
||||
const max = Number($repeater.data('max-items') || 50);
|
||||
$repeater.children('.aksrt-widget-repeater__rows').children('.aksrt-widget-repeater__row').children('.aksrt-widget-repeater__bar').children('.aksrt-widget-repeater__remove').prop('disabled', count <= min);
|
||||
$repeater.children('.aksrt-widget-repeater__add').prop('disabled', count >= max);
|
||||
});
|
||||
}
|
||||
|
||||
function fieldValue($form, id) {
|
||||
@@ -116,12 +216,29 @@
|
||||
}
|
||||
|
||||
$(document)
|
||||
.on('click', '[data-aksrt-transfer-action]', function () {
|
||||
openTransferModal(String($(this).data('aksrt-transfer-action')), String($(this).data('sidebar-id')));
|
||||
})
|
||||
.on('click', '[data-aksrt-transfer-close]', closeTransferModal)
|
||||
.on('click', '[data-transfer-tab]', function () {
|
||||
const $modal = $(this).closest('.aksrt-widget-transfer-modal');
|
||||
const panel = String($(this).data('transfer-tab'));
|
||||
$modal.find('[data-transfer-tab]').removeClass('is-active');
|
||||
$(this).addClass('is-active');
|
||||
$modal.find('[data-transfer-panel]').prop('hidden', true);
|
||||
$modal.find(`[data-transfer-panel="${panel}"]`).prop('hidden', false);
|
||||
})
|
||||
.on('click', '.aksrt-widget-customizer-remove', function (event) {
|
||||
event.preventDefault();
|
||||
$(this).closest('.customize-control-widget_form').find('.widget-control-remove').first().trigger('click');
|
||||
})
|
||||
.on('click', '.aksrt-widget-form-lazy__load', function () { loadForm($(this).closest('.aksrt-widget-form-lazy')); })
|
||||
.on('click', '.widget.open .aksrt-widget-form-lazy', function () { loadForm(this); })
|
||||
.on('click', '.widget.open .aksrt-widget-form-lazy, .customize-control-widget_form .aksrt-widget-form-lazy', function () { loadForm(this); })
|
||||
.on('change input', '.aksrt-widget-form input, .aksrt-widget-form select, .aksrt-widget-form textarea', function () {
|
||||
const $form = $(this).closest('.aksrt-widget-form');
|
||||
evaluateDependencies($form);
|
||||
updateWidgetTitle($form);
|
||||
updateRepeaterTitles($form);
|
||||
})
|
||||
.on('click', '.aksrt-widget-repeater__add', function () {
|
||||
const $repeater = $(this).closest('.aksrt-widget-repeater');
|
||||
@@ -134,11 +251,17 @@
|
||||
const $row = $(html).appendTo($rows);
|
||||
initForm($row);
|
||||
renumberRepeaters.call($rows[0]);
|
||||
updateRepeaterControls($repeater);
|
||||
})
|
||||
.on('click', '.aksrt-widget-repeater__remove', function () {
|
||||
const $rows = $(this).closest('.aksrt-widget-repeater__rows');
|
||||
.on('click', '.aksrt-widget-repeater__remove', function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const $repeater = $(this).closest('.aksrt-widget-repeater');
|
||||
const $rows = $repeater.children('.aksrt-widget-repeater__rows');
|
||||
if ($rows.children('.aksrt-widget-repeater__row').length <= Number($repeater.data('min-items') || 0)) return;
|
||||
$(this).closest('.aksrt-widget-repeater__row').remove();
|
||||
renumberRepeaters.call($rows[0]);
|
||||
updateRepeaterControls($repeater);
|
||||
})
|
||||
.on('click', '.aksrt-widget-terms__remove', function () {
|
||||
$(this).closest('.aksrt-widget-terms__chip').remove();
|
||||
@@ -150,6 +273,7 @@
|
||||
const id = String(this.value);
|
||||
const name = (option.querySelector('[data-term-name]') || option).getAttribute('data-term-name');
|
||||
if (this.checked) addTermChip(wrap, id, name || id);
|
||||
else $(wrap).find('.aksrt-widget-terms__chip').filter((_index, chip) => String($(chip).data('term-id')) === id).remove();
|
||||
syncTerms(wrap);
|
||||
})
|
||||
.on('input', '.aksrt-widget-terms__search', function () {
|
||||
@@ -186,17 +310,40 @@
|
||||
nonce: config.transferNonce,
|
||||
template: $form.find('[name="template"]').val(),
|
||||
}).done((response) => {
|
||||
if (!response?.success) throw new Error('Invalid response');
|
||||
if (!response?.success || !response.data) {
|
||||
$output.empty().append($('<p class="notice notice-error"></p>').text(response?.data?.message || config.i18n?.error || '预览加载失败,请重试。'));
|
||||
return;
|
||||
}
|
||||
$output.html(response.data.html || '<p>模板为空。</p>');
|
||||
}).fail(() => $output.html('<p class="notice notice-error">预览加载失败。</p>'));
|
||||
}).fail((xhr) => {
|
||||
const message = xhr?.responseJSON?.data?.message || config.i18n?.error || '预览加载失败,请重试。';
|
||||
$output.empty().append($('<p class="notice notice-error"></p>').text(message));
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('widget-added widget-updated', function (_event, widget) {
|
||||
$(widget).find('.aksrt-widget-form-lazy').each(function () { loadForm(this); });
|
||||
updatePlacementWarning(widget);
|
||||
scanWidgetAreas();
|
||||
});
|
||||
|
||||
if (window.wp?.customize?.bind) {
|
||||
window.wp.customize.bind('ready', function () {
|
||||
$('#customize-theme-controls').on('expanded', function (_event, section) {
|
||||
if (!section || !String(section.id || section).includes('sidebar-widgets')) return;
|
||||
window.setTimeout(function () {
|
||||
scanWidgetAreas();
|
||||
$('.customize-control-widget_form .aksrt-widget-form-lazy').each(function () { loadForm(this); });
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(function () {
|
||||
scanWidgetAreas();
|
||||
$(document).on('keydown', function (event) {
|
||||
if (event.key === 'Escape') closeTransferModal();
|
||||
});
|
||||
$('.aksrt-widget-form').each(function () { initForm(this); });
|
||||
$('.widget.open .aksrt-widget-form-lazy').each(function () { loadForm(this); });
|
||||
});
|
||||
|
||||
@@ -50,16 +50,19 @@
|
||||
.aksrt-widget-search--compact button span { display: none; }
|
||||
.aksrt-widget-search--large { min-height: 58px; border-radius: 14px; }
|
||||
|
||||
.aksrt-widget-quote { position: relative; min-height: 130px; margin: 0; padding: 24px 48px 24px 25px; overflow: hidden; border-radius: var(--aksrt-widget-radius); background: linear-gradient(135deg, #6772e5, #8b5cf6); color: #fff; }
|
||||
.aksrt-widget-quote > i { opacity: .35; font-size: 26px; }
|
||||
.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-quote { margin: 0; padding: 12px 15px; border-radius: var(--aksrt-widget-radius); background: var(--main-bg-color, #fff); color: var(--main-color, inherit); box-shadow: 0 0 10px var(--main-shadow, rgba(116,116,116,.08)); }
|
||||
.aksrt-widget-quote .yiyan { line-height: 1.8; }
|
||||
.aksrt-widget-quote .yiyan .en { font-size: .9em; opacity: .8; }
|
||||
.aksrt-widget-quote.is-loading { opacity: .65; }
|
||||
|
||||
/* The AKSRT base stylesheet already carries Zibll's icon-card and utility rules.
|
||||
* Restore the one renamed primitive used verbatim by these two widget renderers. */
|
||||
.zib-widget { clear:both; margin-bottom:20px; padding:15px; 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)); }
|
||||
.zib-widget.padding-10 { padding:10px; }
|
||||
.zib-widget.nobottom.notop { padding-top:0; padding-bottom:0; }
|
||||
.zib-widget.posts-mini-lists { padding:5px; }
|
||||
.zib-widget.posts-mini-lists .relative > .tab-nav-theme ~ .scroll-x-prev,
|
||||
.zib-widget.posts-mini-lists .relative > .tab-nav-theme ~ .scroll-x-next { top:10px; }
|
||||
|
||||
.zib-widget-wrap { padding:var(--aksrt-widget-space-top,0) var(--aksrt-widget-space-right,0) var(--aksrt-widget-space-bottom,0) var(--aksrt-widget-space-left,0); background-color:var(--aksrt-widget-bg-light,transparent); background-image:var(--aksrt-widget-bg-image-light,none); background-position:center; background-size:cover; }
|
||||
.dark-theme .zib-widget-wrap, [data-theme="dark"] .zib-widget-wrap { background-color:var(--aksrt-widget-bg-dark,var(--aksrt-widget-bg-light,transparent)); background-image:var(--aksrt-widget-bg-image-dark,var(--aksrt-widget-bg-image-light,none)); }
|
||||
@@ -151,12 +154,7 @@
|
||||
/* Zibll modern widget contracts */
|
||||
.widget-tab-post .tab-content > .tab-pane { display: none; }
|
||||
.widget-tab-post .tab-content > .tab-pane.active { display: block; }
|
||||
.widget-tab-post .list-inline { margin: 0 0 15px; padding: 0; list-style: none; }
|
||||
.widget-tab-post .list-inline > li { display: inline-block; margin: 0 15px 0 0; }
|
||||
.widget-tab-post .list-inline > li > a { color: inherit; text-decoration: none; cursor: pointer; }
|
||||
.widget-tab-post .list-inline > li.active > a { color: var(--focus-color, #f04494); }
|
||||
.widget-tab-post .tab-nav-theme > li { position: relative; padding-bottom: 5px; }
|
||||
.widget-tab-post .tab-nav-theme > li.active::before { content: ''; position: absolute; right: 0; bottom: 0; left: 0; height: 2px; background: var(--focus-color, #f04494); }
|
||||
.widget-tab-post, .term-aggregation { line-height:1.42857143; }
|
||||
.widget-tab-post .post_ajax_loader { min-height: 40px; }
|
||||
/* Widget pagination mirrors the reference .ajax-next rules while staying on the REST client. */
|
||||
.theme-pagination { margin: 20px 0; }
|
||||
|
||||
@@ -74,26 +74,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
function updateQuote(figure) {
|
||||
if (figure.dataset.source !== 'api' || !config.quoteUrl) return Promise.resolve();
|
||||
var button = figure.querySelector('button');
|
||||
if (button) button.disabled = true;
|
||||
return fetch(config.quoteUrl, { credentials: 'same-origin', headers: { Accept: 'application/json' } })
|
||||
.then(function (response) { if (!response.ok) throw new Error('quote'); return response.json(); })
|
||||
.then(function (data) {
|
||||
if (data && data.text) figure.querySelector('blockquote').textContent = data.text;
|
||||
})
|
||||
.catch(function () {})
|
||||
.finally(function () { if (button) button.disabled = false; });
|
||||
}
|
||||
|
||||
function initQuote(figure) {
|
||||
if (!once(figure, 'Quote')) return;
|
||||
var button = figure.querySelector('button');
|
||||
if (button) button.addEventListener('click', function () { updateQuote(figure); });
|
||||
if (!reducedMotion && figure.dataset.source === 'api') {
|
||||
window.setInterval(function () { if (!document.hidden) updateQuote(figure); }, Math.max(5, Number(figure.dataset.interval || 30)) * 1000);
|
||||
}
|
||||
if (window.AksrtYiyan) window.AksrtYiyan.init(figure);
|
||||
}
|
||||
|
||||
function errorMarkup() {
|
||||
@@ -187,6 +170,7 @@
|
||||
Array.prototype.slice.call(container.querySelectorAll(':scope > .ajax-pag')).forEach(function (node) { node.remove(); });
|
||||
if (data.paginate_html) container.insertAdjacentHTML('beforeend', data.paginate_html);
|
||||
container.dataset.page = String(data.page || page);
|
||||
container.dataset.loaded = '1';
|
||||
anchorLoader(container);
|
||||
scan(container);
|
||||
})
|
||||
@@ -247,7 +231,7 @@
|
||||
if (!panel) return;
|
||||
panel.dataset.segment = button.dataset.segment;
|
||||
panel.setAttribute('aria-labelledby', button.id);
|
||||
loadItems(panel, 1, false);
|
||||
if (panel.dataset.loaded !== '1') loadItems(panel, 1, false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+32
-102
@@ -33,124 +33,54 @@ if (!$term instanceof WP_Term) {
|
||||
return;
|
||||
}
|
||||
|
||||
$archive_settings = class_exists('\AKSRT\Core\TermMeta')
|
||||
? \AKSRT\Core\TermMeta::resolve_archive_settings($term)
|
||||
: [
|
||||
'header_style' => 'cover',
|
||||
'show_sidebar' => true,
|
||||
'show_cover' => false,
|
||||
'show_desc' => !empty($term->description),
|
||||
'filter_bar' => true,
|
||||
'cover_url' => '',
|
||||
'description' => (string) $term->description,
|
||||
];
|
||||
|
||||
$has_sidebar = function_exists('aksrt_archive_has_sidebar')
|
||||
? aksrt_archive_has_sidebar(!empty($archive_settings['show_sidebar']))
|
||||
: (!empty($archive_settings['show_sidebar']) && is_active_sidebar('cat_sidebar'));
|
||||
$description = !empty($archive_settings['show_desc']) ? wp_kses_post((string) $archive_settings['description']) : '';
|
||||
$header_style = (string) $archive_settings['header_style'];
|
||||
$archive_settings = \AKSRT\Core\TermMeta::resolve_archive_settings($term);
|
||||
$has_sidebar = aksrt_archive_has_sidebar(!empty($archive_settings['show_sidebar']));
|
||||
$ajax_filter = !empty($archive_settings['filter_bar']);
|
||||
$sort_state = function_exists('aksrt_archive_get_sort_state')
|
||||
? aksrt_archive_get_sort_state((array) ($archive_settings['orderby_option'] ?? []))
|
||||
: ['current' => 'date', 'links' => []];
|
||||
$current_sort = $sort_state['current'];
|
||||
$sort_links = $sort_state['links'];
|
||||
$render_config = function_exists('aksrt_archive_get_render_config')
|
||||
? aksrt_archive_get_render_config(['context' => 'category'])
|
||||
: ['context' => 'category'];
|
||||
|
||||
$sort_state = aksrt_archive_get_sort_state((array) ($archive_settings['orderby_option'] ?? []));
|
||||
$filter_groups = [];
|
||||
if (!empty($archive_settings['ajax_category_enabled']) && function_exists('aksrt_archive_build_term_filter_group')) {
|
||||
$group = aksrt_archive_build_term_filter_group('category', (array) ($archive_settings['ajax_category'] ?? []), __('分类', 'aksrt'), $ajax_filter);
|
||||
if ($group !== []) {
|
||||
foreach (['category' => __('分类', 'aksrt'), 'post_tag' => __('标签', 'aksrt')] as $taxonomy => $label) {
|
||||
$key = $taxonomy === 'category' ? 'ajax_category' : 'ajax_tag';
|
||||
if (!empty($archive_settings[$key . '_enabled'])) {
|
||||
$group = aksrt_archive_build_term_filter_group($taxonomy, (array) $archive_settings[$key], $label);
|
||||
if ($group) {
|
||||
$filter_groups[] = $group;
|
||||
}
|
||||
}
|
||||
if (!empty($archive_settings['ajax_tag_enabled']) && function_exists('aksrt_archive_build_term_filter_group')) {
|
||||
$group = aksrt_archive_build_term_filter_group('post_tag', (array) ($archive_settings['ajax_tag'] ?? []), __('标签', 'aksrt'), $ajax_filter);
|
||||
if ($group !== []) {
|
||||
$filter_groups[] = $group;
|
||||
}
|
||||
if (!empty($archive_settings['custom_filter_enabled'])) {
|
||||
$filter_groups = array_merge($filter_groups, aksrt_archive_build_custom_filter_groups(
|
||||
(array) $archive_settings['custom_dimensions'], $archive_settings['custom_filter_keys']
|
||||
));
|
||||
}
|
||||
if (!empty($archive_settings['custom_filter_enabled']) && function_exists('aksrt_archive_build_custom_filter_groups')) {
|
||||
$filter_groups = array_merge(
|
||||
$filter_groups,
|
||||
aksrt_archive_build_custom_filter_groups(
|
||||
(array) ($archive_settings['custom_dimensions'] ?? []),
|
||||
$archive_settings['custom_filter_keys'] ?? 'all',
|
||||
$ajax_filter
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_archive_open_shell')) {
|
||||
aksrt_archive_open_shell([
|
||||
'main_class' => 'site-main container',
|
||||
'has_sidebar' => $has_sidebar,
|
||||
'top_fluid' => ['cat_top_fluid'],
|
||||
'page_kind' => 'category',
|
||||
'main_class' => 'site-main container', 'has_sidebar' => $has_sidebar,
|
||||
'top_fluid' => ['cat_top_fluid'], 'page_kind' => 'category',
|
||||
]);
|
||||
}
|
||||
?>
|
||||
<?php if (function_exists('aksrt_archive_render_widget_zone')) { aksrt_archive_render_widget_zone(['cat_top_content']); } elseif (is_active_sidebar('cat_top_content')) { dynamic_sidebar('cat_top_content'); } ?>
|
||||
|
||||
<div class="content-main content-main--archive content-main--category">
|
||||
<?php if (function_exists('aksrt_breadcrumbs')) { aksrt_breadcrumbs(); } ?>
|
||||
|
||||
<?php
|
||||
if (function_exists('aksrt_archive_render_term_header')) {
|
||||
aksrt_archive_render_widget_zone(['cat_top_content']);
|
||||
aksrt_archive_render_term_header([
|
||||
'header_style' => $header_style,
|
||||
'header_style' => $archive_settings['header_style'],
|
||||
'show_cover' => !empty($archive_settings['show_cover']),
|
||||
'cover_url' => (string) ($archive_settings['cover_url'] ?? ''),
|
||||
'eyebrow' => __('分类归档', 'aksrt'),
|
||||
'cover_url' => (string) $archive_settings['cover_url'],
|
||||
'title' => single_cat_title('', false),
|
||||
'description' => $description,
|
||||
'admin_path' => 'page-display/category',
|
||||
'admin_label' => __('分类页设置', 'aksrt'),
|
||||
'description' => !empty($archive_settings['show_desc']) ? $archive_settings['description'] : '',
|
||||
'post_count' => !empty($archive_settings['post_count']),
|
||||
]);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
if (!empty($archive_settings['filter_bar']) && function_exists('aksrt_archive_render_filter_bar')) {
|
||||
ob_start();
|
||||
if ($ajax_filter) {
|
||||
aksrt_archive_render_filter_bar([
|
||||
'ajax' => $ajax_filter,
|
||||
'ajax_archive' => 'category',
|
||||
'term_id' => (int) $term->term_id,
|
||||
'aria_label' => __('分类筛选', 'aksrt'),
|
||||
'meta_text' => !empty($archive_settings['post_count']) ? sprintf(_n('%d 篇文章', '%d 篇文章', (int) $term->count, 'aksrt'), (int) $term->count) : '',
|
||||
'groups' => $filter_groups,
|
||||
'aria_label' => __('分类筛选', 'aksrt'), 'groups' => $filter_groups,
|
||||
'sort_enabled' => !empty($archive_settings['orderby_enabled']),
|
||||
'sort_current' => $current_sort,
|
||||
'sort_links' => $sort_links,
|
||||
'sort_labels' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::homepage_sort_labels() : [],
|
||||
'sort_as_main' => false,
|
||||
'sort_current' => $sort_state['current'], 'sort_links' => $sort_state['links'],
|
||||
'sort_labels' => \AKSRT\Settings\Settings::homepage_sort_labels(),
|
||||
'sort_option' => (array) $archive_settings['orderby_option'],
|
||||
]);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
if (function_exists('aksrt_archive_render_stream')) {
|
||||
aksrt_archive_render_stream(null, $render_config, [
|
||||
'page_kind' => 'category',
|
||||
'query_context' => 'archive',
|
||||
'stream_class' => 'archive__ajax-wrap content-stream--archive content-stream--category',
|
||||
'empty_text' => __('该分类下暂无文章', 'aksrt'),
|
||||
'ajax_enabled' => $ajax_filter,
|
||||
$filter_html = (string) ob_get_clean();
|
||||
aksrt_archive_render_stream(null, aksrt_archive_get_render_config(['context' => 'category']), [
|
||||
'page_kind' => 'category', 'query_context' => 'archive',
|
||||
'empty_text' => __('暂无内容', 'aksrt'),
|
||||
'filter_html' => $filter_html,
|
||||
]);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if (function_exists('aksrt_archive_render_widget_zone')) { aksrt_archive_render_widget_zone(['cat_bottom_content']); } elseif (is_active_sidebar('cat_bottom_content')) { dynamic_sidebar('cat_bottom_content'); } ?>
|
||||
</div>
|
||||
<?php
|
||||
if (function_exists('aksrt_archive_close_shell')) {
|
||||
aksrt_archive_close_shell([
|
||||
'has_sidebar' => $has_sidebar,
|
||||
'bottom_fluid' => ['cat_bottom_fluid'],
|
||||
]);
|
||||
}
|
||||
|
||||
aksrt_archive_render_widget_zone(['cat_bottom_content']);
|
||||
aksrt_archive_close_shell(['has_sidebar' => $has_sidebar, 'bottom_fluid' => ['cat_bottom_fluid']]);
|
||||
get_footer();
|
||||
@@ -10,58 +10,43 @@
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_archive_render_term_header')) :
|
||||
/**
|
||||
* Shared term archive header for category/tag pages.
|
||||
*
|
||||
* @param array<string,mixed> $args Header args.
|
||||
*/
|
||||
/** Reference term cover, including first-page count and term search. */
|
||||
function aksrt_archive_render_term_header(array $args = []): void {
|
||||
$args = wp_parse_args($args, [
|
||||
'header_style' => 'simple',
|
||||
'show_cover' => false,
|
||||
'cover_url' => '',
|
||||
'eyebrow' => '',
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
'admin_path' => '',
|
||||
'admin_label' => '',
|
||||
'header_style' => 'simple', 'show_cover' => false, 'cover_url' => '',
|
||||
'title' => '', 'description' => '', 'post_count' => false,
|
||||
]);
|
||||
|
||||
$header_style = (string) $args['header_style'];
|
||||
if ($header_style === 'none') {
|
||||
if ($args['header_style'] === 'none') {
|
||||
return;
|
||||
}
|
||||
|
||||
$show_cover = !empty($args['show_cover']) && (string) $args['cover_url'] !== '';
|
||||
$cover_url = (string) $args['cover_url'];
|
||||
?>
|
||||
<header class="page-header term-page-header term-page-header--<?php echo esc_attr($header_style); ?><?php echo ($show_cover && $header_style === 'cover') ? ' has-cover-bg' : ''; ?>">
|
||||
<?php if ($show_cover && $header_style === 'cover') : ?>
|
||||
<div class="term-page-header__cover-bg" style="background-image:url('<?php echo esc_url($cover_url); ?>')">
|
||||
<div class="term-page-header__cover-overlay"></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="term-page-header__body<?php echo $header_style === 'cover' ? ' term-page-header__body--overlay' : ''; ?>">
|
||||
<div>
|
||||
<?php if ((string) $args['eyebrow'] !== '') : ?>
|
||||
<p class="term-page-header__eyebrow"><?php echo esc_html((string) $args['eyebrow']); ?></p>
|
||||
<?php endif; ?>
|
||||
<h1 class="entry-title"><?php echo esc_html((string) $args['title']); ?></h1>
|
||||
<?php if ((string) $args['description'] !== '') : ?>
|
||||
<p class="term-page-header__desc"><?php echo $args['description']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
if ((string) $args['admin_path'] !== '' && function_exists('aksrt_render_admin_quick_link')) {
|
||||
aksrt_render_admin_quick_link((string) $args['admin_path'], (string) $args['admin_label']);
|
||||
$term = get_queried_object();
|
||||
$is_tag = $term instanceof WP_Term && $term->taxonomy === 'post_tag';
|
||||
$paged = max(1, (int) get_query_var('paged', 1));
|
||||
$title = '<i class="fa fas ' . ($is_tag ? 'fa-tags' : 'fa-folder-open') . ' em12 mr10 ml6" aria-hidden="true"></i>' . esc_html((string) $args['title']);
|
||||
global $wp_query;
|
||||
if (!empty($args['post_count']) && $paged === 1) {
|
||||
$count = $wp_query instanceof WP_Query ? (int) $wp_query->found_posts : 0;
|
||||
$title .= '<span class="icon-spot">' . sprintf(esc_html__('共%s篇', 'aksrt'), (string) $count) . '</span>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php if ($show_cover && $header_style !== 'cover') : ?>
|
||||
<div class="term-page-header__cover" style="background-image:url('<?php echo esc_url($cover_url); ?>')"></div>
|
||||
<?php endif; ?>
|
||||
</header>
|
||||
<?php
|
||||
$description = esc_html(trim(wp_strip_all_tags((string) $args['description'])));
|
||||
$more = '';
|
||||
if ($term instanceof WP_Term) {
|
||||
$more = '<div class="page-cover-more-btns flex ac"><a href="javascript:;" class="main-search-btn" search-type="post" search-trem="' . (int) $term->term_id . '" trem-name="' . esc_attr($term->name) . '" search-placeholder="' . esc_attr(sprintf(__('在%s中搜索', 'aksrt'), $term->name)) . '" title="' . esc_attr(sprintf(__('在%s中搜索', 'aksrt'), $term->name)) . '"><i class="fa fas fa-search" aria-hidden="true"></i></a></div>';
|
||||
}
|
||||
if (empty($args['show_cover'])) {
|
||||
echo '<div class="aksrt-panel relative"><h4 class="title-h-left">' . $title . '</h4><div class="muted-2-color">' . $description . '</div>' . $more . '</div>';
|
||||
return;
|
||||
}
|
||||
if ($paged > 1) {
|
||||
$title .= ' <small class="icon-spot">' . sprintf(esc_html__('第%s页', 'aksrt'), $paged) . '</small>';
|
||||
}
|
||||
$cover = (string) $args['cover_url'];
|
||||
if ($cover === '') {
|
||||
$cover = get_template_directory_uri() . '/assets/img/user_t.jpg';
|
||||
}
|
||||
echo '<div' . ($paged === 1 ? ' win-ajax-replace="page-cover"' : '') . ' class="page-cover aksrt-panel">';
|
||||
echo '<img' . aksrt_get_lazy_attr('lazy_cover', $cover, 'fit-cover') . ' alt="' . esc_attr((string) $args['title']) . '">';
|
||||
echo '<div class="absolute linear-mask"></div><div class="list-inline box-body page-cover-con"><div class="title-h-left"><b>' . $title . '</b></div><div class="em09 page-desc">' . $description . '</div></div>' . $more . '</div>';
|
||||
}
|
||||
endif;
|
||||
|
||||
@@ -76,46 +61,43 @@ if (!function_exists('aksrt_archive_render_filter_chip')) :
|
||||
'label' => '',
|
||||
'url' => '',
|
||||
'active' => false,
|
||||
'attrs' => '',
|
||||
]);
|
||||
if ((string) $chip['label'] === '' || (string) $chip['url'] === '') {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<a class="archive-filter-bar__chip<?php echo !empty($chip['active']) ? ' is-active' : ''; ?>" href="<?php echo esc_url((string) $chip['url']); ?>"<?php echo $chip['attrs']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php echo esc_html((string) $chip['label']); ?></a>
|
||||
<a rel="nofollow" ajax-replace="true" class="ajax-next<?php echo !empty($chip['active']) ? ' focus-color' : ''; ?>" href="<?php echo esc_url((string) $chip['url']); ?>"><?php echo esc_html((string) $chip['label']); ?></a>
|
||||
<?php
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_archive_render_filter_group')) :
|
||||
/**
|
||||
* One labeled chip group.
|
||||
*
|
||||
* @param array<string,mixed> $group Group args.
|
||||
*/
|
||||
/** One reference option row; dropdown entries stay separate from inline entries. */
|
||||
function aksrt_archive_render_filter_group(array $group): void {
|
||||
$group = wp_parse_args($group, [
|
||||
'label' => '',
|
||||
'chips' => [],
|
||||
'class' => '',
|
||||
]);
|
||||
$chips = is_array($group['chips'] ?? null) ? $group['chips'] : [];
|
||||
if (empty($chips)) {
|
||||
$group = wp_parse_args($group, ['label' => '', 'chips' => [], 'dropdown' => []]);
|
||||
if (empty($group['chips']) && empty($group['dropdown'])) {
|
||||
return;
|
||||
}
|
||||
$class = trim('archive-filter-bar__main ' . (string) $group['class']);
|
||||
?>
|
||||
<div class="<?php echo esc_attr($class); ?>">
|
||||
<?php if ((string) $group['label'] !== '') : ?>
|
||||
<span class="archive-filter-bar__label"><?php echo esc_html((string) $group['label']); ?></span>
|
||||
<?php endif; ?>
|
||||
<div class="archive-filter-bar__chips">
|
||||
<?php foreach ($chips as $chip) : ?>
|
||||
<?php if (is_array($chip)) { aksrt_archive_render_filter_chip($chip); } ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
echo '<div class="flex ac"><div class="option-dropdown splitters-this-r dropdown flex0">';
|
||||
if (!empty($group['dropdown'])) {
|
||||
echo '<a href="#" data-toggle="dropdown" aria-haspopup="true"><span>' . esc_html((string) $group['label']) . '</span><i class="fa fas fa-fw fa-sort opacity5" aria-hidden="true"></i></a><ul class="dropdown-menu">';
|
||||
foreach ($group['dropdown'] as $chip) {
|
||||
echo '<li>';
|
||||
aksrt_archive_render_filter_chip($chip);
|
||||
echo '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
} else {
|
||||
echo esc_html((string) $group['label']);
|
||||
}
|
||||
echo '</div><ul class="list-inline scroll-x mini-scrollbar option-items">';
|
||||
foreach ((array) $group['chips'] as $chip) {
|
||||
aksrt_archive_render_filter_chip($chip);
|
||||
}
|
||||
echo '</ul></div>';
|
||||
foreach ((array) ($group['children'] ?? []) as $child_group) {
|
||||
aksrt_archive_render_filter_group($child_group);
|
||||
}
|
||||
}
|
||||
endif;
|
||||
|
||||
@@ -126,10 +108,9 @@ if (!function_exists('aksrt_archive_build_term_filter_group')) :
|
||||
* @param string $taxonomy category|post_tag.
|
||||
* @param array<string,mixed> $option lists/dropdown/dropdown_lists fieldset.
|
||||
* @param string $label Visible group label.
|
||||
* @param bool $ajax Whether to attach archive AJAX attributes.
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
function aksrt_archive_build_term_filter_group(string $taxonomy, array $option, string $label, bool $ajax): array {
|
||||
function aksrt_archive_build_term_filter_group(string $taxonomy, array $option, string $label): array {
|
||||
if (!in_array($taxonomy, ['category', 'post_tag'], true)) {
|
||||
return [];
|
||||
}
|
||||
@@ -153,113 +134,81 @@ if (!function_exists('aksrt_archive_build_term_filter_group')) :
|
||||
return [];
|
||||
}
|
||||
|
||||
$query_key = $taxonomy === 'category' ? 'aksrt_filter_cat' : 'aksrt_filter_tag';
|
||||
$data_key = $taxonomy === 'category' ? 'data-filter-cat' : 'data-filter-tag';
|
||||
$current = isset($_GET[$query_key]) ? absint(wp_unslash($_GET[$query_key])) : 0;
|
||||
$chips = [[
|
||||
'label' => __('全部', 'aksrt'),
|
||||
'url' => remove_query_arg([$query_key, 'paged']),
|
||||
'active' => $current === 0,
|
||||
'attrs' => $ajax ? ' ' . $data_key . '=""' : '',
|
||||
]];
|
||||
|
||||
$inline_ids = array_map('absint', (array) ($option['lists'] ?? []));
|
||||
$dropdown_ids = !empty($option['dropdown']) ? array_map('absint', (array) ($option['dropdown_lists'] ?? [])) : [];
|
||||
$current = get_queried_object();
|
||||
$active_ids = $current instanceof WP_Term
|
||||
? array_merge([(int) $current->term_id], get_ancestors($current->term_id, $current->taxonomy, 'taxonomy')) : [];
|
||||
$group = ['label' => $label, 'chips' => [], 'dropdown' => [], 'children' => []];
|
||||
$child_ids = [];
|
||||
foreach ($terms as $term) {
|
||||
$term_id = (int) $term->term_id;
|
||||
$chips[] = [
|
||||
'label' => $term->name,
|
||||
'url' => add_query_arg($query_key, $term_id, remove_query_arg('paged')),
|
||||
'active' => $current === $term_id,
|
||||
'attrs' => $ajax ? ' ' . $data_key . '="' . $term_id . '"' : '',
|
||||
];
|
||||
$link = get_term_link($term);
|
||||
if (is_wp_error($link)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return [
|
||||
'label' => $label,
|
||||
'chips' => $chips,
|
||||
];
|
||||
$chip = ['label' => aksrt_widget_str_cut((string) $term->name, 8), 'url' => $link, 'active' => in_array((int) $term->term_id, $active_ids, true)];
|
||||
if (in_array((int) $term->term_id, $inline_ids, true)) {
|
||||
$group['chips'][] = $chip;
|
||||
if ($chip['active']) {
|
||||
$children = get_terms(['taxonomy' => $taxonomy, 'parent' => (int) $term->term_id, 'fields' => 'ids']);
|
||||
if (!is_wp_error($children)) {
|
||||
$child_ids = array_merge($child_ids, $children);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (in_array((int) $term->term_id, $dropdown_ids, true)) {
|
||||
$group['dropdown'][] = $chip;
|
||||
}
|
||||
}
|
||||
if ($child_ids) {
|
||||
$child_group = aksrt_archive_build_term_filter_group($taxonomy, ['lists' => array_unique($child_ids)], $taxonomy === 'category' ? __('子分类', 'aksrt') : __('子标签', 'aksrt'));
|
||||
if ($child_group) {
|
||||
$group['children'][] = $child_group;
|
||||
}
|
||||
}
|
||||
return $group;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_archive_render_filter_bar')) :
|
||||
/**
|
||||
* Shared filter bar shell for category/tag archives.
|
||||
*
|
||||
* @param array<string,mixed> $args Filter bar args.
|
||||
*/
|
||||
/** Category/tag filter rows share the homepage option menu presentation. */
|
||||
function aksrt_archive_render_filter_bar(array $args = []): void {
|
||||
// legacy zib_ajax_option_menu:nofilter 请求(首页更多栏目 tab 加载)不输出筛选菜单。
|
||||
if (!empty($_GET['nofilter'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$args = wp_parse_args($args, [
|
||||
'ajax' => false,
|
||||
'ajax_archive' => '',
|
||||
'term_id' => 0,
|
||||
'aria_label' => '',
|
||||
'meta_text' => '',
|
||||
'groups' => [],
|
||||
'sort_enabled' => false,
|
||||
'sort_current' => 'date',
|
||||
'sort_links' => [],
|
||||
'sort_labels' => [],
|
||||
'sort_as_main' => false,
|
||||
'aria_label' => '', 'groups' => [], 'sort_enabled' => false,
|
||||
'sort_current' => '', 'sort_links' => [], 'sort_labels' => [], 'sort_option' => [],
|
||||
]);
|
||||
|
||||
$groups = is_array($args['groups'] ?? null) ? $args['groups'] : [];
|
||||
$sort_links = is_array($args['sort_links'] ?? null) ? $args['sort_links'] : [];
|
||||
$sort_labels = is_array($args['sort_labels'] ?? null) ? $args['sort_labels'] : [];
|
||||
$has_sort = !empty($args['sort_enabled']) && !empty($sort_links);
|
||||
if (empty($groups) && !$has_sort && (string) $args['meta_text'] === '') {
|
||||
$groups = (array) $args['groups'];
|
||||
if (!empty($args['sort_enabled'])) {
|
||||
$sort = ['label' => __('排序', 'aksrt'), 'chips' => [], 'dropdown' => []];
|
||||
$option = (array) $args['sort_option'];
|
||||
foreach ((array) $args['sort_links'] as $key => $url) {
|
||||
$chip = [
|
||||
'label' => (string) ($args['sort_labels'][$key] ?? $key), 'url' => $url,
|
||||
'active' => (string) $args['sort_current'] === (string) $key,
|
||||
];
|
||||
if (in_array($key, (array) ($option['lists'] ?? []), true)) {
|
||||
$sort['chips'][] = $chip;
|
||||
}
|
||||
if (!empty($option['dropdown'])) {
|
||||
$sort['dropdown'][] = $chip;
|
||||
}
|
||||
}
|
||||
if ($sort['chips'] || $sort['dropdown']) {
|
||||
$groups[] = $sort;
|
||||
}
|
||||
}
|
||||
if (!$groups) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ajax = !empty($args['ajax']);
|
||||
?>
|
||||
<div
|
||||
class="archive-filter-bar<?php echo $ajax ? ' archive-filter-bar--ajax' : ''; ?>"
|
||||
aria-label="<?php echo esc_attr((string) ($args['aria_label'] !== '' ? $args['aria_label'] : __('筛选', 'aksrt'))); ?>"
|
||||
<?php if ($ajax) : ?>
|
||||
data-ajax-archive="<?php echo esc_attr((string) $args['ajax_archive']); ?>"
|
||||
data-term-id="<?php echo (int) $args['term_id']; ?>"
|
||||
data-query-context="archive"
|
||||
<?php endif; ?>
|
||||
>
|
||||
<?php foreach ($groups as $group) : ?>
|
||||
<?php if (is_array($group)) { aksrt_archive_render_filter_group($group); } ?>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if ((string) $args['meta_text'] !== '') : ?>
|
||||
<div class="archive-filter-bar__meta"><?php echo esc_html((string) $args['meta_text']); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($has_sort) : ?>
|
||||
<?php
|
||||
$sort_chips = [];
|
||||
foreach ($sort_links as $sort_key => $sort_url) {
|
||||
$sort_chips[] = [
|
||||
'label' => (string) ($sort_labels[$sort_key] ?? $sort_key),
|
||||
'url' => (string) $sort_url,
|
||||
'active' => (string) $args['sort_current'] === (string) $sort_key,
|
||||
'attrs' => $ajax ? ' data-filter-sort="' . esc_attr((string) $sort_key) . '"' : '',
|
||||
];
|
||||
echo '<div class="ajax-option ajax-replace" win-ajax-replace="filter" aria-label="' . esc_attr((string) $args['aria_label']) . '"';
|
||||
echo '>';
|
||||
foreach ($groups as $group) {
|
||||
aksrt_archive_render_filter_group($group);
|
||||
}
|
||||
if (!empty($args['sort_as_main'])) {
|
||||
aksrt_archive_render_filter_group([
|
||||
'label' => __('排序', 'aksrt'),
|
||||
'chips' => $sort_chips,
|
||||
]);
|
||||
} else {
|
||||
?>
|
||||
<div class="archive-filter-bar__sort" aria-label="<?php esc_attr_e('排序方式', 'aksrt'); ?>">
|
||||
<?php foreach ($sort_chips as $chip) { aksrt_archive_render_filter_chip($chip); } ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
echo '</div><div></div>';
|
||||
}
|
||||
endif;
|
||||
|
||||
@@ -272,10 +221,9 @@ if (!function_exists('aksrt_archive_build_custom_filter_groups')) :
|
||||
*
|
||||
* @param array<int,array<string,mixed>> $dimensions Dimension definitions (custom_filter settings).
|
||||
* @param mixed $keys 'all' or list of enabled dimension keys.
|
||||
* @param bool $ajax Attach AJAX attributes.
|
||||
* @return array<int,array<string,mixed>>
|
||||
*/
|
||||
function aksrt_archive_build_custom_filter_groups(array $dimensions, $keys, bool $ajax): array {
|
||||
function aksrt_archive_build_custom_filter_groups(array $dimensions, $keys): array {
|
||||
$groups = [];
|
||||
foreach ($dimensions as $dimension) {
|
||||
$dim_key = sanitize_key((string) ($dimension['key'] ?? ''));
|
||||
@@ -301,10 +249,9 @@ if (!function_exists('aksrt_archive_build_custom_filter_groups')) :
|
||||
$chips[] = [
|
||||
'label' => $value_name,
|
||||
'url' => $is_active
|
||||
? remove_query_arg([$dim_key, 'paged'])
|
||||
: add_query_arg([$dim_key => $value_key], remove_query_arg('paged')),
|
||||
? remove_query_arg($dim_key, aksrt_homepage_url_del_paged(aksrt_homepage_get_current_url()))
|
||||
: add_query_arg([$dim_key => $value_key], aksrt_homepage_url_del_paged(aksrt_homepage_get_current_url())),
|
||||
'active' => $is_active,
|
||||
'attrs' => $ajax ? ' data-filter-dim="' . esc_attr($dim_key) . '" data-filter-dim-value="' . esc_attr($value_key) . '"' : '',
|
||||
];
|
||||
if ($is_active) {
|
||||
$has_focus = true;
|
||||
@@ -316,9 +263,8 @@ if (!function_exists('aksrt_archive_build_custom_filter_groups')) :
|
||||
if ($has_focus) {
|
||||
array_unshift($chips, [
|
||||
'label' => __('全部', 'aksrt'),
|
||||
'url' => remove_query_arg([$dim_key, 'paged']),
|
||||
'url' => remove_query_arg($dim_key, aksrt_homepage_url_del_paged(aksrt_homepage_get_current_url())),
|
||||
'active' => false,
|
||||
'attrs' => $ajax ? ' data-filter-dim="' . esc_attr($dim_key) . '" data-filter-dim-value=""' : '',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -100,12 +100,12 @@ if (!function_exists('aksrt_archive_get_sort_state')) :
|
||||
$lists = array_values(array_unique(array_merge($lists, $all)));
|
||||
}
|
||||
|
||||
$current = isset($_GET['orderby']) ? sanitize_key(wp_unslash((string) $_GET['orderby'])) : 'date';
|
||||
if (!in_array($current, $all, true)) {
|
||||
$current = isset($_GET['orderby']) ? sanitize_key(wp_unslash((string) $_GET['orderby'])) : '';
|
||||
if ($current !== '' && !in_array($current, $all, true)) {
|
||||
$current = 'date';
|
||||
}
|
||||
|
||||
$base_url = remove_query_arg(['orderby', 'order', 'paged']);
|
||||
$base_url = remove_query_arg(['orderby', 'order'], aksrt_homepage_url_del_paged(aksrt_homepage_get_current_url()));
|
||||
$links = [];
|
||||
foreach ($lists as $orderby) {
|
||||
$links[$orderby] = add_query_arg('orderby', $orderby, $base_url);
|
||||
@@ -176,12 +176,12 @@ if (!function_exists('aksrt_archive_render_stream')) :
|
||||
'query_context' => 'archive',
|
||||
'empty_text' => __('暂无文章', 'aksrt'),
|
||||
'stream_class' => '',
|
||||
'ajax_enabled' => false,
|
||||
'ajax_attrs' => '',
|
||||
'filter_html' => '',
|
||||
]);
|
||||
|
||||
$classes = [
|
||||
'content-stream',
|
||||
'content-stream', 'posts-row', 'ajaxpager',
|
||||
'content-stream--' . sanitize_html_class((string) $args['page_kind']),
|
||||
];
|
||||
if (!empty($args['stream_class'])) {
|
||||
@@ -192,9 +192,6 @@ if (!function_exists('aksrt_archive_render_stream')) :
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($args['ajax_enabled'])) {
|
||||
$classes[] = 'is-ajax-enabled';
|
||||
}
|
||||
|
||||
$mode = (string) ($render_config['mode'] ?? 'list');
|
||||
$has_posts = $query instanceof WP_Query ? $query->have_posts() : have_posts();
|
||||
@@ -204,12 +201,9 @@ if (!function_exists('aksrt_archive_render_stream')) :
|
||||
data-page-kind="<?php echo esc_attr((string) $args['page_kind']); ?>"
|
||||
data-render-mode="<?php echo esc_attr($mode); ?>"
|
||||
data-list-type="<?php echo esc_attr((string) ($render_config['list_type'] ?? 'thumb')); ?>"
|
||||
<?php if (!empty($args['ajax_enabled'])) : ?>
|
||||
data-ajax-wrap
|
||||
data-query-context="<?php echo esc_attr((string) $args['query_context']); ?>"
|
||||
<?php endif; ?>
|
||||
<?php echo $args['ajax_attrs']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
>
|
||||
<?php echo $args['filter_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php if ($has_posts) : ?>
|
||||
<?php
|
||||
// legacy 契约:分页渲染在 .ajaxpager 内部(zib_paging 位置),
|
||||
@@ -222,7 +216,7 @@ if (!function_exists('aksrt_archive_render_stream')) :
|
||||
$pagination_html = (string) ob_get_clean();
|
||||
|
||||
// 归档面与首页共用同一套骨架契约:.post_ajax_loader + .is-loading。
|
||||
// 挂在分页之后(列表末尾),archive.js 靠它在 wrap 内定位加载态。
|
||||
// 挂在分页之后(列表末尾),通用列表运行时在 pager 内定位加载态。
|
||||
$loader_html = '<div class="post_ajax_loader" role="status" aria-label="'
|
||||
. esc_attr__('正在加载', 'aksrt') . '">'
|
||||
. aksrt_render_placeholder(
|
||||
@@ -232,14 +226,12 @@ if (!function_exists('aksrt_archive_render_stream')) :
|
||||
. '</div>';
|
||||
|
||||
aksrt_render_post_list(array_merge($render_config, [
|
||||
'wrapper_class' => 'content-stream__list',
|
||||
'skip_wrapper' => true,
|
||||
'append_inner_html' => $pagination_html . $loader_html,
|
||||
]), $query);
|
||||
?>
|
||||
<?php else : ?>
|
||||
<div class="no-results content-stream__empty">
|
||||
<p><?php echo esc_html((string) $args['empty_text']); ?></p>
|
||||
</div>
|
||||
<div class="ajax-item"><?php echo aksrt_get_null((string) $args['empty_text'], 100, 'null-post.svg'); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
@@ -20,21 +20,15 @@ if (!function_exists('aksrt_comments_template_query_args')) :
|
||||
$order = isset($_GET['aksrt_comment_order'])
|
||||
? sanitize_key((string) wp_unslash($_GET['aksrt_comment_order']))
|
||||
: '';
|
||||
if ($order === '') {
|
||||
$settings = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_comment_display_settings()
|
||||
: [];
|
||||
$order = (($settings['order'] ?? 'desc') === 'asc') ? 'oldest' : 'newest';
|
||||
}
|
||||
if (!in_array($order, ['newest', 'oldest', 'hottest'], true)) {
|
||||
if (!in_array($order, ['newest', 'hottest'], true)) {
|
||||
$order = 'newest';
|
||||
}
|
||||
|
||||
if ($order === 'oldest') {
|
||||
$args['order'] = 'ASC';
|
||||
if ($order === 'hottest') {
|
||||
$args['order'] = 'DESC';
|
||||
$args['orderby'] = 'comment_date_gmt';
|
||||
$args['aksrt_hottest_order'] = true;
|
||||
} else {
|
||||
// newest + hottest: base DESC; hottest reorders via comments_clauses.
|
||||
$args['order'] = 'DESC';
|
||||
$args['orderby'] = 'comment_date_gmt';
|
||||
}
|
||||
@@ -63,6 +57,17 @@ add_filter('comments_template_query_args', static function ($args) {
|
||||
: $args;
|
||||
});
|
||||
|
||||
add_filter('comments_clauses', static function (array $clauses, WP_Comment_Query $query): array {
|
||||
if (empty($query->query_vars['aksrt_hottest_order'])) {
|
||||
return $clauses;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$clauses['join'] .= " LEFT JOIN {$wpdb->commentmeta} AS aksrt_cm_hot ON ({$wpdb->comments}.comment_ID = aksrt_cm_hot.comment_id AND aksrt_cm_hot.meta_key = 'aksrt_comment_likes')";
|
||||
$clauses['orderby'] = 'CAST(COALESCE(aksrt_cm_hot.meta_value, 0) AS UNSIGNED) DESC, ' . $wpdb->comments . '.comment_date_gmt DESC';
|
||||
return $clauses;
|
||||
}, 20, 2);
|
||||
|
||||
if (!function_exists('aksrt_comment_box_visible')) :
|
||||
/**
|
||||
* Whether the comment module may render for the current visitor.
|
||||
@@ -103,14 +108,12 @@ if (!function_exists('aksrt_comments_build_context')) :
|
||||
'nested_depth' => 3,
|
||||
'require_login' => false,
|
||||
'avatar_size' => 48,
|
||||
'empty_text' => __('暂无评论,来做第一个评论的人吧~', 'aksrt'),
|
||||
];
|
||||
|
||||
$default_order = (($settings['order'] ?? 'desc') === 'asc') ? 'oldest' : 'newest';
|
||||
$order = isset($_GET['aksrt_comment_order'])
|
||||
? sanitize_key((string) wp_unslash($_GET['aksrt_comment_order']))
|
||||
: $default_order;
|
||||
if (!in_array($order, ['newest', 'oldest', 'hottest'], true)) {
|
||||
: 'newest';
|
||||
if (!in_array($order, ['newest', 'hottest'], true)) {
|
||||
$order = 'newest';
|
||||
}
|
||||
|
||||
@@ -126,13 +129,10 @@ if (!function_exists('aksrt_comments_build_context')) :
|
||||
'current_user' => wp_get_current_user(),
|
||||
'require_login' => !empty($settings['require_login']),
|
||||
'order' => $order,
|
||||
'reverse_comments' => $order === 'newest',
|
||||
'is_hottest' => $order === 'hottest',
|
||||
'author_only' => isset($_GET['aksrt_author_only']) && (int) $_GET['aksrt_author_only'] === 1,
|
||||
'post_author_id' => (int) get_the_author_meta('ID'),
|
||||
'avatar_size' => $avatar_size,
|
||||
'max_depth' => max(1, (int) ($settings['nested_depth'] ?? 3)),
|
||||
'empty_text' => (string) ($settings['empty_text'] ?? __('暂无评论,来做第一个评论的人吧~', 'aksrt')),
|
||||
'toolbar' => $toolbar,
|
||||
'upload_enabled' => !empty($toolbar['upload_img']),
|
||||
'link_enabled' => !empty($toolbar['link']),
|
||||
@@ -140,7 +140,7 @@ if (!function_exists('aksrt_comments_build_context')) :
|
||||
'box_placeholder' => (string) ($settings['box_placeholder'] ?? __('说点什么吧...', 'aksrt')),
|
||||
'submit_text' => (string) ($settings['submit_text'] ?? __('提交评论', 'aksrt')),
|
||||
'title_text' => (string) ($settings['title'] ?? __('评论', 'aksrt')),
|
||||
'corderby_enabled' => !empty($settings['corderby']),
|
||||
'corderby_enabled' => !empty($settings['corderby']) && !empty($settings['like_enabled']),
|
||||
'close_comments' => !empty($settings['close_comments']),
|
||||
'box_hidden' => !aksrt_comment_box_visible(),
|
||||
'oauth_providers' => class_exists('\AKSRT\Auth\OAuth')
|
||||
@@ -150,26 +150,6 @@ if (!function_exists('aksrt_comments_build_context')) :
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_comments_apply_hottest_order')) :
|
||||
/**
|
||||
* Apply hottest-order SQL once for the current comments query.
|
||||
*/
|
||||
function aksrt_comments_apply_hottest_order(): void {
|
||||
static $applied = false;
|
||||
if ($applied) {
|
||||
return;
|
||||
}
|
||||
$applied = true;
|
||||
|
||||
add_filter('comments_clauses', static function ($clauses) {
|
||||
global $wpdb;
|
||||
$clauses['join'] .= " LEFT JOIN {$wpdb->commentmeta} AS aksrt_cm ON ({$wpdb->comments}.comment_ID = aksrt_cm.comment_id AND aksrt_cm.meta_key = 'aksrt_comment_likes')";
|
||||
$clauses['orderby'] = 'CAST(aksrt_cm.meta_value AS UNSIGNED) DESC, ' . $clauses['orderby'];
|
||||
return $clauses;
|
||||
});
|
||||
}
|
||||
endif;
|
||||
|
||||
|
||||
if (!function_exists('aksrt_comments_normalize_order')) :
|
||||
/**
|
||||
@@ -177,13 +157,7 @@ if (!function_exists('aksrt_comments_normalize_order')) :
|
||||
*/
|
||||
function aksrt_comments_normalize_order(string $order = ''): string {
|
||||
$order = sanitize_key($order);
|
||||
if (!in_array($order, ['newest', 'oldest', 'hottest'], true)) {
|
||||
$settings = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_comment_display_settings()
|
||||
: [];
|
||||
$order = (($settings['order'] ?? 'desc') === 'asc') ? 'oldest' : 'newest';
|
||||
}
|
||||
if (!in_array($order, ['newest', 'oldest', 'hottest'], true)) {
|
||||
if (!in_array($order, ['newest', 'hottest'], true)) {
|
||||
$order = 'newest';
|
||||
}
|
||||
return $order;
|
||||
@@ -212,7 +186,7 @@ if (!function_exists('aksrt_comments_query_for_post')) :
|
||||
'number' => $per_page,
|
||||
'offset' => ($page - 1) * $per_page,
|
||||
'orderby' => 'comment_date_gmt',
|
||||
'order' => $order === 'oldest' ? 'ASC' : 'DESC',
|
||||
'order' => 'DESC',
|
||||
'hierarchical' => 'threaded',
|
||||
];
|
||||
|
||||
@@ -228,46 +202,13 @@ if (!function_exists('aksrt_comments_query_for_post')) :
|
||||
// WP_Comment_Query does not support mixed status easily; keep approve only for list fragment.
|
||||
}
|
||||
|
||||
// Hottest uses meta_value_num when meta exists; fall back to date.
|
||||
$hottest_filter = null;
|
||||
if ($order === 'hottest') {
|
||||
$query_args['orderby'] = 'meta_value_num';
|
||||
$query_args['meta_key'] = 'aksrt_comment_likes';
|
||||
$query_args['order'] = 'DESC';
|
||||
// Ensure comments without like meta still appear.
|
||||
$query_args['meta_query'] = [
|
||||
'relation' => 'OR',
|
||||
[
|
||||
'key' => 'aksrt_comment_likes',
|
||||
'compare' => 'EXISTS',
|
||||
],
|
||||
[
|
||||
'key' => 'aksrt_comment_likes',
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
];
|
||||
// Prefer clauses filter for stable hottest ordering.
|
||||
$hottest_filter = static function ($clauses) {
|
||||
global $wpdb;
|
||||
if (strpos($clauses['join'], 'aksrt_cm_hot') === false) {
|
||||
$clauses['join'] .= " LEFT JOIN {$wpdb->commentmeta} AS aksrt_cm_hot ON ({$wpdb->comments}.comment_ID = aksrt_cm_hot.comment_id AND aksrt_cm_hot.meta_key = 'aksrt_comment_likes')";
|
||||
}
|
||||
$clauses['orderby'] = 'CAST(COALESCE(aksrt_cm_hot.meta_value, 0) AS UNSIGNED) DESC, ' . $wpdb->comments . '.comment_date_gmt DESC';
|
||||
return $clauses;
|
||||
};
|
||||
add_filter('comments_clauses', $hottest_filter, 20);
|
||||
// Prefer date orderby with clauses override.
|
||||
$query_args['orderby'] = 'comment_date_gmt';
|
||||
unset($query_args['meta_key'], $query_args['meta_query']);
|
||||
$query_args['aksrt_hottest_order'] = true;
|
||||
}
|
||||
|
||||
$comment_query = new WP_Comment_Query($query_args);
|
||||
$comments = is_array($comment_query->comments) ? $comment_query->comments : [];
|
||||
|
||||
if ($hottest_filter) {
|
||||
remove_filter('comments_clauses', $hottest_filter, 20);
|
||||
}
|
||||
|
||||
// Pin reorder for page 1 when not author-only.
|
||||
if ($page === 1 && !$author_only && function_exists('aksrt_reorder_pinned_comments')) {
|
||||
$comments = aksrt_reorder_pinned_comments($comments, $post_id);
|
||||
@@ -325,8 +266,6 @@ if (!function_exists('aksrt_comments_render_items_html')) :
|
||||
'style' => 'ol',
|
||||
'avatar_size' => $avatar,
|
||||
'max_depth' => $depth,
|
||||
'reverse_top_level' => $reverse,
|
||||
'reverse_children' => $reverse,
|
||||
], $comments);
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
@@ -370,9 +309,8 @@ if (!function_exists('aksrt_comments_render_list_fragment')) :
|
||||
'order' => $order,
|
||||
'author_only' => $author_only,
|
||||
'post_author_id' => (int) get_post_field('post_author', $post_id),
|
||||
'corderby_enabled' => !empty($settings['corderby']),
|
||||
'corderby_enabled' => !empty($settings['corderby']) && !empty($settings['like_enabled']),
|
||||
'comment_count' => $total,
|
||||
'empty_text' => (string) ($settings['empty_text'] ?? __('暂无评论,来做第一个评论的人吧~', 'aksrt')),
|
||||
];
|
||||
|
||||
ob_start();
|
||||
@@ -623,7 +561,7 @@ if (!function_exists('aksrt_comments_render_filter')) :
|
||||
</li>
|
||||
</ul>
|
||||
<?php if ($post_author_id > 0) : ?>
|
||||
<a href="javascript:;" class="but comment-orderby btn-only-author p2-10<?php echo $author_only ? ' active' : ''; ?>" data-author-only="<?php echo $author_only ? '0' : '1'; ?>" rel="nofollow">
|
||||
<a href="javascript:;" class="but comment-orderby btn-only-author p2-10" data-author-only="<?php echo $author_only ? '0' : '1'; ?>" rel="nofollow">
|
||||
<?php echo $author_only ? esc_html__('查看全部', 'aksrt') : esc_html__('只看作者', 'aksrt'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
@@ -652,12 +590,9 @@ if (!function_exists('aksrt_comments_render_empty')) :
|
||||
* @param array<string,mixed> $ctx Comments context.
|
||||
*/
|
||||
function aksrt_comments_render_empty(array $ctx): void {
|
||||
$text = (string) ($ctx['empty_text'] ?? __('暂无评论内容', 'aksrt'));
|
||||
if (!empty($ctx['author_only'])) {
|
||||
$text = __('暂无作者评论', 'aksrt');
|
||||
}
|
||||
$text = __('暂无评论内容', 'aksrt');
|
||||
if (function_exists('aksrt_get_null')) {
|
||||
echo aksrt_get_null($text, 40, 'null.svg', 'comment comment-null', 280); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo aksrt_get_null($text, 40, 'comment-null.svg', 'comment comment-null', 280); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
return;
|
||||
}
|
||||
echo '<div class="text-center comment comment-null" style="padding:40px 0;"><p class="em09 muted-3-color separator">' . esc_html($text) . '</p></div>';
|
||||
@@ -678,10 +613,6 @@ if (!function_exists('aksrt_comments_render')) :
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($ctx['is_hottest'])) {
|
||||
aksrt_comments_apply_hottest_order();
|
||||
}
|
||||
|
||||
set_query_var('aksrt_comment_order', (string) $ctx['order']);
|
||||
set_query_var('aksrt_comment_settings', $ctx['settings']);
|
||||
|
||||
@@ -707,7 +638,7 @@ if (!function_exists('aksrt_comments_render')) :
|
||||
<div id="postcomments">
|
||||
<ol class="commentlist list-unstyled">
|
||||
<?php
|
||||
$show_filter = !empty($ctx['corderby_enabled']) && ($count > 0 || !empty($ctx['author_only']) || have_comments());
|
||||
$show_filter = !empty($ctx['corderby_enabled']) && have_comments();
|
||||
if ($show_filter) {
|
||||
aksrt_comments_render_filter($ctx);
|
||||
}
|
||||
@@ -740,8 +671,6 @@ if (!function_exists('aksrt_comments_render_list_items')) :
|
||||
'avatar_size' => (int) $ctx['avatar_size'],
|
||||
'max_depth' => (int) $ctx['max_depth'],
|
||||
'callback' => 'aksrt_comment_callback',
|
||||
'reverse_top_level' => !empty($ctx['reverse_comments']),
|
||||
'reverse_children' => !empty($ctx['reverse_comments']),
|
||||
]);
|
||||
echo aksrt_get_comment_ajax_loader(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
if (function_exists('aksrt_render_comment_pagination')) {
|
||||
|
||||
@@ -135,6 +135,8 @@ if (!function_exists('aksrt_graphic_card')) :
|
||||
$more_safe = $more !== '' ? wp_kses($more, array_merge($allow, [
|
||||
'div' => ['class' => true, 'style' => true],
|
||||
'badge' => ['class' => true],
|
||||
'svg' => ['class' => true, 'viewbox' => true, 'aria-hidden' => true],
|
||||
'use' => ['href' => true, 'xlink:href' => true],
|
||||
])) : '';
|
||||
|
||||
$text = '';
|
||||
|
||||
@@ -65,22 +65,18 @@ if (!function_exists('aksrt_homepage_get_tab_configs')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$excluded_cat_ids = array_flip(\AKSRT\Settings\Settings::home_exclude_category_ids());
|
||||
$configs = [];
|
||||
|
||||
foreach ($structured_tabs as $tab) {
|
||||
if ($tab['term_id'] <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (isset($excluded_cat_ids[$tab['term_id']])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$term = get_term($tab['term_id']);
|
||||
if (!$term || is_wp_error($term)) {
|
||||
continue;
|
||||
}
|
||||
if ($term->taxonomy !== 'category') {
|
||||
if ($term->taxonomy !== 'category' || (int) $term->count === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -106,7 +102,7 @@ if (!function_exists('aksrt_homepage_get_sort_mode')) {
|
||||
: ['modified', 'date', 'views', 'like', 'comment_count', 'favorite', 'rand'];
|
||||
$default_sort = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::homepage_default_sort()
|
||||
: 'modified';
|
||||
: 'date';
|
||||
$override_sort = get_query_var('aksrt_homepage_sort_override');
|
||||
$sort = is_string($override_sort) && $override_sort !== ''
|
||||
? sanitize_key($override_sort)
|
||||
|
||||
@@ -214,7 +214,6 @@ if (!function_exists('aksrt_widget_term_aggregation')) :
|
||||
}
|
||||
$href = esc_url((string) $href);
|
||||
|
||||
$icon = '<i class="fa fas fa-folder-open mr6" aria-hidden="true"></i>';
|
||||
$img = aksrt_widget_term_cover_url((int) $term->term_id, true);
|
||||
$img_html = aksrt_graphic_card([
|
||||
'class' => '',
|
||||
@@ -228,7 +227,12 @@ if (!function_exists('aksrt_widget_term_aggregation')) :
|
||||
$img_html = '<div class="term-img flex0 em09-sm">' . $img_html . '</div>';
|
||||
|
||||
$name = '<a class="em14 key-color text-ellipsis"' . $target . ' href="' . $href . '">' . esc_html($term->name) . '</a>';
|
||||
$description = '<div class="text-ellipsis-2 muted-color">' . esc_html($term->description) . '</div>';
|
||||
$description = wp_kses_post((string) $term->description);
|
||||
if ($description === '' && current_user_can('manage_categories')) {
|
||||
$description = esc_html__('请在WordPress后台-文章-文章分类中添加描述!', 'aksrt')
|
||||
. '<a href="' . esc_url(get_edit_term_link($term_id, 'category')) . '">' . esc_html__('立即编辑', 'aksrt') . '</a>';
|
||||
}
|
||||
$description = '<div class="text-ellipsis-2 muted-color">' . $description . '</div>';
|
||||
|
||||
$query_args = [
|
||||
'cat' => (string) $term->term_id,
|
||||
@@ -241,14 +245,18 @@ if (!function_exists('aksrt_widget_term_aggregation')) :
|
||||
];
|
||||
$query = \AKSRT\WidgetSystem\PostQuery::build($query_args, 1, []);
|
||||
|
||||
$meta = '<span class="px12 muted-3-color text-ellipsis"><i class="fa fas fa-file-alt fa-fw" aria-hidden="true"></i>' . sprintf(esc_html__('%s篇文章', 'aksrt'), esc_html(aksrt_format_number(max(0, (int) $query->found_posts)))) . '</span>';
|
||||
if (!$query->have_posts()) {
|
||||
wp_reset_postdata();
|
||||
return '';
|
||||
}
|
||||
$meta = '<span class="px12 muted-3-color text-ellipsis"><i class="fa far fa-file-alt fa-fw" aria-hidden="true"></i>' . sprintf(esc_html__('%s篇文章', 'aksrt'), esc_html((string) max(0, (int) $query->found_posts))) . '</span>';
|
||||
$meta .= '<a class="but p2-10 px12 c-blue hide-sm shrink0"' . $target . ' href="' . $href . '"><i class="fa fas fa-angle-right" aria-hidden="true"></i>' . esc_html__('更多文章', 'aksrt') . '</a>';
|
||||
$meta = '<div class="term-meta flex jsb ac">' . $meta . '</div>';
|
||||
|
||||
$posts_html = '';
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$data = aksrt_renderer_collect_post_data(['time_source' => 'date']);
|
||||
$data = aksrt_renderer_collect_post_data(['time_source' => $args['orderby'] === 'date' ? 'date' : 'modified']);
|
||||
if (!$data) {
|
||||
continue;
|
||||
}
|
||||
@@ -258,20 +266,20 @@ if (!function_exists('aksrt_widget_term_aggregation')) :
|
||||
$post_meta = '';
|
||||
switch ((string) $args['orderby']) {
|
||||
case 'views':
|
||||
$post_meta = aksrt_format_number((int) $data['views']);
|
||||
$post_meta = esc_html__('阅读[', 'aksrt') . aksrt_format_number((int) $data['views']) . ']';
|
||||
break;
|
||||
case 'favorite':
|
||||
$post_meta = aksrt_format_number((int) aksrt_get_post_favorites((int) $data['post_id']));
|
||||
$post_meta = esc_html__('收藏[', 'aksrt') . aksrt_format_number((int) aksrt_get_post_favorites((int) $data['post_id'])) . ']';
|
||||
break;
|
||||
case 'like':
|
||||
$post_meta = aksrt_format_number((int) aksrt_get_post_likes((int) $data['post_id']));
|
||||
$post_meta = esc_html__('点赞[', 'aksrt') . aksrt_format_number((int) aksrt_get_post_likes((int) $data['post_id'])) . ']';
|
||||
break;
|
||||
case 'comment_count':
|
||||
$post_meta = aksrt_format_number((int) $data['comments']);
|
||||
$post_meta = esc_html__('评论[', 'aksrt') . aksrt_format_number((int) $data['comments']) . ']';
|
||||
break;
|
||||
}
|
||||
if ($post_meta === '') {
|
||||
$post_meta = '<i class="fa fas fa-clock mr3" aria-hidden="true"></i>' . esc_html((string) $data['time_ago']);
|
||||
$post_meta = '<i class="fa far fa-clock mr3" aria-hidden="true"></i>' . esc_html((string) $data['time_ago']);
|
||||
}
|
||||
$posts_html .= '<div class="mt10 flex jsb ac">' . $title . '<div class="em09 muted-3-color flex0 ml10">' . $post_meta . '</div></div>';
|
||||
}
|
||||
|
||||
+47
-27
@@ -9,47 +9,67 @@
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
add_action('wp_ajax_aksrt_yiyan', 'aksrt_ajax_yiyan');
|
||||
add_action('wp_ajax_nopriv_aksrt_yiyan', 'aksrt_ajax_yiyan');
|
||||
|
||||
if (!function_exists('aksrt_ajax_yiyan')) :
|
||||
/** Return one random Chinese/English pair from the theme quote file. */
|
||||
function aksrt_ajax_yiyan(): void {
|
||||
$custom_file = WP_CONTENT_DIR . '/yiyan.txt';
|
||||
$theme_file = get_template_directory() . '/yiyan/qv-yiyan.txt';
|
||||
$file = is_readable($custom_file) ? $custom_file : $theme_file;
|
||||
$lines = is_readable($file) ? file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : false;
|
||||
$pairs = [];
|
||||
|
||||
if (is_array($lines)) {
|
||||
// The first line is the reference file's title; the rest alternate EN/CN.
|
||||
array_shift($lines);
|
||||
$lines = array_values(array_map('trim', $lines));
|
||||
for ($i = 0, $length = count($lines); $i + 1 < $length; $i += 2) {
|
||||
if ($lines[$i] !== '' && $lines[$i + 1] !== '') {
|
||||
$pairs[] = ['en' => $lines[$i], 'cn' => $lines[$i + 1]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$pairs) {
|
||||
wp_send_json_error(['message' => __('一言内容暂不可用', 'aksrt')], 503);
|
||||
}
|
||||
|
||||
wp_send_json_success($pairs[array_rand($pairs)]);
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_render_yiyan_block')) :
|
||||
/**
|
||||
* Shared 一言 shell for single-post placements (and reusable markup).
|
||||
*
|
||||
* Hitokoto category `c` must be a letter a–l, or empty for mixed random.
|
||||
* Client loads quotes via parallel fetches when count > 1.
|
||||
* Reads bilingual pairs from the bundled reference file and supports the
|
||||
* same optional wp-content/yiyan.txt override used by the reference theme.
|
||||
*
|
||||
* @param array<string,mixed> $args {
|
||||
* @type string $class Extra CSS classes on the root.
|
||||
* @type string $type Category letter a–l, or empty for random.
|
||||
* @type int $count Number of quotes to load (1–10).
|
||||
* @type int $interval Rotation interval in seconds.
|
||||
* @type string $icon Optional FA icon class; empty = no icon.
|
||||
* }
|
||||
*/
|
||||
function aksrt_render_yiyan_block(array $args = []): void {
|
||||
echo aksrt_get_yiyan_markup($args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped by the markup helper.
|
||||
}
|
||||
function aksrt_get_yiyan_markup(array $args = []): string {
|
||||
$class = trim((string) ($args['class'] ?? 'aksrt-yiyan'));
|
||||
$type_raw = strtolower(trim((string) ($args['type'] ?? '')));
|
||||
$type = preg_match('/^[a-l]$/', $type_raw) ? $type_raw : '';
|
||||
$count = max(1, min(10, (int) ($args['count'] ?? 5)));
|
||||
$interval = max(3, min(30, (int) ($args['interval'] ?? 8)));
|
||||
$icon = sanitize_text_field((string) ($args['icon'] ?? ''));
|
||||
|
||||
if (strpos($class, 'aksrt-yiyan') === false) {
|
||||
$class = trim($class . ' aksrt-yiyan');
|
||||
}
|
||||
?>
|
||||
<div class="<?php echo esc_attr($class); ?>"
|
||||
data-aksrt-yiyan
|
||||
data-count="<?php echo esc_attr((string) $count); ?>"
|
||||
data-interval="<?php echo esc_attr((string) $interval); ?>"
|
||||
<?php echo $type !== '' ? ' data-type="' . esc_attr($type) . '"' : ''; ?>>
|
||||
<div class="aksrt-yiyan__content">
|
||||
<?php if ($icon !== '') : ?>
|
||||
<i class="<?php echo esc_attr($icon); ?> aksrt-yiyan__icon" aria-hidden="true"></i>
|
||||
<?php endif; ?>
|
||||
<p class="aksrt-yiyan__text"><?php esc_html_e('加载中…', 'aksrt'); ?></p>
|
||||
<p class="aksrt-yiyan__from"></p>
|
||||
</div>
|
||||
<div class="aksrt-yiyan__dots" aria-hidden="true"></div>
|
||||
</div>
|
||||
<?php
|
||||
$html = '<div class="' . esc_attr($class) . '"'
|
||||
. ' data-aksrt-yiyan'
|
||||
. ' data-endpoint="' . esc_url(admin_url('admin-ajax.php')) . '"'
|
||||
. ' data-toggle="tooltip"'
|
||||
. ' title="' . esc_attr__('点击切换一言', 'aksrt') . '"'
|
||||
. ' role="button" tabindex="0"'
|
||||
. ' aria-label="' . esc_attr__('点击切换一言', 'aksrt') . '"'
|
||||
. ' aria-live="polite">'
|
||||
. '<div class="yiyan"><div class="en"></div><div class="cn">' . esc_html__('加载中…', 'aksrt') . '</div></div>'
|
||||
. '</div>';
|
||||
return $html;
|
||||
}
|
||||
endif;
|
||||
@@ -120,20 +120,11 @@ if (!function_exists('aksrt_homepage_get_tab_panes_html')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$is_card = class_exists('\AKSRT\Settings\PostDisplay')
|
||||
&& \AKSRT\Settings\PostDisplay::get_archive_layout() === 'card';
|
||||
$is_mobel = wp_is_mobile();
|
||||
if ($is_card) {
|
||||
$placeholder_type = 'card';
|
||||
$placeholder_num = $is_mobel ? 4 : 12;
|
||||
} else {
|
||||
$placeholder_type = 'list';
|
||||
$placeholder_num = $is_mobel ? 4 : 6;
|
||||
}
|
||||
$placeholder = aksrt_render_placeholder($placeholder_type, $placeholder_num);
|
||||
|
||||
$html = '';
|
||||
foreach ($tab_configs as $index => $tab_cfg) {
|
||||
$config = aksrt_archive_get_render_config(['context' => 'category', 'term_id' => (int) $tab_cfg['term_id']]);
|
||||
$is_card = ($config['mode'] ?? 'list') === 'card';
|
||||
$placeholder = aksrt_render_placeholder($is_card ? 'card' : 'list', $is_card ? (wp_is_mobile() ? 4 : 12) : (wp_is_mobile() ? 4 : 6));
|
||||
$html .= '<div class="ajaxpager tab-pane fade" id="index-tab-' . (int) $index . '">';
|
||||
$html .= '<span class="post_ajax_trigger"><a class="ajax_load ajax-next ajax-open" href="' . esc_url(aksrt_homepage_get_tab_link($tab_cfg)) . '"></a></span>';
|
||||
$html .= '<div class="post_ajax_loader">' . $placeholder . '</div>';
|
||||
|
||||
@@ -93,22 +93,6 @@ class GlobalController extends ControllerBase {
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Archive AJAX filter (public) — §2.5/§2.6/§2.7
|
||||
register_rest_route(self::API_NAMESPACE, '/archive/posts', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_archive_posts'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'archive_type' => ['type' => 'string', 'default' => 'category', 'sanitize_callback' => 'sanitize_key'],
|
||||
'term_id' => ['type' => 'integer', 'default' => 0, 'sanitize_callback' => 'absint'],
|
||||
'filter_cat' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'filter_tag' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'custom_filter' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'sort' => ['type' => 'string', 'default' => 'date', 'sanitize_callback' => 'sanitize_key'],
|
||||
'page' => ['type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Homepage categories (for admin tab editor)
|
||||
register_rest_route(self::API_NAMESPACE, '/homepage/categories', [
|
||||
'methods' => 'GET',
|
||||
@@ -164,104 +148,6 @@ class GlobalController extends ControllerBase {
|
||||
return $this->success(['message' => '测试邮件已发送']);
|
||||
}
|
||||
|
||||
public function rest_archive_posts(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$archive_type = sanitize_key((string) ($request->get_param('archive_type') ?: 'category'));
|
||||
$archive_type = in_array($archive_type, ['category', 'tag'], true) ? $archive_type : 'category';
|
||||
$term_id = absint($request->get_param('term_id'));
|
||||
$page = max(1, absint($request->get_param('page')));
|
||||
$page_settings = $archive_type === 'tag'
|
||||
? \AKSRT\Settings\Settings::get_tag_page_settings()
|
||||
: \AKSRT\Settings\Settings::get_category_page_settings();
|
||||
$per_page = min(50, max(1, (int) ($page_settings['per_page'] ?? get_option('posts_per_page', 10))));
|
||||
$sort = sanitize_key((string) ($request->get_param('sort') ?: 'date'));
|
||||
$allowed_sorts = \AKSRT\Settings\Settings::homepage_allowed_sorts();
|
||||
if (!in_array($sort, $allowed_sorts, true)) {
|
||||
$sort = 'date';
|
||||
}
|
||||
|
||||
$args = [
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $per_page,
|
||||
'paged' => $page,
|
||||
'ignore_sticky_posts' => true,
|
||||
];
|
||||
$tax_query = [];
|
||||
if ($term_id > 0) {
|
||||
$tax_query[] = [
|
||||
'taxonomy' => $archive_type === 'tag' ? 'post_tag' : 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$term_id],
|
||||
];
|
||||
}
|
||||
$filter_cat = absint($request->get_param('filter_cat'));
|
||||
$filter_tag = absint($request->get_param('filter_tag'));
|
||||
if ($filter_cat > 0) {
|
||||
$tax_query[] = ['taxonomy' => 'category', 'field' => 'term_id', 'terms' => [$filter_cat]];
|
||||
}
|
||||
if ($filter_tag > 0) {
|
||||
$tax_query[] = ['taxonomy' => 'post_tag', 'field' => 'term_id', 'terms' => [$filter_tag]];
|
||||
}
|
||||
if ($tax_query !== []) {
|
||||
$args['tax_query'] = $tax_query;
|
||||
}
|
||||
|
||||
// 自定义高级筛选维度(custom_filter=JSON {dim_key:value};参考主题 meta_query LIKE 语义)。
|
||||
$custom_raw = (string) $request->get_param('custom_filter');
|
||||
$custom = $custom_raw !== '' ? json_decode($custom_raw, true) : [];
|
||||
if (is_array($custom) && $custom !== []) {
|
||||
$filter_cfg = \AKSRT\Settings\Settings::get_post_filter_settings();
|
||||
$allowed = [];
|
||||
foreach ($filter_cfg['custom_filter'] as $dimension) {
|
||||
$dim_key = sanitize_key((string) ($dimension['key'] ?? ''));
|
||||
if ($dim_key === '') {
|
||||
continue;
|
||||
}
|
||||
$vals = [];
|
||||
foreach ((array) ($dimension['vals'] ?? []) as $val) {
|
||||
$vkey = sanitize_key((string) ($val['key'] ?? ''));
|
||||
if ($vkey !== '') {
|
||||
$vals[] = $vkey;
|
||||
}
|
||||
}
|
||||
$allowed[$dim_key] = $vals;
|
||||
}
|
||||
$meta_query = [];
|
||||
foreach ($custom as $dim_key => $dim_value) {
|
||||
$dim_key = sanitize_key((string) $dim_key);
|
||||
$dim_value = sanitize_key((string) $dim_value);
|
||||
if ($dim_key === '' || $dim_value === '' || !isset($allowed[$dim_key]) || !in_array($dim_value, $allowed[$dim_key], true)) {
|
||||
continue;
|
||||
}
|
||||
$meta_query[] = [
|
||||
'key' => '_aksrt_cf_' . $dim_key,
|
||||
'value' => '"' . $dim_value . '"',
|
||||
'compare' => 'LIKE',
|
||||
];
|
||||
}
|
||||
if ($meta_query !== []) {
|
||||
$args['meta_query'] = $meta_query;
|
||||
}
|
||||
}
|
||||
if (function_exists('aksrt_homepage_apply_named_sort_args')) {
|
||||
$args = aksrt_homepage_apply_named_sort_args($args, $sort);
|
||||
}
|
||||
|
||||
$query = new \WP_Query($args);
|
||||
$render_config = aksrt_resolve_post_list_render_config([
|
||||
'context' => $archive_type,
|
||||
'term_id' => $archive_type === 'category' ? $term_id : 0,
|
||||
]);
|
||||
$render_config['skip_wrapper'] = true;
|
||||
$html = aksrt_render_post_list($render_config, $query, false);
|
||||
|
||||
return $this->success([
|
||||
'html' => $html,
|
||||
'total' => (int) $query->found_posts,
|
||||
'has_more' => $page < (int) $query->max_num_pages,
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_homepage_categories(): \WP_REST_Response {
|
||||
$data = [];
|
||||
|
||||
|
||||
+5
-13
@@ -96,8 +96,12 @@ class TermMeta {
|
||||
|
||||
$cover_url = isset($saved['cover_url']) ? esc_url_raw((string) $saved['cover_url']) : '';
|
||||
if ($cover_url === '' && !empty($global['cover_enabled'])) {
|
||||
$cover_id = (int) get_term_meta($term->term_id, 'aksrt_category_cover_id', true);
|
||||
$cover_url = $cover_id > 0 ? (string) wp_get_attachment_url($cover_id) : '';
|
||||
if ($cover_url === '') {
|
||||
$cover_url = esc_url_raw((string) ($global['default_cover'] ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
$description = isset($saved['description']) ? sanitize_textarea_field((string) $saved['description']) : '';
|
||||
if ($description === '') {
|
||||
@@ -138,7 +142,7 @@ class TermMeta {
|
||||
'taxonomy' => $taxonomy,
|
||||
'header_style' => $header_style,
|
||||
'show_sidebar' => $sidebar_enabled,
|
||||
'show_cover' => $header_style === 'cover' && !empty($global['cover_enabled']) && $cover_url !== '',
|
||||
'show_cover' => $header_style === 'cover' && !empty($global['cover_enabled']),
|
||||
'show_desc' => $taxonomy === 'post_tag' ? $description !== '' : (!empty($global['desc_enabled']) && $description !== ''),
|
||||
'filter_bar' => !empty($global['filter_bar']) || $ajax_category_enabled || $ajax_tag_enabled || $orderby_enabled || !empty($custom_filter['custom_enabled']),
|
||||
'post_count' => !empty($global['post_count']),
|
||||
@@ -198,18 +202,6 @@ class TermMeta {
|
||||
$query->set('posts_per_page', (int) $settings['per_page']);
|
||||
}
|
||||
|
||||
$tax_query = is_array($query->get('tax_query')) ? $query->get('tax_query') : [];
|
||||
$filter_cat = isset($_GET['aksrt_filter_cat']) ? absint(wp_unslash($_GET['aksrt_filter_cat'])) : 0;
|
||||
$filter_tag = isset($_GET['aksrt_filter_tag']) ? absint(wp_unslash($_GET['aksrt_filter_tag'])) : 0;
|
||||
if ($filter_cat > 0) {
|
||||
$tax_query[] = ['taxonomy' => 'category', 'field' => 'term_id', 'terms' => [$filter_cat]];
|
||||
}
|
||||
if ($filter_tag > 0) {
|
||||
$tax_query[] = ['taxonomy' => 'post_tag', 'field' => 'term_id', 'terms' => [$filter_tag]];
|
||||
}
|
||||
if ($tax_query !== []) {
|
||||
$query->set('tax_query', $tax_query);
|
||||
}
|
||||
|
||||
// 自定义高级筛选维度 meta 过滤(参考主题 zib_sift_posts_per_page:meta_query LIKE;
|
||||
// meta 值为序列化数组,用带引号精确匹配避免前缀误命中)。
|
||||
|
||||
@@ -104,7 +104,6 @@ class HomeCms {
|
||||
|
||||
public static function get_homepage_fixed_settings(): array {
|
||||
$s = Settings::read_domain('page_display', 'homepage');
|
||||
$allowed_sorts = self::homepage_allowed_sorts();
|
||||
|
||||
// Canonical legacy-aligned primary list contract.
|
||||
$main_list_enabled = !array_key_exists('home_posts_list_s', $s) || !empty($s['home_posts_list_s']);
|
||||
@@ -113,19 +112,11 @@ class HomeCms {
|
||||
$orderby_option = isset($s['home_list1_orderby_option']) && is_array($s['home_list1_orderby_option'])
|
||||
? $s['home_list1_orderby_option']
|
||||
: [];
|
||||
$sort_options_raw = $orderby_option['lists'] ?? [];
|
||||
$sort_options_raw = $orderby_option['lists'] ?? ['modified', 'views', 'like', 'comment_count'];
|
||||
if (!is_array($sort_options_raw)) {
|
||||
$sort_options_raw = [];
|
||||
}
|
||||
$sort_options = self::normalize_sort_options($sort_options_raw);
|
||||
$default_sort = sanitize_key((string) ($sort_options[0] ?? 'modified'));
|
||||
if (!in_array($default_sort, $allowed_sorts, true)) {
|
||||
$default_sort = 'modified';
|
||||
}
|
||||
if (!in_array($default_sort, $sort_options, true)) {
|
||||
array_unshift($sort_options, $default_sort);
|
||||
$sort_options = array_values(array_unique($sort_options));
|
||||
}
|
||||
|
||||
$list_title = wp_kses_post((string) ($s['index_list_title'] ?? ''));
|
||||
|
||||
@@ -171,8 +162,7 @@ class HomeCms {
|
||||
}
|
||||
|
||||
public static function homepage_default_sort(): string {
|
||||
$settings = self::get_homepage_fixed_settings();
|
||||
return $settings['home_list1_orderby_option']['lists'][0] ?? 'modified';
|
||||
return 'date';
|
||||
}
|
||||
|
||||
public static function home_posts_list_enabled(): bool {
|
||||
@@ -223,10 +213,6 @@ class HomeCms {
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($result)) {
|
||||
$result = ['modified', 'views', 'like', 'comment_count'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,14 +31,22 @@ final class AdminFormController {
|
||||
aksrt_icon_picker_assets(['wp-color-picker']);
|
||||
wp_enqueue_script('aksrt-widget-admin-core', AKSRT_URI . '/assets/widgets/admin-core.js', [], AKSRT_VERSION, true);
|
||||
wp_enqueue_script('aksrt-widget-admin', AKSRT_URI . '/assets/widgets/admin.js', ['jquery', 'jquery-ui-sortable', 'wp-color-picker', 'aksrt-widget-admin-core'], AKSRT_VERSION, true);
|
||||
$sidebars = [];
|
||||
foreach ((array) ($GLOBALS['wp_registered_sidebars'] ?? []) as $id => $sidebar) {
|
||||
$sidebars[(string) $id] = (string) ($sidebar['name'] ?? $id);
|
||||
}
|
||||
wp_localize_script('aksrt-widget-admin', 'aksrtWidgetAdmin', [
|
||||
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||
'action' => self::AJAX_ACTION,
|
||||
'nonce' => wp_create_nonce(self::NONCE_ACTION),
|
||||
'transferNonce' => wp_create_nonce('aksrt_widgets_transfer'),
|
||||
'sidebars' => $sidebars,
|
||||
'i18n' => [
|
||||
'loading' => __('正在加载模块配置…', 'aksrt'),
|
||||
'error' => __('加载失败,请重试。', 'aksrt'),
|
||||
'export' => __('导出', 'aksrt'),
|
||||
'import' => __('导入', 'aksrt'),
|
||||
'template' => __('应用模板', 'aksrt'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
@@ -60,6 +68,10 @@ final class AdminFormController {
|
||||
$widget->_set($number);
|
||||
$settings = $widget->get_settings();
|
||||
$instance = isset($settings[$number]) && is_array($settings[$number]) ? $settings[$number] : [];
|
||||
$instance = apply_filters('widget_form_callback', $instance, $widget);
|
||||
if (!is_array($instance)) {
|
||||
$instance = [];
|
||||
}
|
||||
ob_start();
|
||||
$widget->render_full_form($instance);
|
||||
$html = (string) ob_get_clean();
|
||||
|
||||
@@ -34,7 +34,6 @@ final class AssetManager {
|
||||
if (!self::$configured) {
|
||||
$config = [
|
||||
'restUrl' => esc_url_raw(rest_url('aksrt/v1/widgets/')),
|
||||
'quoteUrl' => esc_url_raw(rest_url('aksrt/v1/widgets/quote')),
|
||||
'i18n' => [
|
||||
'loading' => __('正在加载…', 'aksrt'),
|
||||
'error' => __('加载失败,请重试。', 'aksrt'),
|
||||
@@ -57,7 +56,9 @@ final class AssetManager {
|
||||
$js = $root . '/assets/widgets/frontend.js';
|
||||
wp_register_style(self::STYLE_HANDLE, $uri . '/assets/widgets/frontend.css', ['aksrt-public'], is_file($css) ? (string) filemtime($css) : null);
|
||||
wp_register_style('aksrt-pay-box', $uri . '/assets/css/frontend/pay-box.css', ['aksrt-public'], is_file($pay_css) ? (string) filemtime($pay_css) : null);
|
||||
wp_register_script(self::SCRIPT_HANDLE, $uri . '/assets/widgets/frontend.js', ['loader_js'], is_file($js) ? (string) filemtime($js) : null, true);
|
||||
$yiyan_js = $root . '/assets/js/yiyan.js';
|
||||
wp_register_script('aksrt-yiyan', $uri . '/assets/js/yiyan.js', ['loader_js'], is_file($yiyan_js) ? (string) filemtime($yiyan_js) : null, true);
|
||||
wp_register_script(self::SCRIPT_HANDLE, $uri . '/assets/widgets/frontend.js', ['loader_js', 'aksrt-yiyan'], is_file($js) ? (string) filemtime($js) : null, true);
|
||||
$third_party = $uri . '/assets/widgets/third-party/';
|
||||
wp_register_script('aksrt-widget-hls', $third_party . 'hls.min.js', [], 'reference-pinned', true);
|
||||
wp_register_script('aksrt-widget-flv', $third_party . 'flv.min.js', [], 'reference-pinned', true);
|
||||
|
||||
@@ -121,14 +121,37 @@ final class FormRenderer {
|
||||
$image = is_array($label) ? (string) ($label['image'] ?? $label['url'] ?? '') : (string) $label;
|
||||
$text = is_array($label) ? (string) ($label['label'] ?? $label['title'] ?? $key) : (string) $label;
|
||||
if ($type === 'image_select' && $text === $image) $text = (string) $key;
|
||||
$body = $type === 'image_select' && filter_var($image, FILTER_VALIDATE_URL)
|
||||
? '<img src="' . esc_url($image) . '" alt="' . esc_attr($text) . '"><small>' . esc_html($text) . '</small>'
|
||||
: esc_html($text);
|
||||
if ($type === 'image_select' && filter_var($image, FILTER_VALIDATE_URL)) {
|
||||
$body = '<img src="' . esc_url($image) . '" alt="' . esc_attr($text) . '"><small>' . esc_html($text) . '</small>';
|
||||
} elseif ($type === 'palette') {
|
||||
$body = '<span class="aksrt-widget-palette-choice"><i aria-hidden="true" style="background:' . esc_attr(self::palette_color((string) $key)) . '"></i><small>' . esc_html($text) . '</small></span>';
|
||||
} else {
|
||||
$body = esc_html($text);
|
||||
}
|
||||
echo '<label for="' . esc_attr($option_id) . '"><input type="radio" id="' . esc_attr($option_id) . '" name="' . esc_attr($name) . '" value="' . esc_attr((string) $key) . '" ' . checked($value, (string) $key, false) . '><span>' . $body . '</span></label>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
private static function palette_color(string $key): string {
|
||||
$colors = [
|
||||
'' => '#fff', 'c-red' => 'rgba(255,84,115,.4)', 'c-red-2' => 'rgba(194,41,46,.4)',
|
||||
'c-yellow' => 'rgba(255,111,6,.4)', 'c-yellow-2' => 'rgba(179,103,8,.4)', 'c-cyan' => 'rgba(8,196,193,.4)',
|
||||
'c-blue' => 'rgba(41,151,247,.4)', 'c-blue-2' => 'rgba(77,130,249,.4)', 'c-green' => 'rgba(18,185,40,.4)',
|
||||
'c-green-2' => 'rgba(72,135,24,.4)', 'c-purple' => 'rgba(213,72,245,.4)', 'c-purple-2' => 'rgba(154,72,245,.4)',
|
||||
'b-red' => '#f74b3d', 'b-yellow' => '#f3920a', 'b-cyan' => '#08c4c1', 'b-blue' => '#0a8cf3',
|
||||
'b-green' => '#1fd05a', 'b-purple' => '#c133f5', 'b-black' => '#121517',
|
||||
'jb-red' => 'linear-gradient(135deg,#ffbeb4 10%,#f61a1a 100%)', 'jb-pink' => 'linear-gradient(135deg,#ff5e7f 30%,#ff967e 100%)',
|
||||
'jb-yellow' => 'linear-gradient(135deg,#ffd6b2 10%,#ff651c 100%)', 'jb-cyan' => 'linear-gradient(140deg,#039ab3 10%,#58dbcf 90%)',
|
||||
'jb-blue' => 'linear-gradient(135deg,#b6e6ff 10%,#198aff 100%)', 'jb-green' => 'linear-gradient(135deg,#ccffcd 10%,#52bb51 100%)',
|
||||
'jb-purple' => 'linear-gradient(135deg,#fec2ff 10%,#d000de 100%)', 'jb-vip1' => 'linear-gradient(25deg,#eab869 10%,#fbecd4 60%,#ffe0ae 100%)',
|
||||
'jb-vip2' => 'linear-gradient(317deg,#4d4c4c 30%,#878787 70%,#5f5c5c 100%)',
|
||||
'focus-color' => 'linear-gradient(135deg,#74d5f6,#9866ff)', 'key-color' => '#333', 'muted-color' => '#777',
|
||||
'muted-2-color' => '#999', 'muted-3-color' => '#b1b1b1',
|
||||
];
|
||||
return $colors[$key] ?? '#dcdcde';
|
||||
}
|
||||
|
||||
private static function select(string $id, string $name, array $field, $value): void {
|
||||
$multiple = !empty($field['multiple']);
|
||||
echo '<select class="widefat" id="' . esc_attr($id) . '" name="' . esc_attr($name) . ($multiple ? '[]' : '') . '"' . ($multiple ? ' multiple' : '') . '>';
|
||||
@@ -242,7 +265,7 @@ final class FormRenderer {
|
||||
private static function group(SchemaWidget $widget, array $field, array $rows, string $key): void {
|
||||
$token = '__index_' . substr(md5($key), 0, 8) . '__';
|
||||
$button_title = (string) ($field['button_title'] ?? __('添加项目', 'aksrt'));
|
||||
echo '<div class="aksrt-widget-repeater" data-index-token="' . esc_attr($token) . '" data-max-items="' . esc_attr((string) ($field['max_items'] ?? 50)) . '"><div class="aksrt-widget-repeater__rows">';
|
||||
echo '<div class="aksrt-widget-repeater" data-index-token="' . esc_attr($token) . '" data-item-label="' . esc_attr((string) ($field['item_label'] ?? $field['label'] ?? __('项目', 'aksrt'))) . '" data-min-items="' . esc_attr((string) ($field['min_items'] ?? 0)) . '" data-max-items="' . esc_attr((string) ($field['max_items'] ?? 50)) . '"><div class="aksrt-widget-repeater__rows">';
|
||||
foreach ($rows as $index => $row) {
|
||||
self::group_row($widget, $field, is_array($row) ? $row : [], $key, (string) $index);
|
||||
}
|
||||
@@ -256,12 +279,12 @@ final class FormRenderer {
|
||||
$number = ctype_digit($index) ? ((int) $index + 1) : 0;
|
||||
$title = $prefix . $number;
|
||||
if (!empty($row['title'])) $title .= ':' . strip_tags((string) $row['title']);
|
||||
echo '<div class="aksrt-widget-repeater__row" data-row-index="' . esc_attr(ctype_digit($index) ? $index : '0') . '"><div class="aksrt-widget-repeater__bar"><span class="dashicons dashicons-move" aria-hidden="true"></span><strong>' . esc_html($title) . '</strong><button type="button" class="button-link-delete aksrt-widget-repeater__remove">' . esc_html__('删除', 'aksrt') . '</button></div><div class="aksrt-widget-repeater__fields">';
|
||||
echo '<details class="aksrt-widget-repeater__row" data-row-index="' . esc_attr(ctype_digit($index) ? $index : '0') . '"><summary class="aksrt-widget-repeater__bar"><span class="dashicons dashicons-move" aria-hidden="true"></span><strong class="aksrt-widget-repeater__title" data-title-prefix="' . esc_attr($prefix) . '">' . esc_html($title) . '</strong><button type="button" class="button-link-delete aksrt-widget-repeater__remove">' . esc_html__('删除', 'aksrt') . '</button></summary><div class="aksrt-widget-repeater__fields">';
|
||||
foreach ((array) ($field['fields'] ?? []) as $child) {
|
||||
$child_id = (string) ($child['id'] ?? '');
|
||||
self::field($widget, $child, $row[$child_id] ?? ($child['default'] ?? ''), $key . '[' . $index . ']');
|
||||
}
|
||||
echo '</div></div>';
|
||||
echo '</div></details>';
|
||||
}
|
||||
|
||||
private static function nested(SchemaWidget $widget, array $field, array $value, string $key): void {
|
||||
|
||||
@@ -33,6 +33,8 @@ final class PostRenderer {
|
||||
$mini_opt = (array) ($instance['mini_opt'] ?? []);
|
||||
$config = [
|
||||
'type' => $mode === 'card' ? 'card' : 'list',
|
||||
'is_mult_thumb' => 'disable',
|
||||
'is_no_thumb' => 'disable',
|
||||
'show_number' => in_array('show_number', $mini_opt, true),
|
||||
'show_thumb' => in_array('show_thumb', $mini_opt, true),
|
||||
'show_meta' => in_array('show_meta', $mini_opt, true),
|
||||
|
||||
@@ -56,7 +56,7 @@ final class ContentRenderer {
|
||||
// The loader must live inside the pager: the REST client in frontend.js
|
||||
// resolves it with container.querySelector('.post_ajax_loader'), so a
|
||||
// sibling node would never be found and the skeleton would stay shut.
|
||||
return '<div class="widget-ajaxpager"' . $attrs . ' aria-live="polite" aria-busy="false">' . $payload['html'] . $payload['paginate_html'] . $loader . '</div>';
|
||||
return '<div class="widget-ajaxpager"' . $attrs . ' data-loaded="1" aria-live="polite" aria-busy="false">' . $payload['html'] . $payload['paginate_html'] . $loader . '</div>';
|
||||
}
|
||||
|
||||
private static function placeholder(array $instance): string {
|
||||
@@ -88,7 +88,7 @@ final class ContentRenderer {
|
||||
if (!is_array($tab) || trim((string) ($tab['title'] ?? '')) === '') continue;
|
||||
$tab_id = 'aksrt-tab-' . $key . '-' . $visible_index;
|
||||
$selected = $visible_index === 0;
|
||||
$buttons .= '<li class="' . ($selected ? 'active' : '') . '"><a' . ($selected ? '' : ' data-ajax') . ' data-toggle="tab" href="#' . esc_attr($panel_id . '-' . $visible_index) . '" role="tab" id="' . esc_attr($tab_id) . '" aria-controls="' . esc_attr($panel_id . '-' . $visible_index) . '" aria-selected="' . ($selected ? 'true' : 'false') . '" data-segment="' . (int) $index . '">' . wp_kses_post((string) $tab['title']) . '</a></li>';
|
||||
$buttons .= '<li class="' . ($selected ? 'active' : '') . '"><a' . ($selected ? '' : ' data-ajax') . ' data-toggle="tab" href="#' . esc_attr($panel_id . '-' . $visible_index) . '" role="tab" id="' . esc_attr($tab_id) . '" aria-controls="' . esc_attr($panel_id . '-' . $visible_index) . '" aria-selected="' . ($selected ? 'true' : 'false') . '" tabindex="' . ($selected ? '0' : '-1') . '" data-segment="' . (int) $index . '">' . wp_kses_post((string) $tab['title']) . '</a></li>';
|
||||
if ($selected) {
|
||||
$body = self::ajaxpager($instance, 'tab_posts', $widget_id, 1, (string) $index);
|
||||
} else {
|
||||
@@ -178,6 +178,16 @@ final class ContentRenderer {
|
||||
$animation = (string) ($instance['obs_animation'] ?? '');
|
||||
$html = '';
|
||||
foreach ($term_ids as $key => $term_id) {
|
||||
$card = aksrt_widget_term_aggregation([
|
||||
'term_id' => (int) $term_id,
|
||||
'target_blank' => !empty($instance['target_blank']),
|
||||
'orderby' => (string) ($instance['orderby'] ?? 'modified'),
|
||||
'order' => (($instance['order'] ?? 'desc') === 'asc') ? 'ASC' : 'DESC',
|
||||
'count' => (int) ($instance['count'] ?? 4),
|
||||
]);
|
||||
if ($card === '') {
|
||||
continue;
|
||||
}
|
||||
$attr = '';
|
||||
$col_class = '';
|
||||
if ($animation !== '') {
|
||||
@@ -192,13 +202,7 @@ final class ContentRenderer {
|
||||
$col_class .= ' ' . $row_class;
|
||||
}
|
||||
$html .= '<div class="' . esc_attr(trim($col_class)) . '"' . $attr . '>';
|
||||
$html .= aksrt_widget_term_aggregation([
|
||||
'term_id' => (int) $term_id,
|
||||
'target_blank' => !empty($instance['target_blank']),
|
||||
'orderby' => (string) ($instance['orderby'] ?? 'modified'),
|
||||
'order' => (($instance['order'] ?? 'desc') === 'asc') ? 'ASC' : 'DESC',
|
||||
'count' => (int) ($instance['count'] ?? 4),
|
||||
]);
|
||||
$html .= $card;
|
||||
$html .= '</div>';
|
||||
}
|
||||
return '<div class="clearfix mb6">' . ($is_row ? '<div class="row gutters-5">' : '') . $html . ($is_row ? '</div>' : '') . '</div>';
|
||||
|
||||
@@ -34,9 +34,9 @@ final class GeneralRenderer {
|
||||
}
|
||||
|
||||
private static function render_quote(array $i): string {
|
||||
$quotes = RendererHelpers::lines((string) $i['quotes']);
|
||||
$text = $quotes ? $quotes[array_rand($quotes)] : __('保持好奇,持续创造。', 'aksrt');
|
||||
return '<figure class="aksrt-widget-quote" data-source="' . esc_attr((string) $i['source']) . '" data-interval="' . (int) $i['interval'] . '"><i class="fa fas fa-quote-left" aria-hidden="true"></i><blockquote>' . esc_html($text) . '</blockquote><button type="button" aria-label="' . esc_attr__('换一句', 'aksrt') . '"><i class="fa fas fa-sync-alt" aria-hidden="true"></i></button></figure>';
|
||||
return function_exists('aksrt_get_yiyan_markup')
|
||||
? aksrt_get_yiyan_markup(['class' => 'aksrt-widget-quote'])
|
||||
: '';
|
||||
}
|
||||
|
||||
private static function render_icon_cards(array $i): string {
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace AKSRT\WidgetSystem;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/** Public read-only endpoints for saved widget instances and quotes. */
|
||||
/** Public read-only endpoints for saved widget instances. */
|
||||
final class RestController extends \WP_REST_Controller {
|
||||
public function __construct() {
|
||||
$this->namespace = 'aksrt/v1';
|
||||
@@ -23,12 +23,6 @@ final class RestController extends \WP_REST_Controller {
|
||||
'segment' => ['type' => 'string', 'default' => '', 'pattern' => '^[0-9]{0,2}$'],
|
||||
],
|
||||
]);
|
||||
register_rest_route($this->namespace, '/' . $this->rest_base . '/quote', [
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [$this, 'get_quote'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function validate_widget($value): bool {
|
||||
@@ -59,28 +53,6 @@ final class RestController extends \WP_REST_Controller {
|
||||
return rest_ensure_response(['success' => true, 'code' => 'ok'] + $payload);
|
||||
}
|
||||
|
||||
/** @return \WP_REST_Response|\WP_Error */
|
||||
public function get_quote(\WP_REST_Request $request) {
|
||||
if ($request->get_params()) {
|
||||
return new \WP_Error('aksrt_widget_unknown_parameter', __('一言接口不接受查询参数。', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
$cached = get_transient('aksrt_widget_quote');
|
||||
if (is_array($cached) && !empty($cached['text'])) {
|
||||
return rest_ensure_response($cached);
|
||||
}
|
||||
$fallbacks = [__('保持好奇,持续创造。', 'aksrt'), __('知识因为分享而更有价值。', 'aksrt'), __('把复杂的事,做得清楚而简单。', 'aksrt')];
|
||||
$data = ['text' => $fallbacks[array_rand($fallbacks)], 'author' => 'AKSRT', 'source' => 'local'];
|
||||
$response = wp_remote_get('https://v1.hitokoto.cn/?encode=json', ['timeout' => 3, 'redirection' => 2, 'user-agent' => 'AKSRT Widget/1.0']);
|
||||
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
|
||||
$remote = json_decode((string) wp_remote_retrieve_body($response), true);
|
||||
if (is_array($remote) && !empty($remote['hitokoto'])) {
|
||||
$data = ['text' => sanitize_text_field((string) $remote['hitokoto']), 'author' => sanitize_text_field((string) ($remote['from_who'] ?? $remote['from'] ?? '')), 'source' => 'hitokoto'];
|
||||
}
|
||||
}
|
||||
set_transient('aksrt_widget_quote', $data, 10 * MINUTE_IN_SECONDS);
|
||||
return rest_ensure_response($data);
|
||||
}
|
||||
|
||||
/** @return array<string,mixed>|\WP_Error */
|
||||
private function load_instance(string $slug, string $instance_id) {
|
||||
$definition = WidgetRegistry::get($slug);
|
||||
|
||||
@@ -7,9 +7,11 @@ defined('ABSPATH') || exit;
|
||||
/** Admin-only import, export, template application and preview. */
|
||||
final class TransferController {
|
||||
private const NONCE = 'aksrt_widgets_transfer';
|
||||
private static bool $controls_rendered = false;
|
||||
|
||||
public static function register_hooks(): void {
|
||||
add_action('admin_notices', [self::class, 'render_panel']);
|
||||
add_action('customize_controls_print_footer_scripts', [self::class, 'render_panel']);
|
||||
add_action('admin_post_aksrt_widgets_export', [self::class, 'handle_export']);
|
||||
add_action('admin_post_aksrt_widgets_import', [self::class, 'handle_import']);
|
||||
add_action('admin_post_aksrt_widgets_template', [self::class, 'handle_template']);
|
||||
@@ -17,47 +19,61 @@ final class TransferController {
|
||||
}
|
||||
|
||||
public static function render_panel(): void {
|
||||
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
|
||||
if (!$screen || $screen->id !== 'widgets' || !current_user_can('edit_theme_options')) {
|
||||
if (self::$controls_rendered) {
|
||||
return;
|
||||
}
|
||||
$sidebars = $GLOBALS['wp_registered_sidebars'] ?? [];
|
||||
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
|
||||
if (!$screen || !in_array($screen->id, ['widgets', 'customize'], true) || !current_user_can('edit_theme_options')) {
|
||||
return;
|
||||
}
|
||||
self::$controls_rendered = true;
|
||||
$templates = WidgetTransfer::templates();
|
||||
$notice = isset($_GET['aksrt_widgets_notice']) ? sanitize_key(wp_unslash($_GET['aksrt_widgets_notice'])) : '';
|
||||
if ($notice !== '') {
|
||||
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('小工具配置已更新。', 'aksrt') . '</p></div>';
|
||||
}
|
||||
?>
|
||||
<div class="aksrt-widget-transfer postbox">
|
||||
<div class="postbox-header"><h2><?php esc_html_e('AKSRT 小工具模板与导入导出', 'aksrt'); ?></h2></div>
|
||||
<div class="inside aksrt-widget-transfer__grid">
|
||||
<div class="aksrt-widget-transfer-modal" id="aksrt-widget-transfer-modal" aria-hidden="true" hidden>
|
||||
<button type="button" class="aksrt-widget-transfer-modal__backdrop" data-aksrt-transfer-close aria-label="<?php esc_attr_e('关闭', 'aksrt'); ?>"></button>
|
||||
<section class="aksrt-widget-transfer-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="aksrt-widget-transfer-title">
|
||||
<button type="button" class="aksrt-widget-transfer-modal__close" data-aksrt-transfer-close aria-label="<?php esc_attr_e('关闭', 'aksrt'); ?>">×</button>
|
||||
<h2 id="aksrt-widget-transfer-title"><?php esc_html_e('小工具区域管理', 'aksrt'); ?></h2>
|
||||
<p class="description aksrt-widget-transfer-modal__area"></p>
|
||||
<nav class="aksrt-widget-transfer-modal__tabs" aria-label="<?php esc_attr_e('小工具操作', 'aksrt'); ?>">
|
||||
<button type="button" class="button is-active" data-transfer-tab="export"><?php esc_html_e('导出', 'aksrt'); ?></button>
|
||||
<button type="button" class="button" data-transfer-tab="import"><?php esc_html_e('导入', 'aksrt'); ?></button>
|
||||
<button type="button" class="button" data-transfer-tab="template"><?php esc_html_e('内置模板', 'aksrt'); ?></button>
|
||||
</nav>
|
||||
<div class="aksrt-widget-transfer-modal__panel" data-transfer-panel="export">
|
||||
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
|
||||
<h3><?php esc_html_e('导出', 'aksrt'); ?></h3>
|
||||
<input type="hidden" name="action" value="aksrt_widgets_export">
|
||||
<?php wp_nonce_field(self::NONCE); ?>
|
||||
<?php self::sidebar_select($sidebars, 'sidebar', true); ?>
|
||||
<?php submit_button(__('下载 aksrt.widgets.v1', 'aksrt'), 'secondary', 'submit', false); ?>
|
||||
<input type="hidden" name="sidebar" value="">
|
||||
<p><?php esc_html_e('导出当前小工具区域的配置文件。', 'aksrt'); ?></p>
|
||||
<?php submit_button(__('下载配置', 'aksrt'), 'primary', 'submit', false); ?>
|
||||
</form>
|
||||
</div>
|
||||
<div class="aksrt-widget-transfer-modal__panel" data-transfer-panel="import" hidden>
|
||||
<form method="post" enctype="multipart/form-data" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
|
||||
<h3><?php esc_html_e('导入', 'aksrt'); ?></h3>
|
||||
<input type="hidden" name="action" value="aksrt_widgets_import">
|
||||
<?php wp_nonce_field(self::NONCE); ?>
|
||||
<input type="file" name="widgets_file" accept="application/json,.json" required>
|
||||
<?php self::mode_select(); ?>
|
||||
<?php self::sidebar_select($sidebars, 'target_sidebar', true, __('沿用文件中的区域', 'aksrt')); ?>
|
||||
<?php submit_button(__('导入配置', 'aksrt'), 'secondary', 'submit', false); ?>
|
||||
<input type="hidden" name="target_sidebar" value="">
|
||||
<?php submit_button(__('导入配置', 'aksrt'), 'primary', 'submit', false); ?>
|
||||
</form>
|
||||
</div>
|
||||
<div class="aksrt-widget-transfer-modal__panel" data-transfer-panel="template" hidden>
|
||||
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
|
||||
<h3><?php esc_html_e('内置模板', 'aksrt'); ?></h3>
|
||||
<input type="hidden" name="action" value="aksrt_widgets_template">
|
||||
<?php wp_nonce_field(self::NONCE); ?>
|
||||
<select name="template" class="aksrt-widget-template-select">
|
||||
<select name="template" class="aksrt-widget-template-select" aria-label="<?php esc_attr_e('选择内置模板', 'aksrt'); ?>">
|
||||
<?php foreach ($templates as $key => $template) : ?>
|
||||
<option value="<?php echo esc_attr($key); ?>"><?php echo esc_html((string) $template['label']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php self::mode_select(); ?>
|
||||
<?php self::sidebar_select($sidebars, 'target_sidebar', true, __('使用模板默认区域', 'aksrt')); ?>
|
||||
<input type="hidden" name="target_sidebar" value="">
|
||||
<div class="aksrt-widget-transfer__actions">
|
||||
<button type="button" class="button aksrt-widget-template-preview"><?php esc_html_e('预览模块', 'aksrt'); ?></button>
|
||||
<?php submit_button(__('应用模板', 'aksrt'), 'primary', 'submit', false); ?>
|
||||
@@ -65,6 +81,7 @@ final class TransferController {
|
||||
</form>
|
||||
<div class="aksrt-widget-template-preview-output" hidden></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
@@ -136,17 +153,6 @@ final class TransferController {
|
||||
exit;
|
||||
}
|
||||
|
||||
private static function sidebar_select(array $sidebars, string $name, bool $allow_empty, string $empty_label = ''): void {
|
||||
echo '<label><span class="screen-reader-text">' . esc_html__('小工具区域', 'aksrt') . '</span><select name="' . esc_attr($name) . '">';
|
||||
if ($allow_empty) {
|
||||
echo '<option value="">' . esc_html($empty_label !== '' ? $empty_label : __('全部区域', 'aksrt')) . '</option>';
|
||||
}
|
||||
foreach ($sidebars as $id => $sidebar) {
|
||||
echo '<option value="' . esc_attr((string) $id) . '">' . esc_html((string) ($sidebar['name'] ?? $id)) . '</option>';
|
||||
}
|
||||
echo '</select></label>';
|
||||
}
|
||||
|
||||
private static function mode_select(): void {
|
||||
echo '<select name="mode"><option value="append">' . esc_html__('追加', 'aksrt') . '</option><option value="replace">' . esc_html__('替换区域', 'aksrt') . '</option></select>';
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ final class WidgetCatalog {
|
||||
'general' => [
|
||||
['search', '搜索框', '显示多种样式的站内搜索框', 'sidebar'],
|
||||
['posts_toc', '文章目录树', '显示当前文章的标题目录', 'sidebar'],
|
||||
['quote', '一言', '定时切换本地或在线句子', 'sidebar'],
|
||||
['quote', '一言', '显示并定时切换中英文一言', 'sidebar'],
|
||||
['icon_cards', '图标卡片(竖版)', '图标在文字上方的特色卡片', 'wide'],
|
||||
['video', '视频', '支持常规视频与流媒体格式', 'any'],
|
||||
['embed', '超级嵌入', '按比例嵌入外部在线内容', 'any'],
|
||||
|
||||
@@ -43,11 +43,7 @@ final class WidgetSchemas {
|
||||
['id' => 'in_affix', 'type' => 'switcher', 'label' => '侧栏随动(仅在侧边栏有效)', 'default' => false],
|
||||
];
|
||||
case 'quote':
|
||||
return [
|
||||
['id' => 'source', 'type' => 'select', 'label' => '内容来源', 'default' => 'local', 'options' => ['local' => '本地句子', 'api' => '在线一言']],
|
||||
['id' => 'quotes', 'type' => 'textarea', 'label' => '本地句子', 'description' => '每行一句', 'default' => "保持好奇,持续创造。\n知识因为分享而更有价值。"],
|
||||
['id' => 'interval', 'type' => 'spinner', 'label' => '自动更新间隔(秒)', 'default' => 30, 'min' => 5, 'max' => 300],
|
||||
];
|
||||
return [];
|
||||
case 'video':
|
||||
return [
|
||||
['id' => 'url', 'type' => 'upload', 'label' => '视频地址', 'default' => ''],
|
||||
|
||||
@@ -43,13 +43,13 @@ $render_config = function_exists('aksrt_archive_get_render_config')
|
||||
|
||||
$filter_groups = [];
|
||||
if (!empty($archive_settings['ajax_category_enabled']) && function_exists('aksrt_archive_build_term_filter_group')) {
|
||||
$group = aksrt_archive_build_term_filter_group('category', (array) ($archive_settings['ajax_category'] ?? []), __('分类', 'aksrt'), $ajax_filter);
|
||||
$group = aksrt_archive_build_term_filter_group('category', (array) ($archive_settings['ajax_category'] ?? []), __('分类', 'aksrt'));
|
||||
if ($group !== []) {
|
||||
$filter_groups[] = $group;
|
||||
}
|
||||
}
|
||||
if (!empty($archive_settings['ajax_tag_enabled']) && function_exists('aksrt_archive_build_term_filter_group')) {
|
||||
$group = aksrt_archive_build_term_filter_group('post_tag', (array) ($archive_settings['ajax_tag'] ?? []), __('标签', 'aksrt'), $ajax_filter);
|
||||
$group = aksrt_archive_build_term_filter_group('post_tag', (array) ($archive_settings['ajax_tag'] ?? []), __('标签', 'aksrt'));
|
||||
if ($group !== []) {
|
||||
$filter_groups[] = $group;
|
||||
}
|
||||
@@ -59,8 +59,7 @@ if (!empty($archive_settings['custom_filter_enabled']) && function_exists('aksrt
|
||||
$filter_groups,
|
||||
aksrt_archive_build_custom_filter_groups(
|
||||
(array) ($archive_settings['custom_dimensions'] ?? []),
|
||||
$archive_settings['custom_filter_keys'] ?? 'all',
|
||||
$ajax_filter
|
||||
$archive_settings['custom_filter_keys'] ?? 'all'
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -85,7 +84,7 @@ if (function_exists('aksrt_archive_open_shell')) {
|
||||
'header_style' => $header_style,
|
||||
'show_cover' => !empty($archive_settings['show_cover']),
|
||||
'cover_url' => (string) ($archive_settings['cover_url'] ?? ''),
|
||||
'eyebrow' => __('标签归档', 'aksrt'),
|
||||
'post_count' => !empty($archive_settings['post_count']),
|
||||
'title' => single_tag_title('', false),
|
||||
'description' => $description,
|
||||
'admin_path' => 'page-display/tag',
|
||||
@@ -97,17 +96,13 @@ if (function_exists('aksrt_archive_open_shell')) {
|
||||
<?php
|
||||
if (!empty($archive_settings['filter_bar']) && function_exists('aksrt_archive_render_filter_bar')) {
|
||||
aksrt_archive_render_filter_bar([
|
||||
'ajax' => $ajax_filter,
|
||||
'ajax_archive' => 'tag',
|
||||
'term_id' => (int) $term->term_id,
|
||||
'aria_label' => __('标签工具条', 'aksrt'),
|
||||
'meta_text' => !empty($archive_settings['post_count']) ? sprintf(_n('%d 篇文章', '%d 篇文章', (int) $term->count, 'aksrt'), (int) $term->count) : '',
|
||||
'groups' => $filter_groups,
|
||||
'sort_enabled' => !empty($archive_settings['orderby_enabled']),
|
||||
'sort_current' => $current_sort,
|
||||
'sort_links' => $sort_links,
|
||||
'sort_labels' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::homepage_sort_labels() : [],
|
||||
'sort_as_main' => true,
|
||||
'sort_option' => (array) $archive_settings['orderby_option'],
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -119,7 +114,6 @@ if (function_exists('aksrt_archive_open_shell')) {
|
||||
'query_context' => 'archive',
|
||||
'stream_class' => 'archive__ajax-wrap content-stream--archive content-stream--tag',
|
||||
'empty_text' => __('该标签下暂无文章', 'aksrt'),
|
||||
'ajax_enabled' => $ajax_filter,
|
||||
]);
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -21,7 +21,7 @@ const termMeta = read('src/Core/TermMeta.php');
|
||||
const filtersHelper = read('inc/helpers/aksrt-archive-filters.php');
|
||||
const categoryTpl = read('category.php');
|
||||
const tagTpl = read('tag.php');
|
||||
const archiveJs = read('assets/js/core/archive.js');
|
||||
const listJs = read('assets/js/runtime/main.js');
|
||||
const globalController = read('src/Admin/GlobalController.php');
|
||||
|
||||
// 参考主题字段键(顺序即参考主题定义顺序)。
|
||||
@@ -150,18 +150,16 @@ test('前台链路:覆盖与门禁(参考主题 zib-category.php 语义)',
|
||||
test('前台链路:维度筛选渲染与查询过滤', () => {
|
||||
// 渲染:维度 chip 组(全部 chip 参考主题 focus 语义)。
|
||||
assert.match(filtersHelper, /function aksrt_archive_build_custom_filter_groups\(/);
|
||||
assert.match(filtersHelper, /data-filter-dim/);
|
||||
assert.match(filtersHelper, /ajax-replace="true" class="ajax-next/);
|
||||
assert.match(filtersHelper, /\$keys !== 'all' && !in_array\(\$dim_key, \(array\) \$keys, true\)/);
|
||||
assert.match(categoryTpl, /aksrt_archive_build_custom_filter_groups\(/);
|
||||
assert.match(tagTpl, /aksrt_archive_build_custom_filter_groups\(/);
|
||||
// 查询:_aksrt_cf_{key} meta_query LIKE(主查询 + AJAX 端点)。
|
||||
assert.match(termMeta, /'_aksrt_cf_' \. \$dim_key/);
|
||||
assert.match(termMeta, /'compare'\s*=>\s*'LIKE'/);
|
||||
assert.match(globalController, /'custom_filter'\s*=>\s*\['type' => 'string'/);
|
||||
assert.match(globalController, /json_decode\(\$custom_raw, true\)/);
|
||||
assert.match(globalController, /'_aksrt_cf_' \. \$dim_key/);
|
||||
// JS:维度 chip 状态、请求参数与 URL 同步。
|
||||
assert.match(archiveJs, /data-filter-dim/);
|
||||
assert.match(archiveJs, /JSON\.stringify\(state\.custom\)/);
|
||||
assert.match(archiveJs, /state\.custom\[dimKey\]/);
|
||||
// Category filtering uses the same HTML AJAX pipeline as the homepage.
|
||||
assert.doesNotMatch(globalController, /rest_archive_posts|\/archive\/posts/);
|
||||
assert.match(listJs, /win-ajax-replace/);
|
||||
assert.match(listJs, /post_ajax/);
|
||||
assert.match(filtersHelper, /'key'\s*=>\s*'_aksrt_cf_|\$dim_key/);
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
# 首页、分类与新版小工具对齐记录
|
||||
|
||||
参考目录:`C:\Users\Xing\Desktop\zibll`。对照当前启用的实现,不使用标记为旧版或即将删除的文章小工具。
|
||||
|
||||
## 七项改动
|
||||
|
||||
| 任务 | 实现与修正 | 主要参考源码 |
|
||||
| --- | --- | --- |
|
||||
| 首页 | 恢复 14px 基础字号和参考行高;清除首页额外卡片、导航和分页覆盖;默认按发布时间排序;允许清空排序按钮;栏目骨架采用对应分类的列表模式 | `index.php`、`inc/functions/zib-index.php`、`inc/functions/zib-category.php` |
|
||||
| 分类页 | 封面、标题计数、描述、搜索入口、分页提示和筛选菜单结构对齐;分类与下拉项分开;子分类按层级展开;筛选使用通用 HTML 列表加载机制 | `category.php`、`inc/functions/zib-category.php` |
|
||||
| 多栏目文章 | 采用新版 `widget-tab-post` 的 list/card/mini 结构;恢复导航间距、mini 内边距和下划线;保存已加载栏目和翻页内容;列表模式禁用全局多图/无图模式干扰 | `inc/widgets/widget-posts.php` 的 `zib_widget_ui_tab_post`、`css/main.css` |
|
||||
| 分类聚合卡片 | SVG 白名单保留火焰;时钟、文章统计改为线框字形并修正字重;更新时间随排序来源变化;恢复各类统计文案;空分类不产生空白列 | `inc/functions/zib-category.php` 的 `zib_term_aggregation`、`inc/functions/zib-theme.php` |
|
||||
| 后台小工具 | 按区域操作工具条、单一导入/导出/模板弹窗、自定义器移除按钮;表单懒加载、失败重试、折叠分组、色样和分类勾选行为 | `inc/widgets/widget-index.php`、`js/widget-set.js`、当前 `inc/csf-framework` |
|
||||
| 一言 | 文章内一言和新版小工具共用本地双语句库、点击切换和 30 秒刷新;保留参考主题的自定义句库入口 | `yiyan/qv-yiyan.php`、`yiyan/qv-yiyan.txt`、`js/main.js` |
|
||||
| 评论区 | 最新查询固定 DESC;最热按点赞与时间排序且保留无点赞元数据的评论;空态插图、文案和筛选栏显示条件对齐 | `template/comments.php`、`inc/functions/zib-comments-list.php`、`css/main.css` |
|
||||
|
||||
已删除独立分类 REST 筛选端点与 `assets/js/core/archive.js`,以及在线一言 REST 接口、旧一言来源配置、后台常驻三栏工具面板和被替代的样式。没有添加迁移或兼容双路。
|
||||
|
||||
## 验证
|
||||
|
||||
- `node tools/build-public-assets.mjs`:通过,公共 CSS 和前台运行时资源已更新。
|
||||
- `assets/admin` 中 `npm run build`:通过,SPA 和 NaiveUI 样式已生成。
|
||||
- 所有改动及新增 PHP 文件的 `php -l`:通过。
|
||||
- 改动 JavaScript 的 `node --check`:通过。
|
||||
- `php tests/widget-system/run.php`:139 项断言通过。
|
||||
- 递归执行 `tests/` 下全部 `*.test.cjs`,包含小工具、后台、导航和投稿测试:760 项全部通过,无失败、跳过或取消。
|
||||
- `git diff --check`:通过。
|
||||
|
||||
新增发现页渲染、评论排序行为、一言和后台表单契约测试。分类页头覆盖首屏计数、默认封面、搜索、第二页和简洁标题;小工具覆盖 SVG、行内/下拉分离、栏目缓存标记、数字分页和 mini 样式。渲染 harness 使用轻量 WordPress 桩,未启动真实 WordPress。
|
||||
|
||||
按任务要求,没有进行浏览器截图对比或真实视觉效果验收。
|
||||
|
||||
## 既有 8 项失败的修复
|
||||
|
||||
这些失败均由模块拆分或资源打包后未更新的测试契约引起。已核对现有实现并保留原有功能校验,没有恢复旧实现或删除断言来掩盖失败:
|
||||
|
||||
- `financial-flow-contract.test.cjs`:提现历史交互已经拆到 `credits-withdraw-history.js`,测试改为检查该模块;每页 10 条、到账金额、时间、隐私字段等原有断言保留。
|
||||
- `referral-qr-contract.test.cjs`:推广海报已拆到 `credits-affiliate.js`,测试改为检查真实模块及其本地二维码依赖;画布、弹窗静止隐藏和复制等原有功能断言保留。
|
||||
- `toast-contract.test.cjs`:缓存版本现在取全部 runtime JS 文件的最新修改时间,校验更新为该机制,继续禁止旧支付运行时入口。
|
||||
- `vip-modal-skeleton.test.cjs`:用户菜单样式已经并入公共 CSS,校验改为公共包仅包含一次且不再单独入队,与导航契约一致。
|
||||
|
||||
另新增两项实际行为测试:
|
||||
|
||||
- `withdrawal-history-runtime.test.cjs`:执行真实提现历史脚本,验证打开弹窗、第一页和第二页请求、固定每页 10 条、到账金额渲染及隐藏私有 note。
|
||||
- `runtime-assets-behavior-contract.test.cjs`:通过 PHP harness 调用真实 `ScriptsPart1c`,验证主运行时文件更新、叶子模块更新及无文件时的主题版本回退。
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* Exercise the hottest comments_clauses hook without booting WordPress.
|
||||
* Run with: php tests/widget-system/comment-order-behavior.php
|
||||
*/
|
||||
|
||||
define('ABSPATH', __DIR__ . '/');
|
||||
|
||||
final class WP_Comment_Query {
|
||||
/** @var array<string,mixed> */
|
||||
public array $query_vars;
|
||||
|
||||
/** @param array<string,mixed> $query_vars */
|
||||
public function __construct(array $query_vars) {
|
||||
$this->query_vars = $query_vars;
|
||||
}
|
||||
}
|
||||
|
||||
final class AKSRT_Test_WPDB {
|
||||
public string $comments = 'wp_comments';
|
||||
public string $commentmeta = 'wp_commentmeta';
|
||||
}
|
||||
|
||||
$GLOBALS['wpdb'] = new AKSRT_Test_WPDB();
|
||||
$GLOBALS['aksrt_test_filters'] = [];
|
||||
|
||||
function add_filter(string $tag, callable $callback, int $priority = 10, int $accepted_args = 1): void {
|
||||
$GLOBALS['aksrt_test_filters'][$tag][] = [$callback, $priority, $accepted_args];
|
||||
}
|
||||
|
||||
function sanitize_key(string $key): string {
|
||||
return strtolower(preg_replace('/[^a-z0-9_\-]/', '', $key) ?? '');
|
||||
}
|
||||
|
||||
function wp_unslash($value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
function aksrt_test_apply_comments_clauses(array $clauses, WP_Comment_Query $query): array {
|
||||
$filters = $GLOBALS['aksrt_test_filters']['comments_clauses'] ?? [];
|
||||
usort($filters, static fn (array $a, array $b): int => $a[1] <=> $b[1]);
|
||||
foreach ($filters as [$callback, $priority, $accepted_args]) {
|
||||
$clauses = $callback($clauses, $query);
|
||||
}
|
||||
return $clauses;
|
||||
}
|
||||
|
||||
require __DIR__ . '/../../inc/helpers/aksrt-comments.php';
|
||||
|
||||
$newest_args = aksrt_comments_template_query_args(['order' => 'ASC']);
|
||||
if (($newest_args['order'] ?? '') !== 'DESC' || ($newest_args['orderby'] ?? '') !== 'comment_date_gmt') {
|
||||
throw new RuntimeException('The newest tab must use descending comment date order regardless of the WordPress discussion setting.');
|
||||
}
|
||||
|
||||
$_GET['aksrt_comment_order'] = 'hottest';
|
||||
$hottest_args = aksrt_comments_template_query_args([]);
|
||||
if (empty($hottest_args['aksrt_hottest_order']) || ($hottest_args['order'] ?? '') !== 'DESC') {
|
||||
throw new RuntimeException('The hottest tab must set a descending, query-local hottest marker.');
|
||||
}
|
||||
unset($_GET['aksrt_comment_order']);
|
||||
|
||||
$base = [
|
||||
'join' => '',
|
||||
'where' => 'wp_comments.comment_post_ID = 42',
|
||||
'orderby' => 'wp_comments.comment_date_gmt DESC',
|
||||
];
|
||||
|
||||
$hot = aksrt_test_apply_comments_clauses($base, new WP_Comment_Query([
|
||||
'post_id' => 42,
|
||||
'aksrt_hottest_order' => true,
|
||||
]));
|
||||
|
||||
if (!str_contains($hot['join'], 'LEFT JOIN wp_commentmeta AS aksrt_cm_hot')) {
|
||||
throw new RuntimeException('Hottest ordering must use LEFT JOIN so comments without like metadata remain in the result.');
|
||||
}
|
||||
if (!str_contains($hot['orderby'], 'COALESCE(aksrt_cm_hot.meta_value, 0) AS UNSIGNED) DESC')) {
|
||||
throw new RuntimeException('Hottest ordering must sort likes from high to low and treat missing metadata as zero.');
|
||||
}
|
||||
if (!str_contains($hot['orderby'], 'wp_comments.comment_date_gmt DESC')) {
|
||||
throw new RuntimeException('Equal like counts must use newest date as the stable tie-breaker.');
|
||||
}
|
||||
if ($hot['where'] !== $base['where']) {
|
||||
throw new RuntimeException('Hottest sorting must not alter the query scope.');
|
||||
}
|
||||
|
||||
$ordinary = aksrt_test_apply_comments_clauses($base, new WP_Comment_Query([
|
||||
'post_id' => 99,
|
||||
'orderby' => 'comment_date_gmt',
|
||||
]));
|
||||
if ($ordinary !== $base) {
|
||||
throw new RuntimeException('Queries without the hottest marker must remain unchanged.');
|
||||
}
|
||||
|
||||
fwrite(STDOUT, "Hottest comment query behavior passed.\n");
|
||||
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
test('hottest query hook retains unliked comments and is query-local', () => {
|
||||
const script = path.resolve(__dirname, 'comment-order-behavior.php');
|
||||
const result = spawnSync('php', [script], { encoding: 'utf8' });
|
||||
|
||||
assert.equal(result.status, 0, result.stderr || result.stdout);
|
||||
assert.match(result.stdout, /Hottest comment query behavior passed/);
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
'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('comment order matches the reference newest and hottest behavior', () => {
|
||||
const php = read('inc/helpers/aksrt-comments.php');
|
||||
|
||||
assert.match(php, /\$args\['order'\] = 'DESC';\s*\$args\['orderby'\] = 'comment_date_gmt';/);
|
||||
assert.match(php, /\$args\['aksrt_hottest_order'\] = true;/);
|
||||
assert.match(php, /CAST\(COALESCE\(aksrt_cm_hot\.meta_value, 0\) AS UNSIGNED\) DESC/);
|
||||
assert.match(php, /\$query->query_vars\['aksrt_hottest_order'\]/);
|
||||
assert.equal(php.includes("$args['order'] = 'ASC'"), false, 'the reference always orders newest comments first');
|
||||
assert.equal(php.includes("$order === 'oldest'"), false, 'the unsupported oldest tab must not remain in the UI query path');
|
||||
assert.equal(php.includes('aksrt_comments_apply_hottest_order'), false, 'unscoped global hot-order hook must be removed');
|
||||
assert.equal(php.includes('reverse_top_level'), false, 'DESC results must not be reversed a second time');
|
||||
});
|
||||
|
||||
test('empty comment state uses the reference illustration and caption', () => {
|
||||
const php = read('inc/helpers/aksrt-comments.php');
|
||||
|
||||
assert.match(php, /aksrt_get_null\(\$text, 40, 'comment-null\.svg', 'comment comment-null', 280\)/);
|
||||
assert.match(php, /\$text = __\('暂无评论内容', 'aksrt'\);/);
|
||||
assert.ok(fs.existsSync(path.join(root, 'assets/img/comment-null.svg')), 'the reference empty-state illustration must ship with the theme');
|
||||
assert.equal(php.includes('暂无作者评论'), false, 'empty filter state uses the reference caption');
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
function renderFixtures() {
|
||||
const output = execFileSync('php', [path.join(__dirname, 'discovery-reference-harness.php')], { encoding: 'utf8' });
|
||||
return JSON.parse(output);
|
||||
}
|
||||
|
||||
test('term aggregation renders the real SVG use element, total count, and configured first page', () => {
|
||||
const fixture = renderFixtures();
|
||||
const card = fixture.aggregation;
|
||||
assert.match(card, /<svg class="zibsvg-hot" viewbox="0 0 16 16" aria-hidden="true"><use href="#zibsvg-hot"><\/use><\/svg>/);
|
||||
const sanitizer = read('inc/helpers/aksrt-graphic-card.php');
|
||||
assert.match(sanitizer, /'svg'\s*=>\s*\['class'\s*=>\s*true,\s*'viewbox'\s*=>\s*true,\s*'aria-hidden'\s*=>\s*true\]/);
|
||||
assert.match(sanitizer, /'use'\s*=>\s*\['href'\s*=>\s*true,\s*'xlink:href'\s*=>\s*true\]/);
|
||||
assert.match(card, /7篇文章/);
|
||||
assert.match(card, /文章 101/);
|
||||
assert.match(card, /文章 102/);
|
||||
assert.match(card, /class="fa far fa-file-alt fa-fw"/);
|
||||
assert.match(card, /class="fa far fa-clock mr3"/);
|
||||
assert.doesNotMatch(card, /文章 103|空分类/);
|
||||
|
||||
const baseCss = read('assets/css/frontend/base.css');
|
||||
assert.match(baseCss, /\.fa\.far\s*\{[^}]*font-weight:\s*400;[^}]*\}/s);
|
||||
|
||||
const firstQuery = fixture.aggregation_queries[0];
|
||||
assert.equal(firstQuery.posts_per_page, 2);
|
||||
assert.equal(firstQuery.no_found_rows, false);
|
||||
assert.deepEqual(firstQuery.category__in, [7]);
|
||||
assert.equal(fixture.aggregation_queries[1].category__in[0], 8);
|
||||
});
|
||||
|
||||
test('category filters keep dropdown and inline terms in separate rendered groups on the ajax-option pipeline', () => {
|
||||
const fixture = renderFixtures();
|
||||
const html = fixture.archive_filter_html;
|
||||
const group = fixture.filter_group;
|
||||
assert.deepEqual(group.chips.map((chip) => chip.label), ['技术分类']);
|
||||
assert.deepEqual(group.dropdown.map((chip) => chip.label), ['下拉分类']);
|
||||
assert.match(html, /class="ajax-option ajax-replace" win-ajax-replace="filter"/);
|
||||
assert.match(html, /class="ajax-next" href="https:\/\/example\.test\/category\/7"/);
|
||||
const dropdown = html.match(/<ul class="dropdown-menu">([\s\S]*?)<\/ul>/)[1];
|
||||
const inline = html.match(/<ul class="list-inline[^>]*option-items">([\s\S]*?)<\/ul>/)[1];
|
||||
assert.match(dropdown, /下拉分类/);
|
||||
assert.doesNotMatch(dropdown, /技术分类/);
|
||||
assert.match(inline, /技术分类/);
|
||||
assert.doesNotMatch(inline, /下拉分类/);
|
||||
});
|
||||
|
||||
test('category header matches first-page, later-page, and simple-title render contracts', () => {
|
||||
const fixture = renderFixtures();
|
||||
const first = fixture.term_header_first;
|
||||
assert.match(first, /win-ajax-replace="page-cover"/);
|
||||
assert.match(first, /src="https:\/\/example\.test\/theme\/assets\/img\/user_t\.jpg"/);
|
||||
assert.match(first, /共31篇/);
|
||||
assert.match(first, /class="main-search-btn"[^>]*search-trem="7"/);
|
||||
|
||||
const second = fixture.term_header_second;
|
||||
assert.doesNotMatch(second, /win-ajax-replace="page-cover"|共31篇/);
|
||||
assert.match(second, /第2页/);
|
||||
assert.match(second, /search-trem="7"/);
|
||||
|
||||
const simple = fixture.term_header_simple;
|
||||
assert.match(simple, /class="aksrt-panel relative"/);
|
||||
assert.match(simple, /title-h-left/);
|
||||
assert.match(simple, /共31篇/);
|
||||
assert.match(simple, /简洁描述/);
|
||||
assert.doesNotMatch(simple, /class="page-cover aksrt-panel|<img/);
|
||||
});
|
||||
|
||||
test('homepage sorting retains distinct dropdown and inline option containers', () => {
|
||||
const html = renderFixtures().home_sort;
|
||||
assert.match(html, /<ul class="dropdown-menu">[\s\S]*更新[\s\S]*随机[\s\S]*<\/ul>/);
|
||||
assert.match(html, /<ul class="list-inline[^>]*option-items"><a[^>]*orderby=date[\s\S]*发布[\s\S]*<a[^>]*orderby=views[\s\S]*浏览[\s\S]*<\/ul>/);
|
||||
assert.equal((html.match(/class="ajax-next/g) || []).length, 9);
|
||||
});
|
||||
|
||||
test('number pagination renders a page trigger and tab panes distinguish loaded from lazy content', () => {
|
||||
const fixture = renderFixtures();
|
||||
assert.match(fixture.posts, /class="widget-ajaxpager"[^>]*data-loaded="1"/);
|
||||
assert.match(fixture.posts, /class="next-page page-numbers"[^>]*data-widget-page="2"/);
|
||||
assert.match(fixture.tabs, /data-segment="0" data-loaded="1"/);
|
||||
const lazyPane = fixture.tabs.match(/<div class="widget-ajaxpager"[^>]*data-segment="1"[^>]*>[\s\S]*?<\/div>/)[0];
|
||||
assert.doesNotMatch(lazyPane, /data-loaded="1"/);
|
||||
assert.match(fixture.tabs, /class="next-page page-numbers"[^>]*data-widget-page="2"/);
|
||||
|
||||
const runtime = read('assets/widgets/frontend.js');
|
||||
assert.match(runtime, /container\.dataset\.loaded = '1'/);
|
||||
assert.match(runtime, /if \(panel\.dataset\.loaded !== '1'\) loadItems\(panel, 1, false\)/);
|
||||
assert.match(runtime, /var pageLink = event\.target\.closest\('\.page-numbers\[data-widget-page\]'\)/);
|
||||
assert.match(runtime, /loadItems\(container, Number\(pageLink\.dataset\.widgetPage \|\| 1\), false\)/);
|
||||
});
|
||||
|
||||
test('archive filters use the shared ajax-next handler without a category REST/archive script path', () => {
|
||||
const filters = read('inc/helpers/aksrt-archive-filters.php');
|
||||
const ajax = read('assets/js/core/ajax-list.js');
|
||||
const archives = read('inc/helpers/aksrt-archive.php');
|
||||
const globalController = read('src/Admin/GlobalController.php');
|
||||
assert.match(filters, /class="ajax-option ajax-replace" win-ajax-replace="filter"/);
|
||||
assert.match(filters, /class="ajax-next/);
|
||||
assert.match(ajax, /link\.closest\("\.ajax-option, \.post_ajax_trigger"\)/);
|
||||
assert.doesNotMatch(archives, /register_rest_route|rest_archive_posts|\/archive\/posts/);
|
||||
assert.doesNotMatch(globalController, /register_rest_route\([^\n]*['"][^'"]*archive\/posts/);
|
||||
assert.equal(fs.existsSync(path.join(root, 'assets', 'js', 'core', 'archive.js')), false);
|
||||
|
||||
const widgetCss = read('assets/widgets/frontend.css');
|
||||
assert.match(widgetCss, /\.zib-widget\.posts-mini-lists\s*\{\s*padding:\s*5px;/);
|
||||
assert.doesNotMatch(widgetCss, /\.widget-tab-post[^{}]*\{[^}]*!important/s);
|
||||
});
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
/** Focused render harness for homepage/category discovery widgets and filters. */
|
||||
declare(strict_types=1);
|
||||
|
||||
define('ABSPATH', dirname(__DIR__, 2) . DIRECTORY_SEPARATOR);
|
||||
define('DAY_IN_SECONDS', 86400);
|
||||
|
||||
class WP_Error { public function __construct(public string $code = '') {} }
|
||||
class WP_Term {
|
||||
public int $term_taxonomy_id;
|
||||
public function __construct(public int $term_id, public string $name, public string $description = '', public string $taxonomy = 'category', public int $count = 7, public int $parent = 0) { $this->term_taxonomy_id = $term_id; }
|
||||
}
|
||||
class WP_Post { public function __construct(public int $ID) {} }
|
||||
class WP_Query {
|
||||
public array $posts = [];
|
||||
public int $found_posts = 0;
|
||||
public int $max_num_pages = 1;
|
||||
private int $cursor = 0;
|
||||
public function __construct(public array $query_args = []) {
|
||||
$GLOBALS['discovery_queries'][] = $query_args;
|
||||
$cats = array_map('intval', (array) ($query_args['category__in'] ?? []));
|
||||
$rows = array_values(array_filter($GLOBALS['discovery_posts'], static fn(array $post): bool => !$cats || in_array($post['cat'], $cats, true)));
|
||||
$this->found_posts = count($rows);
|
||||
$per_page = max(1, (int) ($query_args['posts_per_page'] ?? 10));
|
||||
$this->max_num_pages = (int) ceil($this->found_posts / $per_page);
|
||||
$page = max(1, (int) ($query_args['paged'] ?? 1));
|
||||
foreach (array_slice($rows, ($page - 1) * $per_page, $per_page) as $row) $this->posts[] = new WP_Post($row['id']);
|
||||
}
|
||||
public function have_posts(): bool { return $this->cursor < count($this->posts); }
|
||||
public function the_post(): void { $post = $this->posts[$this->cursor++]; $GLOBALS['discovery_current_post'] = $post; }
|
||||
}
|
||||
class wpdb {
|
||||
public string $postmeta = 'wp_postmeta'; public string $posts = 'wp_posts'; public string $term_relationships = 'wp_term_relationships';
|
||||
public function prepare(string $query, ...$args): string { return $query; }
|
||||
public function get_var(string $query): string { return '789'; }
|
||||
}
|
||||
|
||||
$GLOBALS['wpdb'] = new wpdb();
|
||||
$GLOBALS['discovery_queries'] = [];
|
||||
$GLOBALS['discovery_current_post'] = null;
|
||||
$GLOBALS['discovery_terms'] = [
|
||||
7 => new WP_Term(7, '技术分类', '<p>分类说明</p>'),
|
||||
8 => new WP_Term(8, '空分类', ''),
|
||||
9 => new WP_Term(9, '下拉分类', ''),
|
||||
];
|
||||
$GLOBALS['discovery_queried_term'] = null;
|
||||
$GLOBALS['discovery_paged'] = 1;
|
||||
$GLOBALS['discovery_posts'] = [];
|
||||
for ($id = 101; $id <= 107; $id++) $GLOBALS['discovery_posts'][] = ['id' => $id, 'cat' => 7];
|
||||
|
||||
function __($text, $domain = null): string { return (string) $text; }
|
||||
function esc_html__($text, $domain = null): string { return esc_html($text); }
|
||||
function esc_attr__($text, $domain = null): string { return esc_attr($text); }
|
||||
function esc_html($value): string { return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
|
||||
function esc_attr($value): string { return esc_html($value); }
|
||||
function esc_html_e($text, $domain = null): void { echo esc_html($text); }
|
||||
function esc_attr_e($text, $domain = null): void { echo esc_attr($text); }
|
||||
function esc_url_raw($value): string { return (string) $value; }
|
||||
function esc_url($value): string { return esc_attr($value); }
|
||||
function wp_parse_args($args, array $defaults = []): array { return array_merge($defaults, is_array($args) ? $args : []); }
|
||||
function sanitize_key($value): string { return preg_replace('/[^a-z0-9_-]/', '', strtolower((string) $value)) ?? ''; }
|
||||
function sanitize_html_class($value): string { return preg_replace('/[^A-Za-z0-9_-]/', '', (string) $value) ?? ''; }
|
||||
function sanitize_text_field($value): string { return trim(strip_tags((string) $value)); }
|
||||
function absint($value): int { return abs((int) $value); }
|
||||
function wp_strip_all_tags($value): string { return trim(strip_tags((string) $value)); }
|
||||
function wp_kses_post($value): string { return strip_tags((string) $value, '<a><b><strong><em><br><p><span>'); }
|
||||
function wp_kses($value, array $allowed): string {
|
||||
$tags = '<' . implode('><', array_keys($allowed)) . '>';
|
||||
return strip_tags((string) $value, $tags);
|
||||
}
|
||||
function is_wp_error($value): bool { return $value instanceof WP_Error; }
|
||||
function wp_is_mobile(): bool { return false; }
|
||||
function wp_reset_postdata(): void { $GLOBALS['discovery_current_post'] = null; }
|
||||
function aksrt_is_lazy(string $key, bool $default = false): bool { return false; }
|
||||
function aksrt_get_lazy_attr(string $type, string $url, string $class = '', string $lazy = ''): string { return ' class="' . esc_attr($class) . '" src="' . esc_url($url) . '"'; }
|
||||
function aksrt_format_number($number): string { return number_format((int) $number); }
|
||||
function aksrt_renderer_svg_icon(string $name): string { return '<svg class="zibsvg-' . esc_attr($name) . '" viewbox="0 0 16 16" aria-hidden="true"><use href="#zibsvg-' . esc_attr($name) . '"></use></svg>'; }
|
||||
function aksrt_renderer_collect_post_data(array $config = []): array {
|
||||
$post = $GLOBALS['discovery_current_post'];
|
||||
return ['post_id' => $post->ID, 'title' => '文章 ' . $post->ID, 'subtitle' => '', 'permalink' => 'https://example.test/post/' . $post->ID, 'time_ago' => '2天前', 'views' => 42, 'comments' => 3];
|
||||
}
|
||||
function aksrt_get_post_favorites(int $id): int { return 5; }
|
||||
function aksrt_get_post_likes(int $id): int { return 6; }
|
||||
function get_term(int $id, string $taxonomy = 'category') { return $GLOBALS['discovery_terms'][$id] ?? new WP_Error('missing'); }
|
||||
function get_term_meta(int $id, string $key, bool $single = true): int { return $id === 7 ? 77 : 0; }
|
||||
function wp_get_attachment_url(int $id): string { return $id === 77 ? 'https://example.test/category-cover.jpg' : ''; }
|
||||
function get_posts(array $args): array { return []; }
|
||||
function get_the_post_thumbnail_url($id = null, string $size = 'post-thumbnail'): string { return ''; }
|
||||
function get_term_link(WP_Term $term) { return 'https://example.test/category/' . $term->term_id; }
|
||||
function current_user_can(string $capability): bool { return false; }
|
||||
function get_edit_term_link(int $id, string $taxonomy): string { return ''; }
|
||||
function aksrt_widget_str_cut(string $text, int $length): string { return $text; }
|
||||
function aksrt_render_placeholder(string $style, int $count): string { return '<div data-placeholder="' . esc_attr($style) . ':' . $count . '"></div>'; }
|
||||
function aksrt_render_post_list(array $args, WP_Query $query, bool $echo = true): string { return '<div class="rendered-post-count" data-count="' . count($query->posts) . '"></div>'; }
|
||||
function get_terms(array $args) {
|
||||
$ids = array_map('intval', (array) ($args['include'] ?? []));
|
||||
if (($args['fields'] ?? '') === 'ids') return [];
|
||||
$terms = $ids ? array_intersect_key($GLOBALS['discovery_terms'], array_flip($ids)) : $GLOBALS['discovery_terms'];
|
||||
return array_values($terms);
|
||||
}
|
||||
function get_queried_object() { return $GLOBALS['discovery_queried_term']; }
|
||||
function get_query_var(string $name, $default = '') { return $name === 'paged' ? $GLOBALS['discovery_paged'] : $default; }
|
||||
function get_ancestors(int $id, string $taxonomy, string $resource_type): array { return []; }
|
||||
function wp_parse_args_filter($args, array $defaults): array { return array_merge($defaults, $args); }
|
||||
function aksrt_homepage_get_current_url(): string { return 'https://example.test/?paged=3'; }
|
||||
function aksrt_homepage_url_del_paged(string $url): string { return preg_replace('/[?&]paged=\d+/', '', $url) ?: $url; }
|
||||
function add_query_arg($args, $url = false): string {
|
||||
if (is_array($args)) return (string) $url . (str_contains((string) $url, '?') ? '&' : '?') . http_build_query($args);
|
||||
return 'https://example.test/';
|
||||
}
|
||||
function remove_query_arg(array $keys, string $url): string { return $url; }
|
||||
function home_url(string $path = '/'): string { return 'https://example.test' . $path; }
|
||||
function get_template_directory_uri(): string { return 'https://example.test/theme'; }
|
||||
function wp_unslash($value): string { return (string) $value; }
|
||||
|
||||
require dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-graphic-card.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-widgets-markup.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-homepage.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-archive-filters.php';
|
||||
|
||||
use AKSRT\WidgetSystem\Renderers\ContentRenderer;
|
||||
|
||||
$aggregation = ContentRenderer::render('term_aggregation', [
|
||||
'term_id' => [7, 8], 'pc_row' => 2, 'orderby' => 'modified', 'order' => 'desc', 'count' => 2,
|
||||
]);
|
||||
$tabs = ContentRenderer::render('tab_posts', [
|
||||
'style' => 'list', 'count' => 2, 'paginate' => 'number',
|
||||
'tabs' => [['title' => '技术', 'cat' => '7'], ['title' => '空栏目', 'cat' => '8']],
|
||||
], ['widget_id' => 'aksrt_widget_tab_posts-4']);
|
||||
$posts = ContentRenderer::render('posts', ['style' => 'list', 'count' => 2, 'paginate' => 'number'], ['widget_id' => 'aksrt_widget_posts-3']);
|
||||
|
||||
$filter_group = aksrt_archive_build_term_filter_group('category', [
|
||||
'lists' => [7], 'dropdown' => true, 'dropdown_lists' => [9],
|
||||
], '分类');
|
||||
ob_start();
|
||||
aksrt_archive_render_filter_bar(['groups' => [$filter_group], 'aria_label' => '分类筛选']);
|
||||
$archive_filter_html = (string) ob_get_clean();
|
||||
|
||||
$GLOBALS['discovery_queried_term'] = $GLOBALS['discovery_terms'][7];
|
||||
$GLOBALS['wp_query'] = new WP_Query();
|
||||
$GLOBALS['wp_query']->found_posts = 31;
|
||||
$GLOBALS['discovery_paged'] = 1;
|
||||
ob_start();
|
||||
aksrt_archive_render_term_header([
|
||||
'header_style' => 'cover', 'show_cover' => true, 'cover_url' => '',
|
||||
'title' => '技术分类', 'description' => '首屏描述', 'post_count' => true,
|
||||
]);
|
||||
$term_header_first = (string) ob_get_clean();
|
||||
|
||||
$GLOBALS['discovery_paged'] = 2;
|
||||
ob_start();
|
||||
aksrt_archive_render_term_header([
|
||||
'header_style' => 'cover', 'show_cover' => true, 'cover_url' => '',
|
||||
'title' => '技术分类', 'description' => '第二页描述', 'post_count' => true,
|
||||
]);
|
||||
$term_header_second = (string) ob_get_clean();
|
||||
|
||||
$GLOBALS['discovery_paged'] = 1;
|
||||
ob_start();
|
||||
aksrt_archive_render_term_header([
|
||||
'header_style' => 'simple', 'show_cover' => false,
|
||||
'title' => '技术分类', 'description' => '简洁描述', 'post_count' => true,
|
||||
]);
|
||||
$term_header_simple = (string) ob_get_clean();
|
||||
|
||||
$home_sort = aksrt_homepage_get_option_orderby_but(['lists' => ['date', 'views'], 'dropdown' => true]);
|
||||
|
||||
$result = json_encode([
|
||||
'aggregation' => $aggregation,
|
||||
'aggregation_queries' => $GLOBALS['discovery_queries'],
|
||||
'tabs' => $tabs,
|
||||
'posts' => $posts,
|
||||
'filter_group' => $filter_group,
|
||||
'archive_filter_html' => $archive_filter_html,
|
||||
'term_header_first' => $term_header_first,
|
||||
'term_header_second' => $term_header_second,
|
||||
'term_header_simple' => $term_header_simple,
|
||||
'home_sort' => $home_sort,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
if ($result === false) {
|
||||
fwrite(STDERR, json_last_error_msg());
|
||||
exit(1);
|
||||
}
|
||||
echo $result;
|
||||
@@ -82,7 +82,7 @@ test('ledger history carries no financial flow coupling', () => {
|
||||
test('user withdrawal history uses one paginated ten-item endpoint', () => {
|
||||
const controller = read('src/Payment/CommissionLedger.php')
|
||||
const renderer = read('inc/helpers/user-center-renderers/credits-wallet.php')
|
||||
const script = read('assets/js/credits.js')
|
||||
const script = read('assets/js/credits-withdraw-history.js')
|
||||
const styles = read('assets/css/routes/user-center/credits.css')
|
||||
|
||||
assert.match(controller, /\$withdrawal_args\['per_page'\]\['default'\]\s*=\s*10/)
|
||||
|
||||
@@ -259,7 +259,8 @@ test('front-end page-display consumers read only the canonical section contracts
|
||||
assert.equal(category.includes('get_post_filter_settings'), false)
|
||||
assert.equal(category.includes('get_filter_config_for_category'), false)
|
||||
assert.equal(tag.includes('get_post_filter_settings'), false)
|
||||
assert.match(category, /ajax_category_enabled/)
|
||||
assert.match(category, /'ajax_category'\s*:\s*'ajax_tag'/)
|
||||
assert.match(category, /\$archive_settings\[\$key \. '_enabled'\]/)
|
||||
assert.match(category, /orderby_enabled/)
|
||||
assert.match(tag, /ajax_tag_enabled/)
|
||||
assert.match(tag, /orderby_enabled/)
|
||||
|
||||
@@ -111,7 +111,7 @@ test('forced mode wins and thumb_if_has ignores spare and lazy placeholders', ()
|
||||
assert.match(data, /has_post_format\(\['image', 'gallery'\]/);
|
||||
});
|
||||
|
||||
test('category card mode carries its term id through archive, AJAX and REST rendering', () => {
|
||||
test('category card mode carries its term id through archive and shared AJAX rendering', () => {
|
||||
const resolver = read('inc/helpers/aksrt-archive.php');
|
||||
const config = read('inc/helpers/posts-renderer/config.php');
|
||||
const admin = read('src/Admin/Admin.php');
|
||||
@@ -124,7 +124,8 @@ test('category card mode carries its term id through archive, AJAX and REST rend
|
||||
assert.match(config, /\$term_id = absint\(\$args\['term_id'\]\)/);
|
||||
assert.doesNotMatch(config, /get_queried_object_id/);
|
||||
assert.match(admin, /\$clean\['term_id'\]\s*=\s*absint\(\$config\['term_id'\]\)/);
|
||||
assert.match(global, /aksrt_resolve_post_list_render_config\(\[\s*'context'\s*=>\s*\$archive_type,\s*'term_id'\s*=>\s*\$archive_type === 'category' \? \$term_id : 0,/);
|
||||
assert.doesNotMatch(global, /rest_archive_posts|\/archive\/posts/);
|
||||
assert.match(read('category.php'), /aksrt_archive_get_render_config\(\['context' => 'category'\]\)/);
|
||||
assert.doesNotMatch(config, /\b(?:list_scale|card_scale|mobile_meta)\b/);
|
||||
assert.doesNotMatch(resolver, /aksrt_archive_get_list_style_settings/);
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ test('QR generator is bundled locally; no external QR CDN in front-end sources',
|
||||
// 前端引用面不允许出现外部二维码 CDN。
|
||||
const sources = [
|
||||
read('assets', 'js', 'credits.js'),
|
||||
read('assets', 'js', 'credits-affiliate.js'),
|
||||
read('inc', 'helpers', 'user-center-renderers', 'profile-revenue.php'),
|
||||
read('src', 'Core', 'ScriptsPart1b.php'),
|
||||
].join('\n')
|
||||
@@ -53,7 +54,7 @@ test('revenue detail card is a four-row info list with no standalone QR block',
|
||||
// 返佣详解标题走可配置字段,不再硬编码。
|
||||
assert.match(template, /esc_html\(\$details_title\)/)
|
||||
|
||||
const script = read('assets', 'js', 'credits.js')
|
||||
const script = read('assets', 'js', 'credits-affiliate.js')
|
||||
// 独立二维码绘制函数已删除;window.qrcode 仅供海报使用。
|
||||
assert.doesNotMatch(script, /function renderAffiliateQr/)
|
||||
assert.doesNotMatch(script, /renderAffiliateQr\(/)
|
||||
@@ -111,7 +112,7 @@ test('poster canvas is built from admin-configured copy and gated on the poster
|
||||
assert.match(attribution, /'referrer_name' => \$user instanceof WP_User \? \$user->display_name/)
|
||||
assert.match(attribution, /'site_name' => get_bloginfo\('name'\)/)
|
||||
|
||||
const script = read('assets', 'js', 'credits.js')
|
||||
const script = read('assets', 'js', 'credits-affiliate.js')
|
||||
// 海报仅在开关开启时暴露按钮;推荐人昵称也由 REST 下发。
|
||||
assert.match(script, /initAffiliatePoster\(root, url \|\| j\.url \|\| '', j\.poster \|\| \{\}, j\.site_name \|\| document\.title, j\.referrer_name \|\| ''\)/)
|
||||
assert.match(script, /poster\.enabled !== true\) return/)
|
||||
@@ -136,7 +137,7 @@ test('poster canvas is built from admin-configured copy and gated on the poster
|
||||
})
|
||||
|
||||
test('poster canvas layers match the reference rebate branch 1:1', () => {
|
||||
const script = read('assets', 'js', 'credits.js')
|
||||
const script = read('assets', 'js', 'credits-affiliate.js')
|
||||
const body = script.slice(script.indexOf('function buildPosterCanvas'), script.indexOf('// cover 裁切'))
|
||||
|
||||
// 参考主题 rebate 分支用 drawTextOverflow 的换行/截断,不是自研 wrapText。
|
||||
@@ -196,7 +197,7 @@ test('poster modal markup is a 1:1 replica of the reference poster-share dialog'
|
||||
|
||||
test('poster modal shell is hidden at rest so it cannot block the whole page', () => {
|
||||
const modal = read('inc', 'helpers', 'user-center-renderers', 'affiliate-poster-modal.php')
|
||||
const script = read('assets', 'js', 'credits.js')
|
||||
const script = read('assets', 'js', 'credits-affiliate.js')
|
||||
const css = read('assets', 'css', 'routes', 'user-center', 'credits.css')
|
||||
|
||||
// 标记、JS 查询与 CSS 钩子必须共用同一个属性,缺一即失效:
|
||||
@@ -229,12 +230,15 @@ test('invite-code input verifies live against the public REST endpoint', () => {
|
||||
assert.match(script, /\/wp-json\/aksrt\/v1\/invite-code\/verify\?code=/)
|
||||
})
|
||||
|
||||
test('qrcode asset is enqueued only for profile + affiliate and credits depends on it', () => {
|
||||
test('affiliate posters declare the local QR dependency independently of withdrawal history', () => {
|
||||
const scripts = read('src', 'Core', 'ScriptsPart1b.php')
|
||||
assert.match(scripts, /assets\/js\/vendor\/qrcode\.min\.js/)
|
||||
assert.match(scripts, /'aksrt-qrcode'/)
|
||||
// credits.js 的依赖数组必须在 enqueue 调用内合并 QR 库句柄。
|
||||
assert.match(scripts, /array_merge\(\['loader_js', 'aksrt-vip-coordinator'\], \$affiliate_deps\)/)
|
||||
// 海报模块显式依赖本地 QR;提现历史无需 QR。
|
||||
// credits.js 经收银台间接使用 QR,不能把这条支付依赖误认成海报依赖。
|
||||
assert.match(scripts, /\['credits-affiliate' => \['loader_js', 'aksrt-qrcode'\]\]/)
|
||||
assert.match(scripts, /'aksrt-credits'[\s\S]*?\['loader_js', 'aksrt-vip-coordinator', 'aksrt-pay-cashier'\]/)
|
||||
assert.doesNotMatch(scripts, /'credits-withdraw-history' => \['loader_js', 'aksrt-qrcode'\]/)
|
||||
})
|
||||
|
||||
test('client IP honours the trusted-proxy filter before CDN headers', () => {
|
||||
|
||||
@@ -282,8 +282,12 @@ final class WidgetSystemTest {
|
||||
aksrt_test_reset_state();
|
||||
$controller = new RestController();
|
||||
$controller->register_routes();
|
||||
$this->same(2, count($GLOBALS['aksrt_test_state']['routes']), 'REST registers items and quote routes');
|
||||
$route = $GLOBALS['aksrt_test_state']['routes']['aksrt/v1/widgets/(?P<widget>[a-z][a-z0-9_]*)/items'];
|
||||
$this->same(1, count($GLOBALS['aksrt_test_state']['routes']), 'REST registers only the widget items route');
|
||||
$items_route = 'aksrt/v1/widgets/(?P<widget>[a-z][a-z0-9_]*)/items';
|
||||
$route = $GLOBALS['aksrt_test_state']['routes'][$items_route] ?? [];
|
||||
$this->true(isset($GLOBALS['aksrt_test_state']['routes'][$items_route]), 'items route remains registered');
|
||||
$this->false(isset($GLOBALS['aksrt_test_state']['routes']['aksrt/v1/widgets/quote']), 'obsolete quote REST route is removed');
|
||||
$this->false(method_exists($controller, 'get_quote'), 'obsolete quote REST callback is removed');
|
||||
$this->same('__return_true', $route['permission_callback'], 'public items route is read-only accessible');
|
||||
$this->true($controller->validate_widget('posts'), 'known widget validates');
|
||||
$this->false($controller->validate_widget('shop_products'), 'unknown/shop widget is rejected');
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
const path = require('node:path');
|
||||
|
||||
test('the public loader version follows every runtime JS mtime and falls back when none exist', () => {
|
||||
const harness = path.join(__dirname, 'runtime-assets-behavior-harness.php');
|
||||
const result = JSON.parse(execFileSync('php', [harness], { encoding: 'utf8' }));
|
||||
|
||||
// Initial tree: main.min.js is newest. Then only main.min.js changes, then
|
||||
// a leaf module changes, and finally the runtime tree is empty.
|
||||
assert.deepEqual(result.versions, result.expected);
|
||||
assert.ok(Number(result.versions[1]) > Number(result.versions[0]));
|
||||
assert.ok(Number(result.versions[2]) > Number(result.versions[1]));
|
||||
assert.equal(result.versions[3], 'theme-fallback');
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/** Exercise ScriptsPart1c's _win runtime version with an isolated asset tree. */
|
||||
declare(strict_types=1);
|
||||
|
||||
define('ABSPATH', __DIR__ . DIRECTORY_SEPARATOR);
|
||||
define('AKSRT_VERSION', 'theme-fallback');
|
||||
define('AKSRT_URI', 'https://example.test/theme');
|
||||
|
||||
$GLOBALS['runtime_asset_fixture_root'] = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'aksrt-runtime-assets-' . bin2hex(random_bytes(6));
|
||||
$GLOBALS['runtime_asset_localized'] = [];
|
||||
mkdir($GLOBALS['runtime_asset_fixture_root'] . '/assets/js/runtime', 0777, true);
|
||||
|
||||
function get_template_directory(): string { return $GLOBALS['runtime_asset_fixture_root']; }
|
||||
function get_template_directory_uri(): string { return 'https://example.test/theme'; }
|
||||
function wp_enqueue_script(...$args): void { unset($args); }
|
||||
function wp_enqueue_style(...$args): void { unset($args); }
|
||||
function wp_register_script(...$args): void { unset($args); }
|
||||
function wp_register_style(...$args): void { unset($args); }
|
||||
function wp_localize_script(string $handle, string $object_name, array $data): bool {
|
||||
$GLOBALS['runtime_asset_localized'][$handle][$object_name] = $data;
|
||||
return true;
|
||||
}
|
||||
function __($text, $domain = null): string { return (string) $text; }
|
||||
function sanitize_key($value): string { return preg_replace('/[^a-z0-9_-]/', '', strtolower((string) $value)) ?? ''; }
|
||||
function home_url(string $path = '/'): string { return 'https://example.test' . $path; }
|
||||
function admin_url(string $path = ''): string { return 'https://example.test/wp-admin/' . $path; }
|
||||
function wp_login_url(): string { return 'https://example.test/login'; }
|
||||
function wp_registration_url(): string { return 'https://example.test/register'; }
|
||||
function wp_lostpassword_url(): string { return 'https://example.test/lost-password'; }
|
||||
function rest_url(string $path = ''): string { return 'https://example.test/wp-json/' . $path; }
|
||||
function esc_url_raw($url): string { return (string) $url; }
|
||||
function get_allowed_mime_types(): array { return ['jpg|jpeg' => 'image/jpeg']; }
|
||||
function wp_create_nonce(string $action): string { return 'nonce-' . $action; }
|
||||
function get_query_var(string $name, $default = '') { return $default; }
|
||||
function is_user_logged_in(): bool { return false; }
|
||||
function get_current_user_id(): int { return 0; }
|
||||
function wp_max_upload_size(): int { return 1048576; }
|
||||
function is_page_template(string $template = ''): bool { return false; }
|
||||
function aksrt_is_lazy(string $scope): bool { return false; }
|
||||
function aksrt_get_lazy_thumb(string $size = ''): string { return 'https://example.test/lazy.svg'; }
|
||||
function aksrt_get_default_avatar_url(): string { return 'https://example.test/avatar.png'; }
|
||||
|
||||
require dirname(__DIR__, 2) . '/src/Core/ScriptsPart1c.php';
|
||||
|
||||
class RuntimeAssetBehaviorHarness {
|
||||
use \AKSRT\Core\ScriptsPart1c;
|
||||
|
||||
public function enqueue(): void {
|
||||
$this->enqueue_public_fonts_js_and_data('https://example.test/theme', 'aksrt-public', [], false, false, false, false, AKSRT_VERSION, []);
|
||||
}
|
||||
|
||||
private function resolve_frontend_routes(): array {
|
||||
return ['orders' => '/orders', 'resources' => '/resources'];
|
||||
}
|
||||
}
|
||||
|
||||
function write_runtime_file(string $relative, int $mtime): void {
|
||||
$path = get_template_directory() . '/assets/js/runtime/' . $relative;
|
||||
if (!is_dir(dirname($path))) mkdir(dirname($path), 0777, true);
|
||||
file_put_contents($path, '// ' . $relative);
|
||||
touch($path, $mtime);
|
||||
clearstatcache(true, $path);
|
||||
}
|
||||
|
||||
function latest_runtime_version(): string {
|
||||
$GLOBALS['runtime_asset_localized'] = [];
|
||||
(new RuntimeAssetBehaviorHarness())->enqueue();
|
||||
return (string) $GLOBALS['runtime_asset_localized']['loader_js']['_win']['ver'];
|
||||
}
|
||||
|
||||
$baseTime = time() - 600;
|
||||
write_runtime_file('main.js', $baseTime + 10);
|
||||
write_runtime_file('main.min.js', $baseTime + 30);
|
||||
write_runtime_file('imgbox.js', $baseTime + 20);
|
||||
$versions = [latest_runtime_version()];
|
||||
|
||||
write_runtime_file('main.min.js', $baseTime + 40);
|
||||
$versions[] = latest_runtime_version();
|
||||
|
||||
write_runtime_file('comment-tools.js', $baseTime + 50);
|
||||
$versions[] = latest_runtime_version();
|
||||
|
||||
foreach (glob(get_template_directory() . '/assets/js/runtime/*.js') ?: [] as $file) unlink($file);
|
||||
$versions[] = latest_runtime_version();
|
||||
|
||||
foreach (glob(get_template_directory() . '/assets/js/runtime/*') ?: [] as $file) {
|
||||
if (is_dir($file)) rmdir($file);
|
||||
}
|
||||
rmdir(get_template_directory() . '/assets/js/runtime');
|
||||
rmdir(get_template_directory() . '/assets/js');
|
||||
rmdir(get_template_directory() . '/assets');
|
||||
rmdir(get_template_directory());
|
||||
|
||||
echo json_encode([
|
||||
'versions' => $versions,
|
||||
'expected' => [(string) ($baseTime + 30), (string) ($baseTime + 40), (string) ($baseTime + 50), AKSRT_VERSION],
|
||||
], JSON_THROW_ON_ERROR);
|
||||
@@ -308,8 +308,10 @@ test('payment entry points never load the removed legacy pay runtime', () => {
|
||||
assert.match(content, /data-aksrt-pay-content/);
|
||||
assert.match(scripts, /'aksrt-pay-vip'/);
|
||||
assert.match(scripts, /'aksrt-cms-download'/);
|
||||
assert.match(scripts, /\$runtime_main_path = get_template_directory\(\) \. '\/assets\/js\/runtime\/main\.min\.js'/);
|
||||
assert.match(scripts, /\$runtime_version = file_exists\(\$runtime_main_path\)/);
|
||||
// Every lazy module shares _win.ver, so changing any runtime file must
|
||||
// invalidate the URL. Watching main.min.js alone misses rebuilt leaves.
|
||||
assert.match(scripts, /\$runtime_mtimes = array_map\(\s*'filemtime',\s*glob\(get_template_directory\(\) \. '\/assets\/js\/runtime\/\*\.js'\) \?: \[\]/);
|
||||
assert.match(scripts, /\$runtime_version = \$runtime_mtimes !== \[\] \? \(string\) max\(\$runtime_mtimes\) : \$version/);
|
||||
assert.match(scripts, /'ver'\s*=> \$runtime_version/);
|
||||
});
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* 3) 弹窗底部提示句与整条 help_text 管线已移除,顶部说明 desc 保留;
|
||||
* 4) 积分/钱包余额合并为一次 LedgerService::points_and_balance();
|
||||
* 5) 访问统计写库移到 shutdown,wp_head 只做访客判定与 setcookie;
|
||||
* 6) header-userbox 样式仅登录用户入队。
|
||||
* 6) header-userbox 样式由公共样式包提供,避免单独重复入队。
|
||||
*/
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
@@ -129,6 +129,12 @@ test('访问统计写库移出渲染关键路径', () => {
|
||||
assert.match(recordStats, /cleanup_old_stats\(\)/);
|
||||
});
|
||||
|
||||
test('header-userbox 样式仅登录用户入队', () => {
|
||||
assert.match(scripts, /if \(is_user_logged_in\(\)\) \{\s*\$enqueue_module\('header-userbox'\);/);
|
||||
test('header-userbox styles ship once in the shared public bundle', () => {
|
||||
const builder = read('tools', 'build-public-assets.mjs');
|
||||
const bundle = read('assets', 'css', 'build', 'public.css');
|
||||
assert.strictEqual((builder.match(/'assets\/css\/frontend\/header-userbox\.css'/g) || []).length, 1);
|
||||
assert.strictEqual((bundle.match(/\/\* source: assets\/css\/frontend\/header-userbox\.css \*\//g) || []).length, 1);
|
||||
assert.match(bundle, /\.aksrt-header-userbox\s*\{/);
|
||||
assert.match(scripts, /wp_enqueue_style\(\s*'aksrt-public',[\s\S]*?\['aksrt-main'\]/);
|
||||
assert.doesNotMatch(scripts, /\$enqueue_module\('header-userbox'\)/);
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
const test = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
|
||||
const root = path.resolve(__dirname, '../..')
|
||||
const core = require(path.join(root, 'assets/widgets/admin-core.js'))
|
||||
const admin = fs.readFileSync(path.join(root, 'assets/widgets/admin.js'), 'utf8')
|
||||
const controller = fs.readFileSync(path.join(root, 'src/WidgetSystem/AdminFormController.php'), 'utf8')
|
||||
const transfer = fs.readFileSync(path.join(root, 'src/WidgetSystem/TransferController.php'), 'utf8')
|
||||
const renderer = fs.readFileSync(path.join(root, 'src/WidgetSystem/FormRenderer.php'), 'utf8')
|
||||
const styles = fs.readFileSync(path.join(root, 'assets/widgets/admin.css'), 'utf8')
|
||||
|
||||
test('lazy widget forms resolve assigned instances across core and customizer markup', () => {
|
||||
assert.equal(core.resolveWidgetNumber('widget-aksrt_widget_posts[12][title]', 'widget-aksrt_widget_posts-12', '__i__'), '12')
|
||||
assert.equal(core.resolveWidgetNumber('', 'widget-aksrt_widget_posts-8', '__i__'), '8')
|
||||
assert.equal(core.resolveWidgetNumber('', '', '4'), '4')
|
||||
assert.equal(core.resolveWidgetNumber('', '', '__i__'), '')
|
||||
assert.equal(core.resolveWidgetNumber('', 'widget-aksrt_widget_posts-0', ''), '')
|
||||
assert.match(admin, /customize-control-widget_form \.aksrt-widget-form-lazy/)
|
||||
})
|
||||
|
||||
test('lazy form AJAX failures remain retryable and instance filters match WordPress widget forms', () => {
|
||||
assert.match(admin, /data\('loading', false\)/)
|
||||
assert.match(controller, /apply_filters\('widget_form_callback', \$instance, \$widget\)/)
|
||||
})
|
||||
|
||||
test('sidebar toolbars open one contextual import, export, or template dialog', () => {
|
||||
assert.match(admin, /widgets-holder-wrap:not\(#available-widgets\)/)
|
||||
assert.match(admin, /control-section-sidebar/)
|
||||
assert.match(admin, /data-aksrt-transfer-action/)
|
||||
assert.match(admin, /data-sidebar-id/)
|
||||
assert.match(admin, /data-transfer-tab/)
|
||||
assert.match(transfer, /aksrt-widget-transfer-modal__dialog/)
|
||||
assert.match(transfer, /aksrt_widgets_template_preview/)
|
||||
assert.doesNotMatch(transfer, /aksrt-widget-transfer__grid/)
|
||||
assert.doesNotMatch(transfer, /function sidebar_select/)
|
||||
assert.match(admin, /aksrt-widget-customizer-remove/)
|
||||
})
|
||||
|
||||
test('widget fields follow the current CSF compact palette and cloneable group patterns', () => {
|
||||
assert.match(renderer, /aksrt-widget-palette-choice/)
|
||||
assert.match(renderer, /<details class="aksrt-widget-repeater__row"/)
|
||||
assert.match(renderer, /data-min-items=/)
|
||||
assert.match(admin, /function updateRepeaterControls/)
|
||||
assert.match(styles, /\.aksrt-widget-choices--palette/)
|
||||
assert.match(styles, /\.aksrt-widget-repeater__bar/)
|
||||
assert.match(styles, /\.customize-control-widget_form \.aksrt-widget-field__label/)
|
||||
assert.match(admin, /if \(!response\?\.success \|\| !response\.data\) \{/)
|
||||
assert.match(admin, /responseJSON\?\.data\?\.message/)
|
||||
assert.doesNotMatch(admin, /if \(!response\?\.success\) throw new Error/)
|
||||
})
|
||||
@@ -0,0 +1,139 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const vm = require('node:vm');
|
||||
const test = require('node:test');
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
|
||||
class Element {
|
||||
constructor(tag = 'div') {
|
||||
this.tagName = tag.toUpperCase();
|
||||
this.children = [];
|
||||
this.dataset = {};
|
||||
this.attributes = {};
|
||||
this.disabled = false;
|
||||
this.hidden = false;
|
||||
this.listeners = {};
|
||||
this._textContent = '';
|
||||
this.className = '';
|
||||
this.classList = {
|
||||
values: new Set(),
|
||||
add: (...values) => values.forEach((value) => this.classList.values.add(value)),
|
||||
};
|
||||
}
|
||||
|
||||
set textContent(value) {
|
||||
this._textContent = String(value);
|
||||
this.children = [];
|
||||
}
|
||||
|
||||
get textContent() {
|
||||
return this._textContent + this.children.map((child) => child.textContent || '').join('');
|
||||
}
|
||||
|
||||
appendChild(child) {
|
||||
this.children.push(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
addEventListener(type, callback) {
|
||||
this.listeners[type] = callback;
|
||||
}
|
||||
|
||||
setAttribute(name, value) {
|
||||
this.attributes[name] = String(value);
|
||||
}
|
||||
|
||||
closest(selector) {
|
||||
return selector === '[data-aksrt-withdraw-history-page]' && this.dataset.aksrtWithdrawHistoryPage
|
||||
? this
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
function textContent(node) {
|
||||
return node.textContent;
|
||||
}
|
||||
|
||||
test('withdrawal history requests paged ten-item results and renders the returned page', async () => {
|
||||
const requests = [];
|
||||
const pagePayloads = {
|
||||
1: {
|
||||
items: [{ status: 'completed', gross_amount: '10.00', amount: '9.50', withdrawal_number: 'W1', created_at: '2026-10-01', note: 'private note' }],
|
||||
page: 1,
|
||||
max_pages: 2,
|
||||
},
|
||||
2: {
|
||||
items: [{ status: 'pending', gross_amount: '20.00', amount: '20.00', withdrawal_number: 'W2', created_at: '2026-10-02' }],
|
||||
page: 2,
|
||||
max_pages: 2,
|
||||
},
|
||||
};
|
||||
const list = new Element();
|
||||
const pagination = new Element('nav');
|
||||
const closeButtons = [];
|
||||
const modal = new Element();
|
||||
modal.querySelector = (selector) => selector === '[data-aksrt-withdraw-history-list]' ? list : pagination;
|
||||
modal.querySelectorAll = () => closeButtons;
|
||||
const openButton = new Element('button');
|
||||
const document = {
|
||||
body: { classList: { add() {}, remove() {} } },
|
||||
querySelector: (selector) => selector === '[data-aksrt-withdraw-history-modal]' ? modal : null,
|
||||
querySelectorAll: (selector) => selector === '[data-aksrt-withdraw-history-open]' ? [openButton] : [],
|
||||
createElement: (tag) => new Element(tag),
|
||||
createTextNode: (text) => ({ textContent: String(text) }),
|
||||
};
|
||||
let init;
|
||||
const jQuery = () => ({
|
||||
off() { return this; },
|
||||
on(_event, callback) { init = callback; return this; },
|
||||
});
|
||||
const fetch = (url) => {
|
||||
requests.push(url);
|
||||
const page = Number(new URL(url).searchParams.get('page'));
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(pagePayloads[page]),
|
||||
});
|
||||
};
|
||||
const source = fs.readFileSync(path.join(root, 'assets/js/credits-withdraw-history.js'), 'utf8');
|
||||
|
||||
vm.runInNewContext(source, {
|
||||
window: { aksrtData: { restUrl: 'https://example.test/wp-json/aksrt/v1', restNonce: 'nonce' } },
|
||||
document,
|
||||
jQuery,
|
||||
fetch,
|
||||
Array,
|
||||
Number,
|
||||
String,
|
||||
Math,
|
||||
parseInt,
|
||||
Promise,
|
||||
Error,
|
||||
});
|
||||
|
||||
assert.equal(typeof init, 'function', 'module registers its lifecycle initializer');
|
||||
init();
|
||||
openButton.listeners.click({ preventDefault() {} });
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
assert.equal(requests.length, 1);
|
||||
assert.equal(new URL(requests[0]).pathname, '/wp-json/aksrt/v1/earnings/withdrawals');
|
||||
assert.equal(new URL(requests[0]).searchParams.get('page'), '1');
|
||||
assert.equal(new URL(requests[0]).searchParams.get('per_page'), '10');
|
||||
assert.match(textContent(list.children[0]), /实际到账:9\.50元/);
|
||||
assert.doesNotMatch(textContent(list.children[0]), /private note/);
|
||||
|
||||
const pageTwo = pagination.children.find((button) => button.dataset.aksrtWithdrawHistoryPage === '2');
|
||||
assert.ok(pageTwo, 'the API max_pages value creates a second-page control');
|
||||
pagination.listeners.click({ target: pageTwo, preventDefault() {} });
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
assert.equal(requests.length, 2);
|
||||
assert.equal(new URL(requests[1]).searchParams.get('page'), '2');
|
||||
assert.equal(new URL(requests[1]).searchParams.get('per_page'), '10');
|
||||
assert.match(textContent(list.children[0]), /W2/);
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('article and modern widget yiyan use the reference bilingual quote source and interactions', () => {
|
||||
const helper = read('inc/helpers/aksrt-yiyan.php');
|
||||
const client = read('assets/js/yiyan.js');
|
||||
const widgetClient = read('assets/widgets/frontend.js');
|
||||
const renderer = read('src/WidgetSystem/Renderers/GeneralRenderer.php');
|
||||
const rest = read('src/WidgetSystem/RestController.php');
|
||||
const schema = read('src/WidgetSystem/WidgetSchemas.php');
|
||||
const widgetStyles = read('assets/widgets/frontend.css');
|
||||
const catalog = read('yiyan/qv-yiyan.txt').split(/\r?\n/).filter(Boolean);
|
||||
|
||||
assert.match(helper, /WP_CONTENT_DIR\s*\.\s*'\/yiyan\.txt'/);
|
||||
assert.match(helper, /\/yiyan\/qv-yiyan\.txt/);
|
||||
assert.match(helper, /wp_ajax_nopriv_aksrt_yiyan/);
|
||||
assert.match(helper, /'en'\s*=>\s*\$lines\[\$i\],\s*'cn'\s*=>\s*\$lines\[\$i\s*\+\s*1\]/);
|
||||
assert.match(helper, /class="yiyan"><div class="en"><\/div><div class="cn">/);
|
||||
assert.match(client, /REFRESH_MS\s*=\s*30000/);
|
||||
assert.match(client, /on\('auto_fun\.aksrt-yiyan', function \(\) \{\s*init\(document\)/);
|
||||
assert.match(client, /event\.key === 'Enter' \|\| event\.key === ' '/);
|
||||
assert.match(widgetClient, /window\.AksrtYiyan\.init\(figure\)/);
|
||||
assert.match(renderer, /aksrt_get_yiyan_markup\(\['class' => 'aksrt-widget-quote'\]\)/);
|
||||
assert.doesNotMatch(rest, /v1\.hitokoto\.cn|function get_quote/);
|
||||
assert.match(schema, /case 'quote':\s*return \[\];/);
|
||||
assert.match(widgetStyles, /\.aksrt-widget-quote \.yiyan \.en/);
|
||||
assert.ok(catalog.length > 10, 'the bundled quote file should contain a useful quote set');
|
||||
assert.equal((catalog.length - 1) % 2, 0, 'after the title, quote lines must form English/Chinese pairs');
|
||||
});
|
||||
@@ -71,7 +71,6 @@ const runtimePreludeScripts = [
|
||||
'assets/js/core/comment-move-form.js',
|
||||
'assets/js/core/ajax-list.js',
|
||||
'assets/js/core/pagination.js',
|
||||
'assets/js/core/archive.js',
|
||||
'assets/js/core/footer-tabbar.js',
|
||||
'assets/js/core/misc.js',
|
||||
'assets/js/core/theme.js',
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
双语一言-倾微科技
|
||||
Nothing is more terrible than ignorance in action.
|
||||
最可怕的事莫过于无知而行动
|
||||
The wise man is always a good listener.
|
||||
智慧比财富更宝贵
|
||||
The greatest test of courage on earth is to bear defeat without losing heart.
|
||||
世界上对勇气的最大考验是忍受失败而不丧失信心
|
||||
A man's best friends are his ten fingers.
|
||||
人最好的朋友是自己的十个手指
|
||||
Only they who fulfill their duties in everyday matters will fulfill them on great occasions.
|
||||
只有在日常生活中尽责的人才会在重大时刻尽责
|
||||
The shortest way to do many things is to only one thing at a time.
|
||||
做许多事情的捷径就是一次只做一件一件事
|
||||
There's only one corner of the universe you can be sure of improving, and that's your own self.
|
||||
这个宇宙中只有一个角落你肯定可以改进,那就是你自己
|
||||
The world is like a mirror: Frown at itand it frowns at you; smile, and it smiles too.
|
||||
世界犹如一面镜子:朝它皱眉它就朝你皱眉,朝它微笑它也吵你微笑
|
||||
Death comes to all, but great achievements raise a monument which shall endure until the sun grows old.
|
||||
死亡无人能免,但非凡的成就会树起一座纪念碑,它将一直立到太阳冷却之时
|
||||
The reason why a great man is great is that he resolves to be a great man.
|
||||
伟人之所以伟大,是因为他立志要成为伟大的人
|
||||
pencil and a dream can take you anywhere.
|
||||
拿起笔,写下你的梦想,你的人生就从此刻起航
|
||||
It is during our darkest moments that we must focus to see the light.
|
||||
越是在艰难困苦的时候,我们越是要看到希望
|
||||
Judge each day not by the harvest you reap but by the seeds you plant.
|
||||
不要问自己收获了多少果实,而是要问自己今天播种了多少种子
|
||||
The best way out is always through.
|
||||
一路走到底,你就会发现那个最佳出口
|
||||
The questions you ask determine the quality of your life.
|
||||
你生活的品质取决于你所提出的问题
|
||||
Believe you can and you’re halfway there.
|
||||
相信自己,你也就成功了一半
|
||||
Change your thoughts and you change your world.
|
||||
改变你的思想,你就能改变自己的命运
|
||||
It is by acts and not by ideas that people live.
|
||||
行动是根本,想法锦上添花
|
||||
Happiness is not something you postpone for the future; it is something you design for the present.
|
||||
幸福不应该留到未来品尝,幸福是你专门为当下的自己所准备的
|
||||
Mankind is made great or little by its own will.
|
||||
一个人伟大或渺小,取决于他的意志力
|
||||
Put your heart, mind, and soul into even your smallest acts. This is the secret of success.
|
||||
即便是再微小不过的事情,你也要用心去做。这就是成功的秘密
|
||||
Give light and people will find the way.
|
||||
照亮前方的路,路就会被找到
|
||||
There are two ways of spreading light: to be the candle or the mirror that reflects it.
|
||||
传递光亮有两种方式:成为一支蜡烛或当一面镜子
|
||||
You must learn a new way to think before you can master a new way to be.
|
||||
在掌握新方法之前,你必须要先换一种思考方法
|
||||
Put blinders on to those things that conspire to hold you back, especially the ones in your own head.
|
||||
不要去想那些阻碍你的事,尤其是那些自己想象出来的事
|
||||
Making the absolute best of ourselves is not an easy task. It is a pleasurable pursuit...but it requires patience, persistence, and perseverance.
|
||||
做最好的自己并不容易,这是很美好的愿望,需要耐心、坚持和毅力
|
||||
To do the useful thing, to say the courageous thing, to contemplate the beautiful thing: that’s enough for one man’s life.
|
||||
做有用的事,说勇敢的话,想美好的事,一生足矣
|
||||
If you don't enjoy your life, sorrow, sadness, fear, shame and guilt will.
|
||||
如果你不好好享受生活,你的悲伤、难过、害怕、羞愧和内疚会代替你享受
|
||||
Whatever I believed, I did; and whatever I did, I did with my whole heart and mind.
|
||||
凡是我相信的,我都做了;凡是我做了的事,都是全身心地投入去做的
|
||||
Nothing in the world is difficult if you put your heart into it.
|
||||
世上无难事,只怕有心人
|
||||
People are not just to love and live.
|
||||
人不是仅仅为了爱而生存的
|
||||
Only difficult to make people show their true colors.
|
||||
只有困难才能使人显出自己的本色
|
||||
Suffer all the pain can destroy a person, but it also can kill the pain.
|
||||
一切痛苦能够毁灭人,然而受苦的人也能把痛苦消灭
|
||||
Smash the waves would rather get in the way of the reef hill, also not willing to take a step back.
|
||||
海浪宁可在挡路的礁山上撞得粉碎,也不肯后退一步
|
||||
To the path of timber, iron and steel will never regret bright spray of molten steel was abandoned.
|
||||
为了走上成材的道路,钢铁决不惋惜璀璨的钢花被遗弃
|
||||
Flat rich prosperous time in vain to develop a group of coward, hardship is the mother of strong forever.
|
||||
平富足的盛世徒然养成一批懦夫,困苦永远是坚强之母
|
||||
In the face of difficulties, be brave, persistent and tirelessly to overcome it.
|
||||
面对困难的时候,要勇敢、执着、不畏艰辛地去战胜它
|
||||
You have to work very hard to seem effortless.
|
||||
你必须十分努力,才能看上去毫不费劲
|
||||
Life, there will always be the unexpected warmth and the endless hope.
|
||||
人生,总会有不期而遇的温暖,和生生不息的希望
|
||||
Not afraid of people blocking, I'm afraid their surrender.
|
||||
不怕万人阻挡,只怕自己投降
|
||||
No matter when you start, it is important not to stop after the start.
|
||||
无论你在什么时候开始,重要的是开始之后就不要停止
|
||||
Forever facing sunlight, so you can not see the shadow of the.
|
||||
永远面向阳光,这样你就看不见阴影了
|
||||
Every day has not danced, all are life's disappointment.
|
||||
每一个不曾起舞的日子,都是对生命的辜负
|
||||
The future you will certainly thank yourself now desperately.
|
||||
未来的你一定会感谢现在拼命的自己
|
||||
Don't give up just because of what people said. Use that as your motivation to push harder.
|
||||
别因为别人说的话而放弃,把那些话当做加倍努力的动力
|
||||
The God only arranges a happy ending. If it is not happy, it means that it is not the final result.
|
||||
上天只会安排的快乐的结局。如果不快乐,说明还不是最后结局
|
||||
Be happy for this moment, this moment is your life.
|
||||
享受当下的快乐,因为这一刻正是你的人生
|
||||
Don’t worry about what others are doing better than you. Concentrate on beating your own records every day.
|
||||
不要担心别人会做得比你好。你只需要每天都做得比前一天好就可以了
|
||||
Success is a battle between YOU and YOURSELF only.
|
||||
成功是一场和自己的比赛
|
||||
anything that adds laughter and joy to your life.
|
||||
不要延迟任何可以给你的生活带来欢笑与快乐的事情
|
||||
Life is like a cup of tea. It won't be bitter for a lifetime but for a short while anyway.
|
||||
人生就像一杯茶,不会苦一辈子,但总会苦一阵子
|
||||
Many people start a career with a dream, then get busy forgetting it.
|
||||
很多人一开始为了梦想而忙,后来忙得忘了梦想
|
||||
Fight for the things you love no matter what you may face, it will be worth it.
|
||||
不管你面对的是什么,为你所爱的而奋斗都会是值得的
|
||||
Everyone has its disadvantage just like the god bites the apple. the bigger disadvantage you have, the more the god appreciate it.
|
||||
每个人都会有缺陷,就像被上帝咬过的苹果,有的人缺陷比较大,正是因为上帝特别喜欢他的芬芳
|
||||
Remember happiness doesn't depend upon who you are or what you have; it depends solely on what you think.
|
||||
幸福不在于你是谁,你拥有什么,而仅仅在于你自己怎么看待
|
||||
Those who fly solo have the strongest wings.
|
||||
那些单独飞翔的人拥有最强大的翅膀
|
||||
The best things in life are free.
|
||||
生活中最美好的事都是免费的
|
||||
Don't look back, you're not going that way.
|
||||
别回头,你要走的不是那条路
|
||||
Sometimes, we are not waiting for somebody or something. We are waiting to be changed as time goes by.
|
||||
有时候,我们并不是在等什么人或什么事。我们只是在静待岁月改变自己
|
||||
The worst sort of indolence is being given a choice, yet taking no initiative to change.
|
||||
我们人生中最大的懒惰,就是当我们明知自己拥有作出选择的能力,却不去主动改变而是放任它的生活态度
|
||||
Your dream is like a flower. if you water it patiently, the flower will come out beautifully.
|
||||
即使是最简单的梦想,用心浇灌,也能开出绚烂的花
|
||||
Sometimes, you have to make your own happy ending.
|
||||
有时候,只能靠自己书写自己的美好结局
|
||||
Have faith in your dreams and someday your rainbow will come smiling through.
|
||||
请对梦想充满信心,总有一天属于你的彩虹会在天空微笑
|
||||
When we learn to treasure simple happiness then we will be winners in life.
|
||||
当我们懂得珍惜平凡的幸福时,就已经成了人生的赢家
|
||||
Stop to have a rest, do not forget others still in the running.
|
||||
停下来休息的时候,不要忘记别人还在奔跑
|
||||
This moment is your life.
|
||||
活在当下
|
||||
The sacrifices you make today will pay dividends in the future.
|
||||
今天的牺牲和努力未来都会有回报
|
||||
When you procrastinate, you become a slave to yesterday.
|
||||
拖延会让你成为昨天的奴隶
|
||||
Failures are only lessons.
|
||||
失败只是成长的课堂
|
||||
Behind every beautiful life, there has been some kind of pain.
|
||||
破茧成蝶的美好生活都有伤痛
|
||||
Time and experience heals pain.
|
||||
时间和经历会抚平一切伤痛
|
||||
Nobody looks down on you because everybody is too busy to look at you.
|
||||
没谁瞧不起你,因为别人根本就没瞧你,大家都很忙的
|
||||
Stop cheating on your future with your past... it's over.
|
||||
别再用你的过去欺骗你的未来,过去已经过去了
|
||||
No matter what happened in the past, you have to believe that the best is yet to come.
|
||||
无论过去发生过什么,你都要相信,最好的尚未到来
|
||||
You can't wait forever. Do something and make it happen.
|
||||
你不可能永远等下去,去做点儿什么,让一切成真
|
||||
Nobody can go back and start a new beginning, but anyone can start today and make a new ending.
|
||||
没有人可以回到过去从头再来,但是每个人都可以从今天开始,创造一个全新的结局
|
||||
Happiness isn't about getting what you want all the time, it's about loving what you have.
|
||||
幸福并不是一味得到自己想要的,而是珍爱自己拥有的
|
||||
We’d better struggle for the future rather than regret for the past.
|
||||
如果后悔过去,不如奋斗将来
|
||||
It doesn't matter how slow you are, as long as you're determined to get there, you'll get there.
|
||||
不管你有多慢,都不要紧,只要你有决心,你最终都会到达想去的地方
|
||||
Life is simple.You make choices and you don't look back.
|
||||
人生很简单,做了决定就不要后悔
|
||||
I'm not lazy, I'm on energy saving mode.
|
||||
我不懒,我只是开启了节能模式
|
||||
No one can change another. But one can be the reason for another to change.
|
||||
没人能改变另一个人,但是某个人能成为一个人改变的原因
|
||||
No matter how complicated your life is, you have to maintain your elegance.
|
||||
不论生活如何复杂,总要保持自己的那一份优雅
|
||||
I only smile very joy, sorrow will not be see.
|
||||
我只有笑的很欢,忧伤才不会被看穿
|
||||
Pain changes people. However, love will finally guide them back.
|
||||
伤痛会改变一个人,但爱最终总会让你找回最初的自己
|
||||
Time such as water, always silent. If you are well, it is sunny.
|
||||
时光如水,总是无言。若你安好,便是晴天
|
||||
Seeing your adorable smile is the absolute best part of my day.
|
||||
看见你可爱的笑容绝对是我一天中最美好的事
|
||||
Don’t let your dreams be dreams.
|
||||
不要让你的梦想只是想想而已
|
||||
Nothing can't be figured out. The past just can't be reached again.
|
||||
没有什么过不去,只是再也回不去
|
||||
No matter what label is thrown your way, only you can define your self.
|
||||
不管你被贴上什么标签,只有你才能定义你自己
|
||||
Aim for the moon. If you miss, you may hit a star.
|
||||
把月亮作为你的目标。如果你没打中,也许你还能打中星星
|
||||
May you live like a child forever.
|
||||
愿你永远活的像个孩子
|
||||
Don’t hurry say have no choice, perhaps, next intersection will meet hope.
|
||||
不要急着说别无选择,也许、下个路口就会遇见希望
|
||||
Worrying does not empty tomorrow of its troubles, it empties today of its strength.
|
||||
担忧不会清空明日的烦恼,它只会丧失今日的勇气
|
||||
May we all have the power to love ourselves and others.
|
||||
愿我们,都有能力爱自己,有余力爱别人
|
||||
People do a lot of thinking, and sometimes, that's what kills us.
|
||||
有时候是我们自己想太多才让自己如此难受
|
||||
If you don’t try, you’ll never know. So try.
|
||||
如果你不去试,你永远也不知道结果,所以去试试吧
|
||||
I may not be perfect but at least I’m not fake.
|
||||
我可能不完美,但是我至少不虚伪
|
||||
I love you for my life past.
|
||||
我爱你,爱了整整一个曾经
|
||||
Youth means limitless possibilities.
|
||||
年轻就是无限的可能
|
||||
Life is the flower for which love is the honey.
|
||||
生命如花,爱情是蜜
|
||||
Time always save the best for last.
|
||||
时间总把最好的人留到最后
|
||||
The course of true love never did run smooth.
|
||||
真诚的爱情之路永不会是平坦的
|
||||
Real dream is the other shore of reality.
|
||||
真正的梦就是现实的彼岸
|
||||
better late than never.
|
||||
只要开始,虽晚不迟
|
||||
pride relates more to our opinion of ourselves, vanity to what we would have others think of us.
|
||||
骄傲多半涉及我们自己怎样看待自己,而虚荣则涉及我们想别人怎样看我们
|
||||
Misery can be caused by someone being just weak and indecisive.
|
||||
一个人仅仅因为软弱无能或优柔寡断就完全可能招致痛苦
|
||||
Not all of us can offord to be romantic.
|
||||
并不是我们所有的人都会拥有浪漫
|
||||
If you get tired, learn to rest, not to quit.
|
||||
如果你累了,学会休息,而不是放弃
|
||||
Work hard in silence, let success make the noise.
|
||||
在沉默中努力,让成功自己发声
|
||||
With the wonder of your love, the sun above always shines.
|
||||
拥有你美丽的爱情,太阳就永远明媚
|
||||
The best hearts are always the bravest.
|
||||
心灵最高尚的人也总是最勇敢的人
|
||||
Thank you push me off a cliff let me see the whole sky.
|
||||
感谢你将我推下悬崖让我看清整片天空
|
||||
Finger rift,twisted in the love.
|
||||
如果你为着错过夕阳而哭泣,那么你就要错群星了
|
||||
love understands love; it needs no talk.
|
||||
相爱的心息息相通,无需用言语倾诉
|
||||
Don’t let a bad day make you feel lke you have a bad lfe.
|
||||
不要让糟糕的一天让你误以为有个糟糕的人生
|
||||
May there be enough clouds in your life to make a beautiful sunset.
|
||||
愿你生命中有够多的云翳,来造成一个美丽的黄昏
|
||||
I only wish to face the sea, with spring flowers blossoming.
|
||||
我只愿面朝大海,春暖花开
|
||||
As long as there s tomorrow, today s always the startng lne.
|
||||
只要还有明天,今天就永远是起跑线
|
||||
To beleve , each day the sun wll not lve up to ther own smle.
|
||||
要坚信,每一天的阳光都不会辜负自己的笑容
|
||||
I try to give up the dream just a dream.
|
||||
努力了才叫梦想
|
||||
Do not let dream just be your dream.
|
||||
别让梦想只停留在梦里
|
||||
If you never chase your dream, you will never catch them.
|
||||
若不去追逐梦想,你将永远无法抓住梦想
|
||||
When your faith is stronger than your fears, you can make your dreams happen.
|
||||
当你的信念强于你的胆怯时,你就可以将梦想变为现实了
|
||||
Sometimes a winner is just a dreamer that never gives up.
|
||||
有时候,成功者只是坚持梦想不放弃的人
|
||||
No matter how you feel, get up, dress up and fight for your dreams.
|
||||
无论你现在感觉如何,请起床、穿好衣服然后为你的梦想而奋斗
|
||||
Someone to love, something to do, and something to hope for.
|
||||
有爱的人,有喜欢的事业,有梦想
|
||||
Life must be lived with love, happiness, and dreams.
|
||||
人生一定要有爱,有快乐,有梦想
|
||||
Never give up your dreams. Miracles happen everyday.
|
||||
别放弃梦想,奇迹每天都在上演
|
||||
The only thing standing between you and your dreams is a lot of hard work.
|
||||
横跨在你和你的梦想之间的唯一的东西就是奋力拼搏
|
||||
If we dream, everything is possible.
|
||||
敢于梦想,一切都将成为可能
|
||||
Like a child, always believe in hope, I believe the dream.
|
||||
像孩子一样,永远相信希望,相信梦想
|
||||
We are the tender love in the world.
|
||||
我们都在被这个世界温柔的爱着
|
||||
A good idea without action is worth nothing.
|
||||
如果没有切实执行,再好的点子也是徒劳
|
||||
Do not give in to fear
|
||||
别在恐惧面前低下你的头
|
||||
things never change, we change.
|
||||
世界并没有变,改变的是我们
|
||||
Sometimes you have to be your own hero.
|
||||
有时候必须做自己的英雄
|
||||
All things in their being are good for something.
|
||||
天生我才必有用
|
||||
I can accept failure but I can’t accept not trying.
|
||||
可以接受暂时的失败,但绝对不能接受未曾奋斗过的自己
|
||||
Everyone there is a part of the living for others their own.
|
||||
每个人都存在着那部分为别人而活的自己
|
||||
Love begins with a smile, grows with a kiss and ends with a tear.
|
||||
爱,起于微笑,浓于亲吻,逝于泪水
|
||||
More grow up more lonely, more grow up more uneasy.
|
||||
越长大越孤单 ,越长大越不安
|
||||
Dream most deep place, only then the smile is not tired.
|
||||
梦的最深处,只有微笑不累
|
||||
Hard-working is actually a cool thing.
|
||||
努力学习其实是一件很酷的事
|
||||
If you don’t try, you’ll never know. So try.
|
||||
如果你不去试,你永远也不知道结果,所以去试试吧
|
||||
Only his strong enough, will not be trampled.
|
||||
只有自己足够强大,才不会被别人践踏
|
||||
If we believe that tomorrow will be better, we can bear a hardship today.
|
||||
如果我们相信明天会更好,今天就能承受艰辛
|
||||
Every day is beautiful if you choose to see it.
|
||||
如果你愿意去发现,其实每一天都很美
|
||||
Live every day as the last day of life.
|
||||
把活着的每一天看作生命的最后一天
|
||||
I lose, I lose, but I never give up.
|
||||
我输过,我败过,但我从未放弃过
|
||||
Never say die.
|
||||
永不言弃
|
||||
You see what you believe.
|
||||
一个人相信什么,就会看见什么
|
||||
I am ordinary yet unique.
|
||||
我很平凡,但我独一无二
|
||||
So what if we fall down? At least we are still young.
|
||||
摔倒了又怎样,至少我们还年轻
|
||||
Turn your face to the sun and the shadows fall behind you.
|
||||
永远面向阳光,这样你就看不见阴影了
|
||||
I am the luckiest person in the world.
|
||||
我是世界上最幸运的人
|
||||
Happiness will never miss any people, sooner or later it will find you.
|
||||
幸福不会遗漏任何人,迟早有一天它会找到你
|
||||
Little compliments mean so much to me sometimes.
|
||||
有时候,一点微不足道的肯定,对我却意义非凡
|
||||
If you hold tight, how can a free hand to hug now?
|
||||
你若将过去抱的太紧,怎么能腾出手来拥抱现在?
|
||||
Do not find excuses for failure, to chase success reasons.
|
||||
不要找失败的借口,去追成功的理由
|
||||
Wish my smile clear off the sky, of all days.
|
||||
微笑可以晴朗所有的天
|
||||
Life is never easier, we just get stronger.
|
||||
生活从未变得容易,只是我们变得更加坚强
|
||||
Be happy. No worries, just smile.
|
||||
开心点,别担心,微笑就好
|
||||
Reference in new issue
Block a user