Merge pull request 'Worktree/widgets zibll parity' (#23) from worktree/widgets-zibll-parity into main
Reviewed-on: #23
This commit was merged in pull request #23.
This commit is contained in:
+52
-52
@@ -3932,11 +3932,11 @@ body:has(.modal.in) .float-btn,
|
||||
}
|
||||
|
||||
/**------小工具:复合信息卡片模块--------**/
|
||||
.aksrt-combo-info-card {
|
||||
.zib-combo-info-card {
|
||||
--zci-radius: 12px;
|
||||
--zci-band-h: 80px;
|
||||
}
|
||||
.aksrt-combo-info-card .zci-card {
|
||||
.zib-combo-info-card .zci-card {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-height: 180px;
|
||||
@@ -3946,11 +3946,11 @@ body:has(.modal.in) .float-btn,
|
||||
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
||||
box-shadow: 0 1px 3px var(--main-shadow);
|
||||
}
|
||||
.aksrt-combo-info-card .zci-card:hover {
|
||||
.zib-combo-info-card .zci-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 12px 32px var(--main-shadow);
|
||||
}
|
||||
.aksrt-combo-info-card .zci-card::before {
|
||||
.zib-combo-info-card .zci-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@@ -3961,33 +3961,33 @@ body:has(.modal.in) .float-btn,
|
||||
transition: opacity 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
.aksrt-combo-info-card.zci-has-band .zci-card::before {
|
||||
.zib-combo-info-card.zci-has-band .zci-card::before {
|
||||
opacity: 1;
|
||||
}
|
||||
/* 色带配色(仅保留关键的渐变,无法用主题变量简写) */
|
||||
.aksrt-combo-info-card .zci-card.zci-band-blue::before {
|
||||
.zib-combo-info-card .zci-card.zci-band-blue::before {
|
||||
background: linear-gradient(170deg, rgba(114, 160, 255, 0.3), rgba(196, 174, 255, 0.2) 15%, transparent 60%);
|
||||
}
|
||||
.aksrt-combo-info-card .zci-card.zci-band-cyan::before {
|
||||
.zib-combo-info-card .zci-card.zci-band-cyan::before {
|
||||
background: linear-gradient(170deg, rgba(82, 196, 214, 0.3), rgba(130, 200, 255, 0.18) 15%, transparent 60%);
|
||||
}
|
||||
.aksrt-combo-info-card .zci-card.zci-band-green::before {
|
||||
.zib-combo-info-card .zci-card.zci-band-green::before {
|
||||
background: linear-gradient(170deg, rgba(92, 209, 158, 0.3), rgba(180, 230, 170, 0.2) 15%, transparent 60%);
|
||||
}
|
||||
.aksrt-combo-info-card .zci-card.zci-band-yellow::before {
|
||||
.zib-combo-info-card .zci-card.zci-band-yellow::before {
|
||||
background: linear-gradient(170deg, rgba(255, 190, 102, 0.3), rgba(255, 220, 132, 0.22) 15%, transparent 60%);
|
||||
}
|
||||
.aksrt-combo-info-card .zci-card.zci-band-pink::before {
|
||||
.zib-combo-info-card .zci-card.zci-band-pink::before {
|
||||
background: linear-gradient(170deg, rgba(255, 146, 181, 0.3), rgba(255, 190, 220, 0.2) 15%, transparent 60%);
|
||||
}
|
||||
.aksrt-combo-info-card .zci-card.zci-band-purple::before {
|
||||
.zib-combo-info-card .zci-card.zci-band-purple::before {
|
||||
background: linear-gradient(170deg, rgba(170, 140, 255, 0.36), rgba(220, 180, 255, 0.22) 15%, transparent 60%);
|
||||
}
|
||||
.aksrt-combo-info-card .zci-card.zci-band-gray::before {
|
||||
.zib-combo-info-card .zci-card.zci-band-gray::before {
|
||||
background: linear-gradient(170deg, rgba(140, 150, 170, 0.22), rgba(190, 195, 210, 0.14) 15%, transparent 60%);
|
||||
}
|
||||
/* 内容区:只控 padding 和层级,flex 布局由 HTML class 完成 */
|
||||
.aksrt-combo-info-card .zci-body {
|
||||
.zib-combo-info-card .zci-body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 20px;
|
||||
@@ -4001,7 +4001,7 @@ body:has(.modal.in) .float-btn,
|
||||
}
|
||||
|
||||
/* 图标盒子:尺寸与圆角,背景沿用卡片主色自动适配暗色 */
|
||||
.aksrt-combo-info-card .zci-icon {
|
||||
.zib-combo-info-card .zci-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 8px;
|
||||
@@ -4009,65 +4009,65 @@ body:has(.modal.in) .float-btn,
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
.aksrt-combo-info-card .zci-icon img {
|
||||
.zib-combo-info-card .zci-icon img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
/* 标题字号(font-bold / text-ellipsis-2 / mb10 已走工具类) */
|
||||
.aksrt-combo-info-card .zci-title {
|
||||
.zib-combo-info-card .zci-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
/* CTA 行动链接:仅保留颜色 + hover 行为 + 箭头动效 */
|
||||
.aksrt-combo-info-card .zci-cta {
|
||||
.zib-combo-info-card .zci-cta {
|
||||
margin-top: 20px;
|
||||
color: var(--this-color, --key-color);
|
||||
font-weight: 500;
|
||||
transition: 0.2s;
|
||||
}
|
||||
.aksrt-combo-info-card .zci-cta:hover {
|
||||
.zib-combo-info-card .zci-cta:hover {
|
||||
gap: 10px;
|
||||
color: var(--focus-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
.aksrt-combo-info-card .zci-cta .zci-arrow {
|
||||
.zib-combo-info-card .zci-cta .zci-arrow {
|
||||
transform: rotate(-45deg);
|
||||
font-size: 11px;
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
.aksrt-combo-info-card .zci-cta:hover .zci-arrow {
|
||||
.zib-combo-info-card .zci-cta:hover .zci-arrow {
|
||||
transform: rotate(-45deg) translateX(2px);
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.aksrt-combo-info-card .zci-card {
|
||||
.zib-combo-info-card .zci-card {
|
||||
min-height: 160px;
|
||||
}
|
||||
.aksrt-combo-info-card .zci-body {
|
||||
.zib-combo-info-card .zci-body {
|
||||
padding: 14px;
|
||||
}
|
||||
.col-xs-12 .zci-card .zci-body {
|
||||
padding: 20px;
|
||||
}
|
||||
.aksrt-combo-info-card .zci-title {
|
||||
.zib-combo-info-card .zci-title {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar .aksrt-combo-info-card > * {
|
||||
.sidebar .zib-combo-info-card > * {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.sidebar .aksrt-combo-info-card .zci-card {
|
||||
.sidebar .zib-combo-info-card .zci-card {
|
||||
min-height: 160px;
|
||||
}
|
||||
.sidebar .aksrt-combo-info-card .zci-body {
|
||||
.sidebar .zib-combo-info-card .zci-body {
|
||||
padding: 14px;
|
||||
}
|
||||
.sidebar .col-xs-12 .zci-card .zci-body {
|
||||
padding: 20px;
|
||||
}
|
||||
.sidebar .aksrt-combo-info-card .zci-title {
|
||||
.sidebar .zib-combo-info-card .zci-title {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
@@ -4397,7 +4397,7 @@ body:has(.modal.in) .float-btn,
|
||||
}
|
||||
|
||||
/**------小工具:横向滚动合作伙伴模块--------**/
|
||||
.aksrt-partners-scroll {
|
||||
.zib-partners-scroll {
|
||||
--zps-card-w: 220px;
|
||||
--zps-card-h: 90px;
|
||||
--zps-card-radius: 10px;
|
||||
@@ -4407,42 +4407,42 @@ body:has(.modal.in) .float-btn,
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.aksrt-partners-scroll .zps-row {
|
||||
.zib-partners-scroll .zps-row {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin-bottom: var(--zps-gap);
|
||||
}
|
||||
.aksrt-partners-scroll .zps-row:last-child {
|
||||
.zib-partners-scroll .zps-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.aksrt-partners-scroll .zps-track {
|
||||
.zib-partners-scroll .zps-track {
|
||||
display: flex;
|
||||
width: max-content;
|
||||
will-change: transform;
|
||||
animation: zps-scroll-left var(--zps-duration, 30s) linear infinite;
|
||||
animation-play-state: paused;
|
||||
}
|
||||
.aksrt-partners-scroll .zps-dir-right .zps-track {
|
||||
.zib-partners-scroll .zps-dir-right .zps-track {
|
||||
animation-name: zps-scroll-right;
|
||||
}
|
||||
.aksrt-partners-scroll .zps-track[data-zps-ready] {
|
||||
.zib-partners-scroll .zps-track[data-zps-ready] {
|
||||
animation-play-state: running;
|
||||
}
|
||||
.aksrt-partners-scroll .zps-group {
|
||||
.zib-partners-scroll .zps-group {
|
||||
gap: var(--zps-gap);
|
||||
padding-right: var(--zps-gap);
|
||||
}
|
||||
.aksrt-partners-scroll.zps-pause-hover .zps-row:hover .zps-track {
|
||||
.zib-partners-scroll.zps-pause-hover .zps-row:hover .zps-track {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
.aksrt-partners-scroll .zps-item {
|
||||
.zib-partners-scroll .zps-item {
|
||||
width: var(--zps-card-w);
|
||||
height: var(--zps-card-h);
|
||||
overflow: hidden;
|
||||
padding: 8px 10px;
|
||||
color: inherit;
|
||||
}
|
||||
.aksrt-partners-scroll .zps-img {
|
||||
.zib-partners-scroll .zps-img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: auto;
|
||||
@@ -4450,34 +4450,34 @@ body:has(.modal.in) .float-btn,
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.aksrt-partners-scroll.zps-card-bg .zps-item {
|
||||
.zib-partners-scroll.zps-card-bg .zps-item {
|
||||
background: var(--main-bg-color);
|
||||
border-radius: var(--zps-card-radius);
|
||||
}
|
||||
.aksrt-panel .aksrt-partners-scroll.zps-card-bg .zps-item {
|
||||
.aksrt-panel .zib-partners-scroll.zps-card-bg .zps-item {
|
||||
background: var(--muted-border-color);
|
||||
}
|
||||
/* 图文卡片模式 */
|
||||
.aksrt-partners-scroll.zps-style-card .zps-card-icon {
|
||||
.zib-partners-scroll.zps-style-card .zps-card-icon {
|
||||
width: calc(var(--zps-card-h) - 30px);
|
||||
height: calc(var(--zps-card-h) - 30px);
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.aksrt-partners-scroll.zps-style-card .zps-img {
|
||||
.zib-partners-scroll.zps-style-card .zps-img {
|
||||
object-fit: cover;
|
||||
border-radius: calc(var(--zps-card-radius) - 4px);
|
||||
width: 100%;
|
||||
}
|
||||
/* 纯 Logo 模式:高度固定、宽度自适应,图片保持原始比例 */
|
||||
.aksrt-partners-scroll.zps-style-logo .zps-item {
|
||||
.zib-partners-scroll.zps-style-logo .zps-item {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
justify-content: center;
|
||||
padding: 0 20px;
|
||||
}
|
||||
.aksrt-partners-scroll.zps-style-logo .zps-img {
|
||||
.zib-partners-scroll.zps-style-logo .zps-img {
|
||||
height: calc(var(--zps-card-h) - 24px);
|
||||
max-height: calc(var(--zps-card-h) - 24px);
|
||||
width: auto;
|
||||
@@ -4485,28 +4485,28 @@ body:has(.modal.in) .float-btn,
|
||||
filter: grayscale(40%);
|
||||
transition: filter 0.25s ease, transform 0.25s ease;
|
||||
}
|
||||
.aksrt-partners-scroll.zps-style-logo .zps-item:hover .zps-img {
|
||||
.zib-partners-scroll.zps-style-logo .zps-item:hover .zps-img {
|
||||
filter: grayscale(0);
|
||||
transform: scale(1.04);
|
||||
}
|
||||
/* 精简胶囊模式 */
|
||||
.aksrt-partners-scroll.zps-style-chip .zps-item {
|
||||
.zib-partners-scroll.zps-style-chip .zps-item {
|
||||
min-height: 40px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 8px 14px;
|
||||
}
|
||||
.aksrt-partners-scroll.zps-card-bg.zps-style-chip .zps-item {
|
||||
.zib-partners-scroll.zps-card-bg.zps-style-chip .zps-item {
|
||||
padding: 8px 16px 8px 10px;
|
||||
}
|
||||
.aksrt-partners-scroll.zps-style-chip .zps-chip-icon .zps-img {
|
||||
.zib-partners-scroll.zps-style-chip .zps-chip-icon .zps-img {
|
||||
height: 22px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.aksrt-partners-scroll.zps-card-bg.zps-style-chip .zps-chip-icon .zps-img {
|
||||
.zib-partners-scroll.zps-card-bg.zps-style-chip .zps-chip-icon .zps-img {
|
||||
border-radius: calc(var(--zps-card-radius) - 4px);
|
||||
}
|
||||
.aksrt-partners-scroll.zps-style-chip .zps-chip-title {
|
||||
.zib-partners-scroll.zps-style-chip .zps-chip-title {
|
||||
max-width: 180px;
|
||||
}
|
||||
.zps-end-fade {
|
||||
@@ -4551,11 +4551,11 @@ body:has(.modal.in) .float-btn,
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.aksrt-partners-scroll {
|
||||
.zib-partners-scroll {
|
||||
--zps-card-w: 160px;
|
||||
--zps-card-h: 70px;
|
||||
}
|
||||
.aksrt-partners-scroll.zps-style-logo .zps-item {
|
||||
.zib-partners-scroll.zps-style-logo .zps-item {
|
||||
padding: 0 12px;
|
||||
}
|
||||
.zps-end-fade::after {
|
||||
@@ -4563,7 +4563,7 @@ body:has(.modal.in) .float-btn,
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.aksrt-partners-scroll .zps-track {
|
||||
.zib-partners-scroll .zps-track {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -4032,7 +4032,7 @@ function partners_scroll_init() {
|
||||
}
|
||||
|
||||
function initAll() {
|
||||
document.querySelectorAll('.aksrt-partners-scroll').forEach(init);
|
||||
document.querySelectorAll('.zib-partners-scroll').forEach(init);
|
||||
}
|
||||
|
||||
initAll();
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -9,7 +9,7 @@
|
||||
'use strict';
|
||||
|
||||
var HEADING_SEL = 'h1, h2, h3, h4';
|
||||
var MIN_HEADINGS = 2;
|
||||
var MIN_HEADINGS = 3;
|
||||
var NAVBOX = '.posts-nav-box';
|
||||
var NAV_CLASS = 'posts-nav-lists';
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
function ensureId(el, index) {
|
||||
if (!el.id) {
|
||||
el.id = 'section-nav-' + index;
|
||||
el.id = 'wznav_' + index;
|
||||
}
|
||||
return el.id;
|
||||
}
|
||||
@@ -54,7 +54,7 @@
|
||||
box.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
titleHtml +
|
||||
'<div class="aksrt-panel"><div class="' +
|
||||
'<div class="zib-widget"><div class="' +
|
||||
NAV_CLASS +
|
||||
' scroll-y mini-scrollbar list-unstyled">' +
|
||||
listsHtml +
|
||||
|
||||
@@ -197,3 +197,13 @@
|
||||
.aksrt-widget-switch span::after { transition: none; }
|
||||
}
|
||||
|
||||
|
||||
/* terms: searchable multi-select with drag ordering */
|
||||
.aksrt-widget-terms .aksrt-widget-terms__selected { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }
|
||||
.aksrt-widget-terms .aksrt-widget-terms__selected:empty { display: none; }
|
||||
.aksrt-widget-terms .aksrt-widget-terms__chip { display: inline-flex; align-items: center; gap: 3px; padding: 2px 6px; border-radius: 4px; background: #f0f0f1; border: 1px solid #dcdcde; font-size: 12px; cursor: move; }
|
||||
.aksrt-widget-terms .aksrt-widget-terms__chip .dashicons { font-size: 14px; width: 14px; height: 14px; }
|
||||
.aksrt-widget-terms .aksrt-widget-terms__remove { border: 0; background: none; cursor: pointer; line-height: 1; padding: 0 0 0 2px; text-decoration: none; }
|
||||
.aksrt-widget-terms .aksrt-widget-terms__search { margin-bottom: 6px; }
|
||||
.aksrt-widget-terms .aksrt-widget-terms__options { max-height: 180px; overflow-y: auto; border: 1px solid #dcdcde; border-radius: 4px; padding: 4px 8px; background: #fff; }
|
||||
.aksrt-widget-terms .aksrt-widget-terms__option { display: block; padding: 2px 0; }
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
axis: 'y',
|
||||
update: renumberRepeaters,
|
||||
});
|
||||
$root.find('.aksrt-widget-terms__selected').sortable({
|
||||
items: '.aksrt-widget-terms__chip',
|
||||
update: () => $root.find('.aksrt-widget-terms').each((_i, el) => syncTerms(el)),
|
||||
});
|
||||
$root.find('input[type="range"]').each(function () {
|
||||
const output = this.parentElement.querySelector('output');
|
||||
if (output) this.addEventListener('input', () => { output.textContent = this.value; });
|
||||
@@ -21,6 +25,25 @@
|
||||
updatePlacementWarning($root);
|
||||
}
|
||||
|
||||
/* terms 字段:以“已选标签”为唯一真源,同步隐藏输入(选择顺序)与复选框。 */
|
||||
function syncTerms(wrap) {
|
||||
const $wrap = $(wrap);
|
||||
const ids = $wrap.find('.aksrt-widget-terms__chip').map((_i, el) => String($(el).data('term-id'))).get();
|
||||
$wrap.find('.aksrt-widget-terms__value').val(ids.join(',')).trigger('change');
|
||||
$wrap.find('.aksrt-widget-terms__option input').each(function () {
|
||||
this.checked = ids.indexOf(String(this.value)) !== -1;
|
||||
});
|
||||
}
|
||||
|
||||
function addTermChip(wrap, id, name) {
|
||||
const $selected = $(wrap).find('.aksrt-widget-terms__selected');
|
||||
if ($selected.find(`.aksrt-widget-terms__chip[data-term-id="${id}"]`).length) return;
|
||||
const chip = $(`<span class="aksrt-widget-terms__chip" data-term-id="${id}"><span class="dashicons dashicons-move" aria-hidden="true"></span></span>`);
|
||||
chip.append(document.createTextNode(name));
|
||||
chip.append('<button type="button" class="button-link-delete aksrt-widget-terms__remove" aria-label="移除">×</button>');
|
||||
$selected.append(chip);
|
||||
}
|
||||
|
||||
function updateWidgetTitle(root) {
|
||||
const $root = $(root);
|
||||
const title = String($root.find('.aksrt-widget-field[data-field-id="title"] input').first().val() || '').trim();
|
||||
@@ -117,6 +140,25 @@
|
||||
$(this).closest('.aksrt-widget-repeater__row').remove();
|
||||
renumberRepeaters.call($rows[0]);
|
||||
})
|
||||
.on('click', '.aksrt-widget-terms__remove', function () {
|
||||
$(this).closest('.aksrt-widget-terms__chip').remove();
|
||||
syncTerms($(this).closest('.aksrt-widget-terms'));
|
||||
})
|
||||
.on('change', '.aksrt-widget-terms__option input', function () {
|
||||
const wrap = this.closest('.aksrt-widget-terms');
|
||||
const option = this.closest('.aksrt-widget-terms__option');
|
||||
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);
|
||||
syncTerms(wrap);
|
||||
})
|
||||
.on('input', '.aksrt-widget-terms__search', function () {
|
||||
const needle = this.value.trim().toLowerCase();
|
||||
$(this).closest('.aksrt-widget-terms').find('.aksrt-widget-terms__option').each(function () {
|
||||
const name = String(this.getAttribute('data-term-name') || '').toLowerCase();
|
||||
this.hidden = needle !== '' && name.indexOf(needle) === -1;
|
||||
});
|
||||
})
|
||||
.on('click', '.aksrt-widget-upload__choose', function () {
|
||||
const $wrap = $(this).closest('.aksrt-widget-upload');
|
||||
const frame = wp.media({ title: $wrap.data('media-library') === 'video' ? '选择视频' : '选择图片', library: $wrap.data('media-library') ? { type: $wrap.data('media-library') } : undefined, multiple: false });
|
||||
|
||||
@@ -50,14 +50,6 @@
|
||||
.aksrt-widget-search--compact button span { display: none; }
|
||||
.aksrt-widget-search--large { min-height: 58px; border-radius: 14px; }
|
||||
|
||||
.aksrt-widget-toc { padding: 0; background: transparent; }
|
||||
.aksrt-widget-toc__toggle { display: flex; width: 100%; align-items: center; justify-content: space-between; padding: 0; border: 0; background: transparent; color: inherit; font-weight: 700; cursor: pointer; }
|
||||
.aksrt-widget-toc__list { margin: 14px 0 0; padding-left: 20px; max-height: 55vh; overflow: auto; }
|
||||
.aksrt-widget-toc__list li { margin: 7px 0; color: var(--muted-color, #737582); }
|
||||
.aksrt-widget-toc__list .level-h3 { padding-left: 12px; font-size: 14px; }
|
||||
.aksrt-widget-toc__list .level-h4 { padding-left: 24px; font-size: 13px; }
|
||||
.aksrt-widget-toc.is-collapsed .aksrt-widget-toc__list { display: none; }
|
||||
|
||||
.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; }
|
||||
@@ -100,23 +92,11 @@
|
||||
.dark-theme .aksrt-media-card__image--dark ~ .aksrt-media-card__image, [data-theme="dark"] .aksrt-media-card__image--dark ~ .aksrt-media-card__image { display: none; }
|
||||
.aksrt-media-card__body { display: flex; flex-direction: column; gap: 5px; padding: 14px 16px 16px; }
|
||||
.aksrt-media-card__body small { color: var(--muted-color, #8a8c98); }
|
||||
.aksrt-widget-partners { overflow: hidden; padding: 5px 0; }
|
||||
.aksrt-widget-partners__track { display: grid; grid-auto-flow: column; grid-template-rows: repeat(var(--aksrt-partner-rows), 76px); grid-auto-columns: 210px; gap: 12px; width: max-content; animation: aksrt-partners var(--aksrt-partner-speed) linear infinite; }
|
||||
.aksrt-widget-partners:hover .aksrt-widget-partners__track { animation-play-state: paused; }
|
||||
.aksrt-widget-partners .aksrt-media-card { display: flex; min-height: 76px; align-items: center; }
|
||||
.aksrt-widget-partners .aksrt-media-card__media { width: 64px; aspect-ratio: 1; margin-left: 8px; border-radius: 9px; }
|
||||
.aksrt-widget-partners .aksrt-media-card__body { padding: 10px; }
|
||||
@keyframes aksrt-partners { to { transform: translateX(-50%); } }
|
||||
.aksrt-combo-card { overflow: hidden; border-radius: var(--aksrt-widget-radius); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }
|
||||
.aksrt-combo-card .aksrt-media-card { box-shadow: none; }
|
||||
.aksrt-combo-card__tags { display: flex; flex-wrap: wrap; gap: 5px; padding: 0 16px 16px; }
|
||||
.aksrt-combo-card__tags span { padding: 3px 8px; border-radius: 999px; background: rgba(127,127,127,.1); font-size: 12px; }
|
||||
|
||||
.aksrt-widget-text-title { padding: 18px 0; }.aksrt-widget-text-title.is-center { text-align: center; }.aksrt-widget-text-title.is-right { text-align: right; }
|
||||
.aksrt-widget-text-title h2 { margin: 0; font-size: clamp(26px,4vw,44px); line-height: 1.25; }.aksrt-widget-text-title p { margin: 12px 0 0; color: var(--muted-color,#8a8c98); line-height: 1.8; }
|
||||
.aksrt-widget-gap { height: var(--aksrt-gap); }
|
||||
.aksrt-widget-tag-cloud { display: flex; flex-wrap: wrap; gap: 8px; }.aksrt-widget-tag-cloud a { padding: 6px 11px; border-radius: 6px; background: rgba(127,127,127,.1); font-size: 13px; transition: background .2s,color .2s; }.aksrt-widget-tag-cloud a:hover { background: var(--focus-color,#ff5473); color: #fff; }.aksrt-widget-tag-cloud small { margin-left: 5px; opacity: .65; }
|
||||
.aksrt-widget-comments { display: grid; gap: 15px; margin: 0; padding: 0; list-style: none; }.aksrt-widget-comments li { display: flex; gap: 11px; }.aksrt-widget-comments__avatar img { width: 42px; height: 42px; border-radius: 50%; }.aksrt-widget-comments p { margin: 3px 0; color: var(--muted-color,#8a8c98); font-size: 13px; line-height: 1.55; }.aksrt-widget-comments__source { font-size: 12px; opacity: .65; }
|
||||
.aksrt-widget-links { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 10px; }.aksrt-widget-links a { display: flex; flex-direction: column; gap: 4px; padding: 10px 8px; border-radius: var(--main-radius,8px); background: var(--muted-border-color,#f2f3f5); color:var(--key-color,#333); }.aksrt-widget-links small { color: var(--muted-color,#8a8c98); }.aksrt-widget-links--simple { display: flex; flex-wrap: wrap; }.aksrt-widget-links--simple a { padding: 5px 9px; box-shadow: none; background: transparent; }
|
||||
.widget-buttons-wrap { margin: calc(0px - var(--pc-gap, 10px)); }
|
||||
.widget-buttons-wrap > div { margin: var(--pc-gap, 10px); }
|
||||
@@ -134,10 +114,6 @@
|
||||
.swiper-bulletin[class*="b-"] .bulletin-icon { color: var(--this-bg); }
|
||||
.swiper-bulletin[class*="jb-"] .bulletin-icon { color: var(--this-bg-b); }
|
||||
|
||||
.aksrt-term-card { display: block; overflow: hidden; border-radius: var(--aksrt-widget-radius); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }.aksrt-term-card__media { display: block; aspect-ratio: 16/9; background: var(--muted-border-color,#eee); }.aksrt-term-card__media img { width: 100%; height: 100%; object-fit: cover; }.aksrt-term-card__body { display: flex; flex-direction: column; gap: 5px; padding: 14px; }.aksrt-term-card__body small { min-height: 38px; color: var(--muted-color,#8a8c98); }.aksrt-term-card__body em { font-size: 12px; color: var(--focus-color,#ff5473); font-style: normal; }
|
||||
.aksrt-term-aggregation { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 15px; }.aksrt-term-aggregation__card { padding: 15px; border-radius: var(--aksrt-widget-radius); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }.aksrt-term-aggregation header a { display: flex; justify-content: space-between; }.aksrt-term-aggregation header span { color: var(--muted-color,#8a8c98); font-size: 13px; }.aksrt-term-aggregation ul { display: grid; gap: 10px; margin: 14px 0 0; padding: 0; list-style: none; }.aksrt-term-aggregation li a { display: flex; align-items: center; gap: 10px; }.aksrt-term-aggregation li img { width: 55px; height: 42px; border-radius: 6px; object-fit: cover; }
|
||||
.aksrt-widget-items { position: relative; min-height: 40px; }.aksrt-widget-items.is-loading { opacity: .72; pointer-events: none; }.aksrt-widget-skeleton { height: 180px; border-radius: 10px; background: linear-gradient(90deg,rgba(127,127,127,.08),rgba(127,127,127,.17),rgba(127,127,127,.08)); background-size: 200% 100%; animation: aksrt-shimmer 1.2s infinite; }.aksrt-widget-load-more { display: block; min-width: 120px; margin: 18px auto 0; padding: 9px 18px; border: 0; border-radius: 999px; background: rgba(127,127,127,.12); color: inherit; cursor: pointer; }.aksrt-widget-pagination { display:flex;justify-content:center;gap:6px;margin-top:18px; }.aksrt-widget-pagination button { min-width:34px;height:34px;border:0;border-radius:8px;background:rgba(127,127,127,.1);color:inherit;cursor:pointer; }.aksrt-widget-pagination button[aria-current="page"] { background:var(--focus-color,#ff5473);color:#fff; }@keyframes aksrt-shimmer { to { background-position: -200% 0; } }
|
||||
.aksrt-widget-oneline .swiper-slide { width: min(300px,78vw); }.aksrt-widget-tabs__nav { display: flex; gap: 8px; margin-bottom: 15px; overflow-x: auto; }.aksrt-widget-tabs__nav button { flex: none; padding: 7px 14px; border: 0; border-radius: 999px; background: rgba(127,127,127,.1); color: inherit; cursor: pointer; }.aksrt-widget-tabs__nav button[aria-selected="true"] { background: var(--focus-color,#ff5473); color: #fff; }
|
||||
|
||||
.aksrt-pricing-card { position: relative; display: flex; flex-direction: column; padding: 20px; border: 1px solid var(--main-border-color,rgba(127,127,127,.13)); border-radius: var(--main-radius,8px); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }.aksrt-pricing-card.is-featured { border-color: var(--focus-color,#ff5473); }.aksrt-pricing-card__badge { position: absolute; top: 12px; right: 12px; padding: 4px 9px; border-radius: 999px; background: var(--focus-color,#ff5473); color: #fff; font-size: 12px; }.aksrt-pricing-card h3 { margin: 0 0 12px; }.aksrt-pricing-card__price { font-size: 34px; font-weight: 800; }.aksrt-pricing-card p { color: var(--muted-color,#8a8c98); }.aksrt-pricing-card ul { flex: 1; display: grid; gap: 9px; margin: 15px 0 22px; padding: 0; list-style: none; }.aksrt-pricing-card li i { margin-right: 8px; color: #22c55e; }
|
||||
.aksrt-reviews .swiper-slide { width: min(390px,86vw); height: auto; }.aksrt-review-card { height: 100%; padding: 20px; border-radius: var(--main-radius,8px); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }.aksrt-review-card__rating { color: #f6b73c; }.aksrt-review-card blockquote { min-height: 80px; margin: 15px 0; line-height: 1.75; }.aksrt-review-card footer { display: flex; align-items: center; gap: 11px; }.aksrt-review-card__avatar { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; }.aksrt-review-card footer span { display: flex; flex-direction: column; }.aksrt-review-card footer small { color: var(--muted-color,#8a8c98); }
|
||||
@@ -145,7 +121,6 @@
|
||||
.aksrt-stat { padding: 22px; text-align: center; }.aksrt-stat > strong { display: block; color: var(--focus-color,#ff5473); font-size: clamp(32px,5vw,52px); }.aksrt-stat h3 { margin: 5px 0; }.aksrt-stat p { margin: 0; color: var(--muted-color,#8a8c98); }.aksrt-highlight--large { grid-column: span 2; }.aksrt-tilt-case { transform-style: preserve-3d; }.aksrt-tilt-case .aksrt-media-card__body { transform: translateZ(20px); }
|
||||
.aksrt-feature-intro__tabs { display: flex; justify-content: center; gap: 8px; margin-bottom: 18px; }.aksrt-feature-intro__tabs button { padding: 7px 14px; border: 0; border-radius: 999px; background: rgba(127,127,127,.1); color: inherit; cursor: pointer; }.aksrt-feature-intro__tabs button[aria-selected="true"] { background: var(--focus-color,#ff5473); color: #fff; }.aksrt-feature-intro__panels article { display: grid; grid-template-columns: 1.15fr 1fr; align-items: center; gap: 34px; }.aksrt-feature-intro__copy p, .aksrt-feature-intro__copy li { color: var(--muted-color,#8a8c98); line-height: 1.7; }
|
||||
|
||||
.aksrt-widget-slider { height: var(--aksrt-slider-height); overflow: hidden; border-radius: var(--aksrt-widget-radius); background: #111; }.aksrt-widget-slider .swiper-slide, .aksrt-widget-slider .swiper-slide > a { position: relative; display: block; width: 100%; height: 100%; overflow: hidden; }.aksrt-widget-slider .swiper-slide > img, .aksrt-widget-slider .swiper-slide > video, .aksrt-widget-slider .swiper-slide > a > img, .aksrt-widget-slider .swiper-slide > a > video { width: 100%; height: 100%; object-fit: cover; }.aksrt-slide-copy { position: absolute; z-index: 2; right: 8%; bottom: 12%; left: 8%; color: #fff; text-shadow: 0 2px 12px rgba(0,0,0,.4); }.aksrt-slide-copy.is-center { top: 50%; bottom: auto; text-align: center; transform: translateY(-50%); }.aksrt-slide-copy.is-right-bottom { text-align: right; }.aksrt-slide-copy h3 { margin: 0; font-size: clamp(25px,4vw,44px); }.aksrt-slide-copy p { margin: 8px 0 0; }.aksrt-slide-layer { position: absolute; z-index: 1; inset: 0; width: 100%; height: 100%; object-fit: contain; pointer-events: none; }.aksrt-slider-combo { display: grid; grid-template-columns: minmax(0,2fr) minmax(240px,1fr); gap: 15px; }.aksrt-slider-combo__side { display: grid; gap: 15px; }.aksrt-slider-combo__side .aksrt-media-card { min-height: 0; }.aksrt-slider-combo__side .aksrt-media-card__media { aspect-ratio: 2/1; }
|
||||
|
||||
.aksrt-user-ranking { display: grid; gap: 11px; margin: 0; padding: 0; list-style: none; }.aksrt-user-ranking li { display: flex; align-items: center; gap: 9px; }.aksrt-user-ranking__index { width: 24px; text-align: center; color: var(--muted-color,#8a8c98); font-weight: 700; }.aksrt-user-ranking__index.is-top { color: #f59e0b; }.aksrt-user-tile { display: flex; min-width: 0; flex: 1; align-items: center; gap: 10px; }.aksrt-user-tile img { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; }.aksrt-user-tile span { display: flex; min-width: 0; flex-direction: column; }.aksrt-user-tile strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.aksrt-user-tile small { color: var(--muted-color,#8a8c98); }.aksrt-user-list { --aksrt-cols: 4; }.aksrt-user-list article { padding: 14px; border-radius: 10px; background: var(--main-bg,#fff); box-shadow: 0 6px 20px rgba(31,35,48,.06); }
|
||||
|
||||
@@ -161,10 +136,8 @@
|
||||
.widget-buttons.m-size-small .but { padding: .3em .6em; }
|
||||
.widget-buttons.m-size-medium .but { padding: 8px 16px; }
|
||||
.widget-buttons.m-size-large .but { padding: 12px 24px; }
|
||||
.aksrt-widget-partners__track { grid-template-rows: repeat(var(--aksrt-partner-rows-m),70px); }
|
||||
.aksrt-term-aggregation, .aksrt-feature-intro__panels article, .aksrt-slider-combo { grid-template-columns: 1fr; }
|
||||
.aksrt-feature-intro__panels article { grid-template-columns: 1fr; }
|
||||
.aksrt-highlight--large { grid-column: auto; }
|
||||
.aksrt-widget-slider { height: var(--aksrt-slider-height-m); }.aksrt-slide--hide-mobile { display: none !important; }
|
||||
.aksrt-widget-gap { height: var(--aksrt-gap-m); }.aksrt-pricing-card.is-featured { transform: none; }
|
||||
.aksrt-user-list { --aksrt-cols-m: 1; }
|
||||
}
|
||||
@@ -186,15 +159,18 @@
|
||||
.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 .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; }
|
||||
.theme-pagination .next-page a { border-radius: 30px; padding: 5px 15px; color: var(--muted-color, #8a8c98); opacity: .6; cursor: pointer; }
|
||||
.theme-pagination .next-page a:hover { background: var(--main-border-color, rgba(127,127,127,.18)); opacity: 1; }
|
||||
.pagenav .page-numbers { cursor: pointer; }
|
||||
.widget-ajaxpager[lazyload-action="ias"] .post_ajax_trigger { display: none; }
|
||||
.widget-slider-graphic { gap: 10px; align-items: stretch; }
|
||||
.widget-slider-graphic .slider-col { width: var(--layout-ratio, 60%); min-width: 0; }
|
||||
.widget-slider-graphic .cover-col { width: calc(100% - var(--layout-ratio, 60%)); }
|
||||
.widget-slider-graphic .widget-graphic-cover { height: 100%; }
|
||||
.widget-slider-graphic .widget-graphic-cover > .row { height: 100%; margin: 0 -5px; }
|
||||
.widget-slider-graphic .widget-graphic-cover > .row > [class*="col-"] { height: 100%; padding: 0 5px; }
|
||||
.widget-slider-graphic .graphic-cover-item { aspect-ratio: auto; height: var(--cover-ratio, auto); }
|
||||
.widget-slider-graphic .graphic-cover-item { position: relative; overflow: hidden; min-height: 0; border-radius: var(--aksrt-widget-radius, 8px); background: #111; color: #fff; }
|
||||
.widget-slider-graphic .graphic-cover-item img, .widget-slider-graphic .graphic-cover-item video { display: block; width: 100%; height: 100%; object-fit: cover; }
|
||||
.widget-slider-graphic .graphic-cover-title { position: absolute; right: 10px; bottom: 10px; left: 10px; z-index: 1; }
|
||||
.new-swiper.slide-widget { overflow: hidden; position: relative; }
|
||||
.new-swiper.slide-widget .swiper-wrapper { height: var(--pc-height, auto); }
|
||||
@@ -225,7 +201,6 @@
|
||||
.widget-slider-graphic .cover-col { width: 100%; }
|
||||
.widget-slider-graphic .widget-graphic-cover > .row { height: auto; }
|
||||
.widget-slider-graphic .widget-graphic-cover > .row > [class*="col-"] { height: auto; }
|
||||
.widget-slider-graphic .graphic-cover-item { height: auto; aspect-ratio: 100 / var(--m-cover-ratio, 45); }
|
||||
.widget-slider-graphic .cover-col .graphic-text { font-size: var(--font-size-sm, 14px); font-weight: var(--font-weight-sm, normal); color: var(--color-sm, inherit); }
|
||||
}
|
||||
@keyframes aksrt-widget-fade { from { opacity: 0; } }@keyframes aksrt-widget-slideup { from { opacity:0;transform:translateY(25px); } }@keyframes aksrt-widget-slidedown { from { opacity:0;transform:translateY(-25px); } }@keyframes aksrt-widget-slideright { from { opacity:0;transform:translateX(-25px); } }@keyframes aksrt-widget-slideleft { from { opacity:0;transform:translateX(25px); } }@keyframes aksrt-widget-zoomin { from { opacity:0;transform:scale(.9); } }@keyframes aksrt-widget-zoomout { from { opacity:0;transform:scale(1.1); } }
|
||||
|
||||
+84
-74
@@ -72,41 +72,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function initToc(toc) {
|
||||
if (!once(toc, 'Toc')) return;
|
||||
var scope = document.querySelector('.article-content, .wp-posts-content, article .entry-content');
|
||||
var headings = scope ? Array.prototype.slice.call(scope.querySelectorAll('h2,h3,h4')) : [];
|
||||
var minimum = Number(toc.dataset.minHeadings || 3);
|
||||
var list = toc.querySelector('.aksrt-widget-toc__list');
|
||||
if (headings.length < minimum || !list) {
|
||||
toc.hidden = true;
|
||||
return;
|
||||
}
|
||||
headings.forEach(function (heading, index) {
|
||||
if (!heading.id) heading.id = 'aksrt-heading-' + (index + 1);
|
||||
var item = document.createElement('li');
|
||||
item.className = 'level-' + heading.tagName.toLowerCase();
|
||||
var link = document.createElement('a');
|
||||
link.href = '#' + heading.id;
|
||||
link.textContent = heading.textContent.trim();
|
||||
link.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
window.scrollTo({ top: heading.getBoundingClientRect().top + window.scrollY - Number(toc.dataset.scrollOffset || 70), behavior: reducedMotion ? 'auto' : 'smooth' });
|
||||
history.replaceState(null, '', link.href);
|
||||
});
|
||||
item.appendChild(link);
|
||||
list.appendChild(item);
|
||||
});
|
||||
var empty = toc.querySelector('.aksrt-widget-toc__empty');
|
||||
if (empty) empty.remove();
|
||||
var toggle = toc.querySelector('.aksrt-widget-toc__toggle');
|
||||
if (toggle) toggle.addEventListener('click', function () {
|
||||
var expanded = toggle.getAttribute('aria-expanded') === 'true';
|
||||
toggle.setAttribute('aria-expanded', expanded ? 'false' : 'true');
|
||||
toc.classList.toggle('is-collapsed', expanded);
|
||||
});
|
||||
}
|
||||
|
||||
function updateQuote(figure) {
|
||||
if (figure.dataset.source !== 'api' || !config.quoteUrl) return Promise.resolve();
|
||||
var button = figure.querySelector('button');
|
||||
@@ -141,26 +106,39 @@
|
||||
return url;
|
||||
}
|
||||
|
||||
function loaderOf(container) {
|
||||
return container.querySelector('.post_ajax_loader');
|
||||
}
|
||||
|
||||
function setBusy(container, busy) {
|
||||
if (busy) {
|
||||
container.dataset.loading = '1';
|
||||
container.setAttribute('aria-busy', 'true');
|
||||
var loader = loaderOf(container);
|
||||
if (loader) loader.style.display = '';
|
||||
} else {
|
||||
delete container.dataset.loading;
|
||||
container.setAttribute('aria-busy', 'false');
|
||||
var loader = loaderOf(container);
|
||||
if (loader) loader.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function loadItems(container, page, append) {
|
||||
var owner = container.closest('[data-widget]') || container;
|
||||
var widget = owner.dataset.widget || container.dataset.widget;
|
||||
var instance = owner.dataset.instance || container.dataset.instance;
|
||||
var segment = container.dataset.segment || owner.dataset.segment || '';
|
||||
if (!widget || !instance || !config.restUrl || container.dataset.loading === '1') return;
|
||||
container.dataset.loading = '1';
|
||||
container.classList.add('is-loading');
|
||||
container.setAttribute('aria-busy', 'true');
|
||||
setBusy(container, true);
|
||||
fetch(itemsUrl(widget, instance, page, segment), { credentials: 'same-origin', headers: { Accept: 'application/json' } })
|
||||
.then(function (response) { if (!response.ok) throw new Error('items'); return response.json(); })
|
||||
.then(function (data) {
|
||||
var oldPagination = container.querySelector('.aksrt-widget-pagination, .aksrt-widget-load-more');
|
||||
if (oldPagination) oldPagination.remove();
|
||||
var fragment = document.createElement('div');
|
||||
fragment.innerHTML = String(data.html || '');
|
||||
Array.prototype.slice.call(container.querySelectorAll(':scope > .ajax-pag')).forEach(function (node) { node.remove(); });
|
||||
if (append) {
|
||||
var target = container.querySelector('.aksrt-widget-posts, .posts-row, .swiper-wrapper') || container;
|
||||
var source = fragment.querySelector('.aksrt-widget-posts, .posts-row, .swiper-wrapper');
|
||||
Array.prototype.slice.call((source || fragment).children).forEach(function (node) { target.appendChild(node); });
|
||||
Array.prototype.slice.call(fragment.children).forEach(function (node) { container.appendChild(node); });
|
||||
} else {
|
||||
container.innerHTML = fragment.innerHTML;
|
||||
}
|
||||
@@ -169,11 +147,7 @@
|
||||
scan(container);
|
||||
})
|
||||
.catch(function () { container.innerHTML = errorMarkup(); })
|
||||
.finally(function () {
|
||||
delete container.dataset.loading;
|
||||
container.classList.remove('is-loading');
|
||||
container.setAttribute('aria-busy', 'false');
|
||||
});
|
||||
.finally(function () { setBusy(container, false); });
|
||||
}
|
||||
|
||||
function initItems(container) {
|
||||
@@ -181,35 +155,49 @@
|
||||
if (container.classList.contains('is-loading')) loadItems(container, 1, false);
|
||||
container.addEventListener('click', function (event) {
|
||||
var retry = event.target.closest('[data-widget-retry]');
|
||||
var more = event.target.closest('.aksrt-widget-load-more');
|
||||
var pageButton = event.target.closest('[data-widget-page]');
|
||||
if (retry) loadItems(container, Number(container.dataset.page || 1), false);
|
||||
if (more) loadItems(container, Number(more.dataset.nextPage || 2), true);
|
||||
if (pageButton) loadItems(container, Number(pageButton.dataset.widgetPage || 1), false);
|
||||
var trigger = event.target.closest('[data-next-page]');
|
||||
var pageLink = event.target.closest('.page-numbers[data-widget-page]');
|
||||
if (retry) {
|
||||
loadItems(container, Number(container.dataset.page || 1), false);
|
||||
return;
|
||||
}
|
||||
if (trigger) {
|
||||
// data-widget-ias marks the hidden reveal trigger the runtime clicks
|
||||
// when the lazy container scrolls into view; it reloads page one.
|
||||
event.preventDefault();
|
||||
if (trigger.dataset.widgetIas === '1') loadItems(container, 1, false);
|
||||
else loadItems(container, Number(trigger.dataset.nextPage || 2), true);
|
||||
return;
|
||||
}
|
||||
if (pageLink) {
|
||||
event.preventDefault();
|
||||
loadItems(container, Number(pageLink.dataset.widgetPage || 1), false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initTabs(tabs) {
|
||||
if (!once(tabs, 'Tabs')) return;
|
||||
tabs.addEventListener('keydown', function (event) { moveTabFocus(event, tabs); });
|
||||
tabs.addEventListener('keydown', function (event) { moveTabFocus(event, tabs); });
|
||||
tabs.addEventListener('click', function (event) {
|
||||
var button = event.target.closest('[role="tab"][data-segment]');
|
||||
if (!button) return;
|
||||
event.preventDefault();
|
||||
tabs.querySelectorAll('[role="tab"]').forEach(function (tab) {
|
||||
tab.setAttribute('aria-selected', tab === button ? 'true' : 'false');
|
||||
tab.setAttribute('tabindex', tab === button ? '0' : '-1');
|
||||
if (tab.parentElement) tab.parentElement.classList.toggle('active', tab === button);
|
||||
});
|
||||
tabs.querySelectorAll('.tab-content > .tab-pane').forEach(function (item) {
|
||||
var active = item.id === button.getAttribute('aria-controls');
|
||||
item.classList.toggle('active', active); item.classList.toggle('in', active);
|
||||
});
|
||||
var panel = tabs.querySelector('[data-widget-items][data-segment="' + button.dataset.segment + '"]');
|
||||
if (!panel) panel = tabs.querySelector('[data-widget-items]');
|
||||
event.preventDefault();
|
||||
tabs.querySelectorAll('[role="tab"]').forEach(function (tab) {
|
||||
tab.setAttribute('aria-selected', tab === button ? 'true' : 'false');
|
||||
tab.setAttribute('tabindex', tab === button ? '0' : '-1');
|
||||
if (tab.parentElement) tab.parentElement.classList.toggle('active', tab === button);
|
||||
});
|
||||
tabs.querySelectorAll('.tab-content > .tab-pane').forEach(function (item) {
|
||||
var active = item.id === button.getAttribute('aria-controls');
|
||||
item.classList.toggle('active', active); item.classList.toggle('in', active);
|
||||
});
|
||||
var panel = tabs.querySelector('[data-widget-items][data-segment="' + button.dataset.segment + '"]');
|
||||
if (!panel) panel = tabs.querySelector('[data-widget-items]');
|
||||
if (!panel) return;
|
||||
panel.dataset.segment = button.dataset.segment;
|
||||
panel.setAttribute('aria-labelledby', button.id);
|
||||
loadItems(panel, 1, false);
|
||||
panel.setAttribute('aria-labelledby', button.id);
|
||||
loadItems(panel, 1, false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -232,15 +220,15 @@
|
||||
|
||||
function initFeatures(features) {
|
||||
if (!once(features, 'Features')) return;
|
||||
features.addEventListener('keydown', function (event) { moveTabFocus(event, features); });
|
||||
features.addEventListener('keydown', function (event) { moveTabFocus(event, features); });
|
||||
features.addEventListener('click', function (event) {
|
||||
var button = event.target.closest('[data-feature-index]');
|
||||
if (!button) return;
|
||||
var index = button.dataset.featureIndex;
|
||||
features.querySelectorAll('[data-feature-index]').forEach(function (tab) {
|
||||
tab.setAttribute('aria-selected', tab === button ? 'true' : 'false');
|
||||
tab.setAttribute('tabindex', tab === button ? '0' : '-1');
|
||||
});
|
||||
features.querySelectorAll('[data-feature-index]').forEach(function (tab) {
|
||||
tab.setAttribute('aria-selected', tab === button ? 'true' : 'false');
|
||||
tab.setAttribute('tabindex', tab === button ? '0' : '-1');
|
||||
});
|
||||
features.querySelectorAll('[data-feature-panel]').forEach(function (panel) { panel.hidden = panel.dataset.featurePanel !== index; });
|
||||
});
|
||||
}
|
||||
@@ -262,16 +250,38 @@
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
/* 幻灯片加图文视频卡片:以左侧幻灯片实际高度重算右侧卡片比例。
|
||||
* 合作伙伴滚动由运行时 main.js 初始化(.zib-partners-scroll 唯一所有者)。 */
|
||||
function measureSliderGraphic(widget) {
|
||||
if (once(widget, 'Combo')) return;
|
||||
var slider = widget.querySelector('.slider-col .zib-slider') || widget.querySelector('.slider-col');
|
||||
var covers = widget.querySelector('.cover-col');
|
||||
if (!slider || !covers) return;
|
||||
var row = Number(widget.dataset.row || 2);
|
||||
var col = Number(widget.dataset.col || 2);
|
||||
var gap = 10;
|
||||
function update() {
|
||||
var height = slider.offsetHeight;
|
||||
var width = covers.offsetWidth;
|
||||
if (!height || !width) return;
|
||||
var ratio = (((height - (col - 1) * gap) / col) / ((width - (row - 1) * gap) / row)) * 100;
|
||||
covers.style.setProperty('--cover-ratio', ratio.toFixed(2) + '%');
|
||||
}
|
||||
update();
|
||||
window.addEventListener('resize', update, { passive: true });
|
||||
if ('ResizeObserver' in window) new ResizeObserver(update).observe(slider);
|
||||
}
|
||||
|
||||
function scan(root) {
|
||||
var scope = root || document;
|
||||
scope.querySelectorAll('.aksrt-widget-video[data-video-url]').forEach(initVideo);
|
||||
scope.querySelectorAll('.aksrt-widget-toc').forEach(initToc);
|
||||
scope.querySelectorAll('.aksrt-widget-quote').forEach(initQuote);
|
||||
scope.querySelectorAll('[data-widget-items]').forEach(initItems);
|
||||
scope.querySelectorAll('.aksrt-widget-tabs, .widget-tab-post').forEach(initTabs);
|
||||
scope.querySelectorAll('.aksrt-faq').forEach(initFaq);
|
||||
scope.querySelectorAll('.aksrt-feature-intro').forEach(initFeatures);
|
||||
scope.querySelectorAll('[data-count-up]').forEach(initCount);
|
||||
scope.querySelectorAll('.widget-slider-graphic').forEach(measureSliderGraphic);
|
||||
if (typeof window.VanillaTilt !== 'undefined') scope.querySelectorAll('[data-aksrt-tilt]').forEach(function (el) { if (once(el, 'Tilt')) window.VanillaTilt.init(el); });
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ require_once AKSRT_DIR . '/inc/helpers/header/aksrt-header-menu-search.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-runtime-win.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-identity.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-graphic-card.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-widgets-markup.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-icon-cards.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-user-card.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-rewards.php';
|
||||
|
||||
@@ -168,7 +168,7 @@ if (!function_exists('aksrt_graphic_card')) :
|
||||
$class = trim('graphic hover-zoom-img ' . (string) $args['class'] . ' ' . $type);
|
||||
$height_scale = max(0, (int) $args['height_scale']);
|
||||
$scale_attr = $height_scale > 0
|
||||
? ' style="padding-bottom:' . esc_attr((string) $height_scale) . '%!important;"'
|
||||
? ' style="--cover-height-scale:' . esc_attr((string) $height_scale) . '%;"'
|
||||
: '';
|
||||
|
||||
$html = '';
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
<?php
|
||||
/**
|
||||
* Widget-system markup helpers (reference parity).
|
||||
*
|
||||
* Ports of the reference theme widget renderers that stay procedural so the
|
||||
* schema renderers stay small: hot posts ranking, category aggregation card,
|
||||
* recent comments list and the ajax empty state.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_widget_str_cut')) :
|
||||
/**
|
||||
* Character-based truncation (reference zib_str_cut parity).
|
||||
*/
|
||||
function aksrt_widget_str_cut(string $text, int $length): string {
|
||||
$text = trim($text);
|
||||
if ($length <= 0 || function_exists('mb_strlen') === false) {
|
||||
return $text;
|
||||
}
|
||||
return mb_strlen($text) > $length ? mb_substr($text, 0, $length) . '...' : $text;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_widget_ajax_null')) :
|
||||
/**
|
||||
* Empty state for ajax-paged lists (hidden pager stub included).
|
||||
*/
|
||||
function aksrt_widget_ajax_null(string $text = ''): string {
|
||||
if ($text === '') {
|
||||
$text = __('暂无内容', 'aksrt');
|
||||
}
|
||||
$html = '<div class="ajax-item">' . aksrt_get_null($text, 60, 'null.svg', '', 280) . '</div>';
|
||||
$html .= '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
|
||||
return $html;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_widget_term_cover_url')) :
|
||||
/**
|
||||
* Category cover image URL with the theme meta fallback chain.
|
||||
*/
|
||||
function aksrt_widget_term_cover_url(int $term_id): string {
|
||||
foreach (['aksrt_cover_url', 'cover_url'] as $meta_key) {
|
||||
$url = esc_url_raw((string) get_term_meta($term_id, $meta_key, true));
|
||||
if ($url !== '') {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_widget_term_views_sum')) :
|
||||
/**
|
||||
* Sum of published post views inside a term (badge on the aggregation card).
|
||||
*/
|
||||
function aksrt_widget_term_views_sum(int $term_id): int {
|
||||
static $cache = [];
|
||||
if (isset($cache[$term_id])) {
|
||||
return $cache[$term_id];
|
||||
}
|
||||
$sum = 0;
|
||||
$term = get_term($term_id);
|
||||
if ($term instanceof WP_Term && class_exists('wpdb')) {
|
||||
global $wpdb;
|
||||
$view_key = class_exists('AKSRT\\Core\\DomainModel') ? \AKSRT\Core\DomainModel::POST_VIEW_COUNT_KEY : 'aksrt_post_views';
|
||||
$tt_id = (int) $term->term_taxonomy_id;
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- prepare() below.
|
||||
$sum = (int) $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT SUM(CAST(pm.meta_value AS UNSIGNED)) FROM {$wpdb->postmeta} AS pm
|
||||
INNER JOIN {$wpdb->posts} AS p ON p.ID = pm.post_id
|
||||
INNER JOIN {$wpdb->term_relationships} AS tr ON tr.object_id = p.ID
|
||||
WHERE pm.meta_key = %s AND tr.term_taxonomy_id = %d
|
||||
AND p.post_status = 'publish' AND p.post_type = 'post'",
|
||||
$view_key,
|
||||
$tt_id
|
||||
));
|
||||
}
|
||||
$cache[$term_id] = max(0, $sum);
|
||||
return $cache[$term_id];
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_widget_hot_posts')) :
|
||||
/**
|
||||
* Hot posts ranking box (reference parity, sidebar sized).
|
||||
*
|
||||
* @param array<string,mixed> $args {orderby, limit_day, count, target_blank}.
|
||||
*/
|
||||
function aksrt_widget_hot_posts(array $args = []): string {
|
||||
$instance = wp_parse_args($args, [
|
||||
'orderby' => 'views',
|
||||
'limit_day' => 0,
|
||||
'count' => 6,
|
||||
'target_blank' => false,
|
||||
]);
|
||||
$orderby = (string) $instance['orderby'];
|
||||
$target = !empty($instance['target_blank']) ? ' target="_blank" rel="noopener noreferrer"' : '';
|
||||
|
||||
$instance['paginate'] = '';
|
||||
$query = \AKSRT\WidgetSystem\PostQuery::build($instance, 1, []);
|
||||
if (!$query->have_posts()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$posts_html = '';
|
||||
$i = 0;
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$i++;
|
||||
$data = aksrt_renderer_collect_post_data(['time_source' => 'date']);
|
||||
if (!$data) {
|
||||
continue;
|
||||
}
|
||||
$title = esc_html((string) $data['title']) . ($data['subtitle'] !== '' ? '<span class="focus-color">' . esc_html((string) $data['subtitle']) . '</span>' : '');
|
||||
|
||||
$badge_classes = ['', 'jb-red', 'jb-yellow'];
|
||||
$badge_class = ($i === 1 ? 'em12 ' : '') . ($badge_classes[$i - 1] ?? 'b-gray');
|
||||
$top_badge = '<badge class="img-badge left hot ' . esc_attr($badge_class) . '"><i>TOP' . $i . '</i></badge>';
|
||||
|
||||
$meta_value = '';
|
||||
if ($orderby === 'favorite') {
|
||||
$meta_value = aksrt_format_number((int) aksrt_get_post_favorites((int) $data['post_id'])) . __('人收藏', 'aksrt');
|
||||
} elseif ($orderby === 'like') {
|
||||
$meta_value = aksrt_format_number((int) aksrt_get_post_likes((int) $data['post_id'])) . __('人点赞', 'aksrt');
|
||||
} elseif ($orderby === 'comment_count') {
|
||||
$meta_value = aksrt_format_number((int) $data['comments']) . __('条讨论', 'aksrt');
|
||||
}
|
||||
if ($meta_value === '') {
|
||||
$meta_value = aksrt_format_number((int) $data['views']) . __('人已阅读', 'aksrt');
|
||||
}
|
||||
|
||||
$thumb = '';
|
||||
if ((string) $data['primary'] !== '') {
|
||||
$thumb = '<img' . aksrt_get_lazy_attr('lazy_posts_thumb', (string) $data['primary'], 'fit-cover radius8') . ' alt="' . esc_attr((string) $data['title']) . '">';
|
||||
}
|
||||
$time_ago = '<i class="fa fa-clock-o mr3" aria-hidden="true"></i>' . esc_html((string) $data['time_ago']);
|
||||
$permalink = esc_url((string) $data['permalink']);
|
||||
|
||||
if ($i === 1) {
|
||||
$posts_html .= '<div class="relative">';
|
||||
$posts_html .= '<a' . $target . ' href="' . $permalink . '">';
|
||||
$posts_html .= '<div class="graphic hover-zoom-img" style="padding-bottom: 60%!important;">' . $thumb;
|
||||
$posts_html .= '<div class="absolute linear-mask"></div>';
|
||||
$posts_html .= '<div class="abs-center left-bottom box-body">';
|
||||
$posts_html .= '<div class="mb6"><span class="badg b-theme badg-sm">' . esc_html($meta_value) . '</span></div>';
|
||||
$posts_html .= $title;
|
||||
$posts_html .= '</div></div></a>';
|
||||
$posts_html .= $top_badge;
|
||||
$posts_html .= '</div>';
|
||||
} else {
|
||||
$posts_html .= '<div class="flex mt15 relative hover-zoom-img">';
|
||||
$posts_html .= '<a' . $target . ' href="' . $permalink . '"><div class="graphic">' . $thumb . '</div></a>';
|
||||
$posts_html .= '<div class="term-title ml10 flex xx flex1 jsb">';
|
||||
$posts_html .= '<div class="text-ellipsis-2"><a' . $target . ' href="' . $permalink . '">' . $title . '</a></div>';
|
||||
$posts_html .= '<div class="px12 muted-3-color text-ellipsis flex jsb"><span>' . $time_ago . '</span><span>' . esc_html($meta_value) . '</span></div>';
|
||||
$posts_html .= '</div>';
|
||||
$posts_html .= $top_badge;
|
||||
$posts_html .= '</div>';
|
||||
}
|
||||
}
|
||||
wp_reset_postdata();
|
||||
|
||||
return '<div class="zib-widget hot-posts">' . $posts_html . '</div>';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_widget_term_aggregation')) :
|
||||
/**
|
||||
* Category aggregation card: cover + description + latest posts list.
|
||||
*
|
||||
* @param array<string,mixed> $args {term_id, target_blank, orderby, order, count}.
|
||||
*/
|
||||
function aksrt_widget_term_aggregation(array $args = []): string {
|
||||
$args = wp_parse_args($args, [
|
||||
'term_id' => 0,
|
||||
'target_blank' => false,
|
||||
'orderby' => 'modified',
|
||||
'order' => 'DESC',
|
||||
'count' => 4,
|
||||
]);
|
||||
$term_id = (int) $args['term_id'];
|
||||
$term = $term_id > 0 ? get_term($term_id, 'category') : null;
|
||||
if (!$term instanceof WP_Term || is_wp_error($term)) {
|
||||
return '';
|
||||
}
|
||||
$target = !empty($args['target_blank']) ? ' target="_blank" rel="noopener noreferrer"' : '';
|
||||
$href = get_term_link($term);
|
||||
if (is_wp_error($href)) {
|
||||
return '';
|
||||
}
|
||||
$href = esc_url((string) $href);
|
||||
|
||||
$icon = '<i class="fa fa-folder-open-o mr6" aria-hidden="true"></i>';
|
||||
$img = aksrt_widget_term_cover_url((int) $term->term_id);
|
||||
$img_html = aksrt_graphic_card([
|
||||
'class' => '',
|
||||
'img' => $img,
|
||||
'alt' => __('分类', 'aksrt') . '-' . $term->name,
|
||||
'link' => ['url' => $href, 'target' => !empty($args['target_blank']) ? '_blank' : ''],
|
||||
'height_scale' => 0,
|
||||
'mask_opacity' => 0,
|
||||
'more' => '<badge class="img-badge px12">' . aksrt_renderer_svg_icon('hot') . ' ' . esc_html(aksrt_format_number(aksrt_widget_term_views_sum((int) $term->term_id))) . '</badge>',
|
||||
]);
|
||||
$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>';
|
||||
|
||||
$query_args = [
|
||||
'cat' => (string) $term->term_id,
|
||||
'orderby' => (string) $args['orderby'],
|
||||
'order' => strtoupper((string) $args['order']) === 'ASC' ? 'ASC' : 'DESC',
|
||||
'count' => max(1, (int) $args['count']),
|
||||
'paginate' => '',
|
||||
];
|
||||
$query = \AKSRT\WidgetSystem\PostQuery::build($query_args, 1, []);
|
||||
|
||||
$meta = '<span class="px12 muted-3-color text-ellipsis"><i class="fa fa-file-text-o fa-fw" aria-hidden="true"></i>' . sprintf(esc_html__('%s篇文章', 'aksrt'), esc_html(aksrt_format_number(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 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']);
|
||||
if (!$data) {
|
||||
continue;
|
||||
}
|
||||
$title = esc_html((string) $data['title']) . ($data['subtitle'] !== '' ? '<span class="focus-color">' . esc_html((string) $data['subtitle']) . '</span>' : '');
|
||||
$title = '<div class="text-ellipsis"><a class="icon-circle mln3"' . $target . ' href="' . esc_url((string) $data['permalink']) . '">' . $title . '</a></div>';
|
||||
|
||||
$post_meta = '';
|
||||
switch ((string) $args['orderby']) {
|
||||
case 'views':
|
||||
$post_meta = aksrt_format_number((int) $data['views']);
|
||||
break;
|
||||
case 'favorite':
|
||||
$post_meta = 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']));
|
||||
break;
|
||||
case 'comment_count':
|
||||
$post_meta = aksrt_format_number((int) $data['comments']);
|
||||
break;
|
||||
}
|
||||
if ($post_meta === '') {
|
||||
$post_meta = '<i class="fa fa-clock-o 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>';
|
||||
}
|
||||
wp_reset_postdata();
|
||||
|
||||
$html = '<div class="zib-widget term-aggregation mb10">';
|
||||
$html .= '<div class="mb20 hover-zoom-img flex px12-sm px12-m-s">';
|
||||
$html .= $img_html;
|
||||
$html .= '<div class="term-title ml10 flex xx flex1 jsb">' . $name . $description . $meta . '</div>';
|
||||
$html .= '</div>';
|
||||
$html .= $posts_html;
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_widget_comments_list')) :
|
||||
/**
|
||||
* Recent comments widget list (reference parity).
|
||||
*
|
||||
* @param array<string,mixed> $args {limit, outer, outpost, style, show_source}.
|
||||
*/
|
||||
function aksrt_widget_comments_list(array $args = []): string {
|
||||
$args = wp_parse_args($args, [
|
||||
'limit' => 8,
|
||||
'outer' => '',
|
||||
'outpost' => '',
|
||||
'style' => '',
|
||||
'show_source' => false,
|
||||
]);
|
||||
$comments = get_comments([
|
||||
'orderby' => 'comment_date',
|
||||
'number' => max(1, (int) $args['limit']),
|
||||
'status' => 'approve',
|
||||
'post_type' => ['post'],
|
||||
'author__not_in' => \AKSRT\WidgetSystem\RendererHelpers::ids((string) $args['outer']),
|
||||
'post__not_in' => \AKSRT\WidgetSystem\RendererHelpers::ids((string) $args['outpost']),
|
||||
]);
|
||||
if (!$comments) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$output = '';
|
||||
foreach ($comments as $comment) {
|
||||
$content = aksrt_widget_comment_excerpt((string) $comment->comment_content);
|
||||
$link = esc_url((string) get_comment_link($comment));
|
||||
$time = aksrt_time_ago((string) $comment->comment_date);
|
||||
$user_name = function_exists('aksrt_get_comments_user_name_html')
|
||||
? aksrt_get_comments_user_name_html($comment)
|
||||
: esc_html(get_comment_author($comment));
|
||||
$like = aksrt_get_comment_like_html($comment);
|
||||
$avatar = aksrt_get_comment_avatar_box($comment, 'avatar-img');
|
||||
$class = 'comment-widget-item' . ((string) $args['style'] === 'style_2' ? ' style-2' : '');
|
||||
|
||||
$source = '';
|
||||
if (!empty($args['show_source'])) {
|
||||
$post_title = esc_html(get_the_title((int) $comment->comment_post_ID));
|
||||
$source = '<div class="muted-color mt6 em09 text-ellipsis"><a href="' . esc_url((string) get_permalink((int) $comment->comment_post_ID)) . '" class="muted-2-color"><span class="muted-3-color">' . esc_html__('评论于:', 'aksrt') . '</span>' . $post_title . '</a></div>';
|
||||
}
|
||||
|
||||
$output .= '<div class="' . esc_attr($class) . '">';
|
||||
$output .= '<div class="flex gap6">' . $avatar;
|
||||
$output .= '<div class="posts-mini-con em09 ml10 flex xx jsb">';
|
||||
$output .= '<p class="flex jsb"><span class="flex ellipsis-box">' . $user_name . '<span class="flex0 icon-spot muted-3-color" title="' . esc_attr((string) $comment->comment_date) . '">' . esc_html($time) . '</span></span><span class="ml10 flex0">' . $like . '</span></p>';
|
||||
$output .= (string) $args['style'] === 'style_2' ? '' : '<a class="con-link muted-color text-ellipsis-5" href="' . $link . '">' . $content . '</a>';
|
||||
$output .= '</div></div>';
|
||||
$output .= (string) $args['style'] === 'style_2' ? '<a class="em09 con-link relative" href="' . $link . '"><div class="text-ellipsis-5 muted-box padding-10">' . $content . '</div></a>' : '';
|
||||
$output .= $source;
|
||||
$output .= '</div>';
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_widget_comment_excerpt')) :
|
||||
/**
|
||||
* Comment text reduced to a widget-safe plain snippet (images/code dropped).
|
||||
*/
|
||||
function aksrt_widget_comment_excerpt(string $content): string {
|
||||
$content = convert_smilies($content);
|
||||
$content = preg_replace('/\[img=(.*?)\]/', '[图片]', $content) ?? $content;
|
||||
$content = preg_replace('/\[link=(.*?) target=_blank\](.*?)\[\/link\]/', '$2', $content) ?? $content;
|
||||
$content = preg_replace('/\[link=(.*?)\](.*?)\[\/link\]/', '$2', $content) ?? $content;
|
||||
$content = preg_replace('/\[code]([\s\S]*)\[\/code]/', '[代码]', $content) ?? $content;
|
||||
$content = preg_replace('/\[g=(.*?)\]/', '[$1]', $content) ?? $content;
|
||||
// Strip any remaining anchors but keep their text.
|
||||
$content = preg_replace('/<a[^>]*>(.*?)<\/a>/', '$1', $content) ?? $content;
|
||||
return wp_kses_post($content);
|
||||
}
|
||||
endif;
|
||||
@@ -14,7 +14,7 @@ final class FieldSchema {
|
||||
return [
|
||||
'text', 'textarea', 'number', 'spinner', 'slider', 'switcher', 'radio',
|
||||
'button_set', 'select', 'checkbox', 'color', 'palette', 'image_select', 'icon', 'upload',
|
||||
'link', 'spacing', 'background', 'group', 'fieldset', 'accordion',
|
||||
'link', 'spacing', 'background', 'group', 'fieldset', 'accordion', 'terms',
|
||||
'content', 'subheading', 'submessage',
|
||||
];
|
||||
}
|
||||
@@ -63,6 +63,8 @@ final class FieldSchema {
|
||||
return self::choice($field, $value);
|
||||
case 'checkbox':
|
||||
return self::checkbox($field, $value);
|
||||
case 'terms':
|
||||
return self::terms($value);
|
||||
case 'link':
|
||||
return self::link($value);
|
||||
case 'spacing':
|
||||
@@ -103,7 +105,7 @@ final class FieldSchema {
|
||||
if ($type === 'switcher') {
|
||||
return false;
|
||||
}
|
||||
if (in_array($type, ['group', 'checkbox'], true)) {
|
||||
if (in_array($type, ['group', 'checkbox', 'terms'], true)) {
|
||||
return [];
|
||||
}
|
||||
if (in_array($type, ['link', 'spacing', 'background', 'fieldset', 'accordion'], true)) {
|
||||
@@ -121,7 +123,9 @@ final class FieldSchema {
|
||||
$number = min((float) $field['max'], $number);
|
||||
}
|
||||
$step = (float) ($field['step'] ?? 1);
|
||||
return $step >= 1 ? (int) round($number) : $number;
|
||||
// Whole values stay int so JSON round trips keep normalize identity
|
||||
// even for fractional-step fields (e.g. partners speed 0.1–10s).
|
||||
return $step >= 1 || fmod($number, 1.0) === 0.0 ? (int) round($number) : $number;
|
||||
}
|
||||
|
||||
private static function choice(array $field, $value) {
|
||||
@@ -143,6 +147,11 @@ final class FieldSchema {
|
||||
return array_values(array_filter(array_map('strval', $values), static fn($item) => array_key_exists($item, $options)));
|
||||
}
|
||||
|
||||
private static function terms($value): array {
|
||||
$values = is_array($value) ? $value : (preg_split('/[,,\s]+/', (string) $value) ?: []);
|
||||
return array_values(array_unique(array_filter(array_map('absint', $values))));
|
||||
}
|
||||
|
||||
private static function link($value): array {
|
||||
$value = is_array($value) ? $value : [];
|
||||
return [
|
||||
|
||||
@@ -60,6 +60,9 @@ final class FormRenderer {
|
||||
case 'checkbox':
|
||||
self::checkboxes($field_id, $field_name, $field, $value);
|
||||
break;
|
||||
case 'terms':
|
||||
self::terms($field_id, $field_name, $field, $value);
|
||||
break;
|
||||
case 'number':
|
||||
case 'spinner':
|
||||
case 'slider':
|
||||
@@ -153,7 +156,38 @@ final class FormRenderer {
|
||||
$max = isset($field['max']) ? ' max="' . esc_attr((string) $field['max']) . '"' : '';
|
||||
$step = ' step="' . esc_attr((string) ($field['step'] ?? 1)) . '"';
|
||||
$input_type = $type === 'slider' ? 'range' : 'number';
|
||||
echo '<div class="aksrt-widget-number"><input class="widefat" type="' . esc_attr($input_type) . '" id="' . esc_attr($id) . '" name="' . esc_attr($name) . '" value="' . esc_attr((string) $value) . '"' . $min . $max . $step . '><output>' . esc_html((string) $value) . '</output></div>';
|
||||
$unit = trim((string) ($field['unit'] ?? ''));
|
||||
echo '<div class="aksrt-widget-number"><input class="widefat" type="' . esc_attr($input_type) . '" id="' . esc_attr($id) . '" name="' . esc_attr($name) . '" value="' . esc_attr((string) $value) . '"' . $min . $max . $step . '><output>' . esc_html((string) $value) . '</output>' . ($unit !== '' ? '<span class="aksrt-widget-number__unit">' . esc_html($unit) . '</span>' : '') . '</div>';
|
||||
}
|
||||
|
||||
private static function terms(string $id, string $name, array $field, $value): void {
|
||||
$taxonomy = (string) ($field['taxonomy'] ?? 'category');
|
||||
$terms = get_terms(['taxonomy' => $taxonomy, 'hide_empty' => false, 'number' => 500, 'orderby' => 'name']);
|
||||
if (is_wp_error($terms)) {
|
||||
$terms = [];
|
||||
}
|
||||
$selected = array_values(array_filter(array_map('absint', (array) $value)));
|
||||
$by_id = [];
|
||||
foreach ($terms as $term) {
|
||||
$by_id[(int) $term->term_id] = $term;
|
||||
}
|
||||
echo '<div class="aksrt-widget-terms" data-aksrt-terms="' . esc_attr($taxonomy) . '">';
|
||||
echo '<input type="hidden" class="aksrt-widget-terms__value" id="' . esc_attr($id) . '" name="' . esc_attr($name) . '" value="' . esc_attr(implode(',', $selected)) . '">';
|
||||
echo '<div class="aksrt-widget-terms__selected">';
|
||||
foreach ($selected as $term_id) {
|
||||
if (!isset($by_id[$term_id])) {
|
||||
continue;
|
||||
}
|
||||
echo '<span class="aksrt-widget-terms__chip" data-term-id="' . esc_attr((string) $term_id) . '"><span class="dashicons dashicons-move" aria-hidden="true"></span>' . esc_html((string) $by_id[$term_id]->name) . '<button type="button" class="button-link-delete aksrt-widget-terms__remove" aria-label="' . esc_attr__('移除', 'aksrt') . '">×</button></span>';
|
||||
}
|
||||
echo '</div>';
|
||||
echo '<input type="search" class="widefat aksrt-widget-terms__search" placeholder="' . esc_attr__('输入关键词筛选', 'aksrt') . '" aria-label="' . esc_attr__('筛选可选分类', 'aksrt') . '">';
|
||||
echo '<div class="aksrt-widget-terms__options">';
|
||||
foreach ($terms as $term) {
|
||||
$term_id = (int) $term->term_id;
|
||||
echo '<label class="aksrt-widget-terms__option" data-term-name="' . esc_attr((string) $term->name) . '"><input type="checkbox" value="' . esc_attr((string) $term_id) . '" ' . checked(in_array($term_id, $selected, true), true, false) . '> ' . esc_html((string) $term->name) . '</label>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
}
|
||||
|
||||
private static function upload(string $id, string $name, string $value, string $library = ''): void {
|
||||
|
||||
@@ -27,10 +27,27 @@ final class FrontendRenderer {
|
||||
self::render_user_widget($definition, $instance, $widget, $content);
|
||||
return;
|
||||
}
|
||||
if (in_array($definition->slug(), ['post_pay', 'tab_posts', 'slider', 'slider_media_combo', 'icon_cards', 'icon_cover_cards', 'buttons', 'notice'], true)) {
|
||||
if (in_array($definition->slug(), [
|
||||
'post_pay', 'tab_posts', 'posts', 'oneline_posts', 'hot_posts',
|
||||
'slider', 'slider_media_combo', 'icon_cards', 'icon_cover_cards',
|
||||
'buttons', 'notice', 'term_cards', 'term_aggregation', 'media_cards',
|
||||
'combo_info', 'partners', 'recent_comments', 'posts_toc',
|
||||
], true)) {
|
||||
echo (string) ($args['before_widget'] ?? '');
|
||||
if ($definition->slug() !== 'post_pay') echo self::reference_title($instance);
|
||||
echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$animation = '';
|
||||
$content_classes = ['widget-content'];
|
||||
$device_class = WidgetVisibility::device_class($instance);
|
||||
if ($device_class !== '') {
|
||||
$content_classes[] = $device_class;
|
||||
}
|
||||
if (!empty($instance['animation_in'])) {
|
||||
$content_classes[] = 'obs-animate ani-' . sanitize_html_class((string) $instance['animation_in']);
|
||||
if (!empty($instance['animation_repeat'])) {
|
||||
$content_classes[] = 'obs-animate-repeat';
|
||||
}
|
||||
}
|
||||
echo '<div class="' . esc_attr(implode(' ', $content_classes)) . '">' . $content . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo (string) ($args['after_widget'] ?? '');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@ final class PostQuery {
|
||||
$args['paged'] = 1;
|
||||
}
|
||||
|
||||
// The modern tab widget uses Zibll's signed `cat` syntax. Legacy
|
||||
// The modern post widgets use Zibll's signed `cat` syntax. Legacy
|
||||
// category_ids remains available only to the unrelated generic posts
|
||||
// widgets; a tab segment must never fall back to it.
|
||||
$is_reference_segment = array_key_exists('cat', $segment) || array_key_exists('topics', $segment);
|
||||
$is_reference_segment = array_key_exists('cat', $segment);
|
||||
$cat_raw = array_key_exists('cat', $config) ? (string) $config['cat'] : ($is_reference_segment ? '' : (string) ($config['category_ids'] ?? ''));
|
||||
$categories = self::signed_ids($cat_raw);
|
||||
if (!empty($config['category_id'])) $categories = [(int) $config['category_id']];
|
||||
@@ -44,17 +44,6 @@ final class PostQuery {
|
||||
if ($tags) {
|
||||
$args['tag__in'] = $tags;
|
||||
}
|
||||
$topics_raw = (string) ($config['topics'] ?? '');
|
||||
$topics = self::signed_ids($topics_raw);
|
||||
if ($topics) {
|
||||
$tax_query = [];
|
||||
$topic_in = array_values(array_filter($topics, static fn(int $id): bool => $id > 0));
|
||||
$topic_not = array_values(array_map('abs', array_filter($topics, static fn(int $id): bool => $id < 0)));
|
||||
if ($topic_in) $tax_query[] = ['taxonomy' => 'topics', 'field' => 'term_id', 'terms' => $topic_in, 'include_children' => true];
|
||||
if ($topic_not) $tax_query[] = ['taxonomy' => 'topics', 'field' => 'term_id', 'terms' => $topic_not, 'operator' => 'NOT IN', 'include_children' => true];
|
||||
if (count($tax_query) > 1) $tax_query = array_merge(['relation' => 'AND'], $tax_query);
|
||||
if ($tax_query) $args['tax_query'] = $tax_query;
|
||||
}
|
||||
|
||||
$days = max(0, (int) ($config['limit_day'] ?? 0));
|
||||
if ($days > 0) {
|
||||
@@ -65,7 +54,7 @@ final class PostQuery {
|
||||
$args['date_query'] = [['after' => $format($now - ($days * DAY_IN_SECONDS)), 'before' => $format($now), 'inclusive' => true]];
|
||||
}
|
||||
self::apply_order($args, (string) ($config['orderby'] ?? 'date'), (string) ($config['order'] ?? 'DESC'));
|
||||
$pay = $is_reference_segment ? '' : ($config['pay_type'] ?? '');
|
||||
$pay = $is_reference_segment ? ($config['zibpay_type'] ?? []) : ($config['pay_type'] ?? '');
|
||||
self::apply_pay_type($args, $pay);
|
||||
return $args;
|
||||
}
|
||||
@@ -91,16 +80,15 @@ final class PostQuery {
|
||||
$args['order'] = strtoupper($order) === 'ASC' ? 'ASC' : 'DESC';
|
||||
$meta = [
|
||||
'views' => class_exists('AKSRT\\Core\\DomainModel') ? \AKSRT\Core\DomainModel::POST_VIEW_COUNT_KEY : 'aksrt_post_views',
|
||||
'like' => '_aksrt_like_count', 'likes' => '_aksrt_like_count',
|
||||
'favorite' => '_aksrt_favorite_count', 'favorites' => '_aksrt_favorite_count',
|
||||
'downloads' => class_exists('AKSRT\\Core\\DomainModel') ? \AKSRT\Core\DomainModel::RESOURCE_DOWNLOAD_COUNT_KEY : '_aksrt_res_download_count',
|
||||
'like' => '_aksrt_like_count',
|
||||
'favorite' => '_aksrt_favorite_count',
|
||||
];
|
||||
if (isset($meta[$orderby])) {
|
||||
$args['meta_key'] = $meta[$orderby];
|
||||
$args['orderby'] = 'meta_value_num';
|
||||
return;
|
||||
}
|
||||
if ($orderby === 'comments' || $orderby === 'comment_count') {
|
||||
if ($orderby === 'comment_count') {
|
||||
$args['orderby'] = 'comment_count';
|
||||
} elseif (in_array($orderby, ['date', 'modified', 'rand'], true)) {
|
||||
$args['orderby'] = $orderby;
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace AKSRT\WidgetSystem;
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Shared list/card/mini markup and pagination payloads.
|
||||
* Shared list/card/mini markup and pagination payloads (reference parity).
|
||||
*/
|
||||
final class PostRenderer {
|
||||
public static function payload(array $instance, string $slug, int $page = 1, string $segment = ''): array {
|
||||
@@ -26,35 +26,32 @@ final class PostRenderer {
|
||||
|
||||
public static function items(\WP_Query $query, array $instance, string $slug, int $page = 1): string {
|
||||
if (!$query->have_posts()) {
|
||||
return RendererHelpers::empty_state(__('暂无内容', 'aksrt'));
|
||||
return function_exists('aksrt_widget_ajax_null') ? aksrt_widget_ajax_null() : RendererHelpers::empty_state(__('暂无内容', 'aksrt'));
|
||||
}
|
||||
$style = (string) ($instance['style'] ?? 'card');
|
||||
$mode = in_array($style, ['list', 'card', 'mini'], true) ? $style : 'card';
|
||||
$mini_opt = (array) ($instance['mini_opt'] ?? []);
|
||||
$config = [
|
||||
'type' => $mode === 'card' ? 'card' : 'list',
|
||||
'show_number' => $slug === 'hot_posts' || in_array('show_number', (array) ($instance['mini_opt'] ?? []), true),
|
||||
'show_thumb' => $slug === 'tab_posts'
|
||||
? in_array('show_thumb', (array) ($instance['mini_opt'] ?? []), true)
|
||||
: ($slug !== 'hot_posts' || !array_key_exists('show_thumbnail', $instance) || !empty($instance['show_thumbnail'])),
|
||||
'show_meta' => $slug !== 'tab_posts' || in_array('show_meta', (array) ($instance['mini_opt'] ?? []), true),
|
||||
'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),
|
||||
'number_offset' => ($page - 1) * max(1, (int) ($instance['count'] ?? 8)),
|
||||
'wrapper_class' => 'aksrt-widget-posts aksrt-widget-posts--' . $slug,
|
||||
'widget_style' => $mode,
|
||||
];
|
||||
if ($slug === 'oneline_posts') {
|
||||
return self::oneline($query, $config, !empty($instance['autoplay']));
|
||||
return self::oneline($query, $config, $mode);
|
||||
}
|
||||
if ($mode === 'mini') {
|
||||
return aksrt_render_mini_list($query, $config, false);
|
||||
return aksrt_render_mini_list($query, array_merge($config, ['class' => 'ajax-item']), false);
|
||||
}
|
||||
return aksrt_render_post_list($config, $query, false);
|
||||
// The .widget-ajaxpager container rendered by the caller is the ajax
|
||||
// target, so item markup goes in without the page-level list wrapper.
|
||||
return aksrt_render_post_list(array_merge($config, ['skip_wrapper' => true]), $query, false);
|
||||
}
|
||||
|
||||
private static function oneline(\WP_Query $query, array $config, bool $autoplay): string {
|
||||
private static function oneline(\WP_Query $query, array $config, string $style): string {
|
||||
$items = '';
|
||||
$style = in_array(($config['widget_style'] ?? 'card'), ['list', 'card', 'mini'], true)
|
||||
? (string) $config['widget_style']
|
||||
: 'card';
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
if ($style === 'mini') {
|
||||
@@ -74,30 +71,37 @@ final class PostRenderer {
|
||||
}
|
||||
}
|
||||
wp_reset_postdata();
|
||||
$autoplay_attr = $autoplay ? ' data-autoplay="true"' : '';
|
||||
return '<div class="widget-main-post mb20 style-' . esc_attr($style) . '"><div class="swiper-container swiper-scroll" data-slideClass="posts-item"' . $autoplay_attr . '><div class="swiper-wrapper">' . $items . '</div><div class="swiper-button-prev"></div><div class="swiper-button-next"></div></div></div>';
|
||||
return '<div class="widget-main-post mb20 style-' . esc_attr($style) . '"><div class="swiper-container swiper-scroll" data-slideClass="posts-item"><div class="swiper-wrapper">' . $items . '</div><div class="swiper-button-prev"></div><div class="swiper-button-next"></div></div></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Pagination contract: '' keeps the hidden stub, ajax renders the load
|
||||
* more trigger, number renders numbered page links; all targets carry the
|
||||
* `.widget-ajaxpager` reference attribute. Interactive controls stay clear
|
||||
* of the runtime's legacy `.ajax-next` HTML pipeline — the widget system
|
||||
* owns the REST client in frontend.js.
|
||||
*/
|
||||
private static function pagination(array $instance, int $page, int $max_pages): string {
|
||||
$mode = (string) ($instance['paginate'] ?? '');
|
||||
if ($mode === '' || $mode === 'none') {
|
||||
return '<div class="ajax-pag hide"><div class="next-page"><a href="#"></a></div></div>';
|
||||
}
|
||||
if ($max_pages <= 1) {
|
||||
return '';
|
||||
}
|
||||
if ($mode === '' || $mode === 'none') {
|
||||
return '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
|
||||
}
|
||||
if ($mode === 'number' || $mode === 'numbered') {
|
||||
$links = '';
|
||||
$start = max(1, min($page - 2, $max_pages - 4));
|
||||
$end = min($max_pages, max(5, $page + 2));
|
||||
$html = '<nav class="aksrt-widget-pagination" aria-label="' . esc_attr__('文章分页', 'aksrt') . '">';
|
||||
$end = min($max_pages, max($start + 4, $page + 2));
|
||||
for ($number = $start; $number <= $end; $number++) {
|
||||
$html .= '<button type="button" data-widget-page="' . $number . '"' . ($number === $page ? ' aria-current="page"' : '') . '>' . $number . '</button>';
|
||||
if ($number === $page) {
|
||||
$links .= '<span class="page-numbers current">' . $number . '</span>';
|
||||
continue;
|
||||
}
|
||||
$links .= '<a class="next-page page-numbers" ajaxpager-target=".widget-ajaxpager" ajax-replace="true" data-widget-page="' . $number . '" role="button" tabindex="0" href="#page-' . $number . '">' . $number . '</a>';
|
||||
}
|
||||
return $html . '</nav>';
|
||||
return '<div class="pagenav ajax-pag" role="navigation" aria-label="' . esc_attr__('文章分页', 'aksrt') . '">' . $links . '</div>';
|
||||
}
|
||||
$label = __('加载更多', 'aksrt');
|
||||
$class = 'aksrt-widget-load-more';
|
||||
$action = '';
|
||||
return '<button class="' . $class . '"' . $action . ' type="button" data-next-page="' . (int) ($page + 1) . '" data-mode="' . esc_attr($mode) . '">' . esc_html($label) . '</button>';
|
||||
return '<div class="text-center theme-pagination ajax-pag"><div no-scroll="true" ajaxpager-target=".widget-ajaxpager" class="next-page"><a role="button" tabindex="0" href="#next" data-next-page="' . (int) ($page + 1) . '">' . esc_html__('加载更多', 'aksrt') . '</a></div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,6 @@ final class RendererRegistry {
|
||||
}
|
||||
|
||||
public static function supports_items(string $slug): bool {
|
||||
return in_array($slug, ['posts', 'tab_posts', 'oneline_posts', 'hot_posts'], true);
|
||||
return in_array($slug, ['posts', 'tab_posts', 'oneline_posts'], true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use AKSRT\WidgetSystem\RendererHelpers;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/** Render article discovery and paid-content widgets. */
|
||||
/** Render article discovery and paid-content widgets (reference parity). */
|
||||
final class ContentRenderer {
|
||||
public static function render(string $slug, array $instance, array $context = []): string {
|
||||
switch ($slug) {
|
||||
@@ -15,110 +15,71 @@ final class ContentRenderer {
|
||||
case 'term_aggregation': return self::term_aggregation($instance);
|
||||
case 'post_pay': return self::post_pay($instance);
|
||||
case 'tab_posts': return self::tab_posts($instance, $context);
|
||||
case 'posts':
|
||||
case 'hot_posts':
|
||||
case 'oneline_posts': return self::posts($slug, $instance, $context);
|
||||
case 'posts': return self::posts($instance, $context, 'posts');
|
||||
case 'hot_posts': return function_exists('aksrt_widget_hot_posts') ? aksrt_widget_hot_posts($instance) : '';
|
||||
case 'oneline_posts': return self::posts($instance, $context, 'oneline_posts');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private static function terms(array $instance): array {
|
||||
$args = [
|
||||
'taxonomy' => (string) $instance['taxonomy'],
|
||||
'number' => (int) $instance['count'],
|
||||
'hide_empty' => true,
|
||||
'orderby' => 'count',
|
||||
'order' => 'DESC',
|
||||
];
|
||||
$ids = RendererHelpers::ids((string) $instance['term_ids']);
|
||||
if ($ids) {
|
||||
$args['include'] = $ids;
|
||||
$args['orderby'] = 'include';
|
||||
}
|
||||
$terms = get_terms($args);
|
||||
return is_wp_error($terms) ? [] : (array) $terms;
|
||||
}
|
||||
|
||||
private static function term_cards(array $instance): string {
|
||||
$terms = self::terms($instance);
|
||||
if (!$terms) {
|
||||
return RendererHelpers::empty_state(__('暂无分类', 'aksrt'));
|
||||
}
|
||||
$html = '<div class="aksrt-widget-grid aksrt-term-cards" style="--aksrt-cols:' . (int) $instance['pc_row'] . ';--aksrt-cols-m:' . (int) $instance['m_row'] . '">';
|
||||
foreach ($terms as $term) {
|
||||
$link = get_term_link($term);
|
||||
if (is_wp_error($link)) {
|
||||
continue;
|
||||
}
|
||||
$image = (string) get_term_meta((int) $term->term_id, 'aksrt_cover_url', true);
|
||||
if ($image === '') {
|
||||
$image = (string) get_term_meta((int) $term->term_id, 'cover_url', true);
|
||||
}
|
||||
$html .= '<a class="aksrt-term-card" href="' . esc_url($link) . '"><span class="aksrt-term-card__media">' . RendererHelpers::image($image, $term->name) . '</span><span class="aksrt-term-card__body"><strong>' . esc_html($term->name) . '</strong><small>' . esc_html(wp_trim_words((string) $term->description, 18)) . '</small><em>' . sprintf(esc_html__('%s 篇内容', 'aksrt'), number_format_i18n((int) $term->count)) . '</em></span></a>';
|
||||
}
|
||||
return $html . '</div>';
|
||||
}
|
||||
|
||||
private static function term_aggregation(array $instance): string {
|
||||
$terms = self::terms($instance);
|
||||
if (!$terms) {
|
||||
return RendererHelpers::empty_state(__('暂无聚合内容', 'aksrt'));
|
||||
}
|
||||
$taxonomy = (string) $instance['taxonomy'];
|
||||
$term_ids = array_map(static fn($term): int => (int) $term->term_id, $terms);
|
||||
$query = new \WP_Query([
|
||||
'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => min(100, count($terms) * 6),
|
||||
'ignore_sticky_posts' => true, 'no_found_rows' => true,
|
||||
'tax_query' => [['taxonomy' => $taxonomy, 'field' => 'term_id', 'terms' => $term_ids]],
|
||||
]);
|
||||
$by_term = [];
|
||||
foreach ($query->posts as $post) {
|
||||
foreach (wp_get_post_terms((int) $post->ID, $taxonomy, ['fields' => 'ids']) as $term_id) {
|
||||
if (in_array((int) $term_id, $term_ids, true) && count($by_term[$term_id] ?? []) < 4) {
|
||||
$by_term[$term_id][] = $post;
|
||||
}
|
||||
}
|
||||
}
|
||||
$html = '<div class="aksrt-term-aggregation">';
|
||||
foreach ($terms as $term) {
|
||||
$link = get_term_link($term);
|
||||
if (is_wp_error($link)) {
|
||||
continue;
|
||||
}
|
||||
$html .= '<section class="aksrt-term-aggregation__card"><header><a href="' . esc_url($link) . '"><strong>' . esc_html($term->name) . '</strong><span>' . esc_html__('查看全部', 'aksrt') . '</span></a></header><ul>';
|
||||
foreach ($by_term[$term->term_id] ?? [] as $post) {
|
||||
$html .= '<li><a href="' . esc_url(get_permalink($post)) . '">' . get_the_post_thumbnail($post, 'thumbnail') . '<span>' . esc_html(get_the_title($post)) . '</span></a></li>';
|
||||
}
|
||||
$html .= '</ul></section>';
|
||||
}
|
||||
return $html . '</div>';
|
||||
}
|
||||
|
||||
private static function posts(string $slug, array $instance, array $context): string {
|
||||
private static function posts(array $instance, array $context, string $slug): string {
|
||||
$widget_id = (string) ($context['widget_id'] ?? '');
|
||||
if (($instance['load_mode'] ?? 'sync') === 'ajax') {
|
||||
return '<div class="aksrt-widget-items is-loading" data-widget-items data-widget="' . esc_attr($slug) . '" data-instance="' . esc_attr($widget_id) . '" data-page="1" aria-live="polite" aria-busy="true"><div class="aksrt-widget-skeleton" role="status" aria-label="' . esc_attr__('正在加载', 'aksrt') . '"></div></div>';
|
||||
$style = in_array(($instance['style'] ?? 'list'), ['list', 'card', 'mini'], true) ? (string) $instance['style'] : 'list';
|
||||
if ($slug === 'oneline_posts') {
|
||||
// Reference parity: the swiper row is fully rendered server-side.
|
||||
$query = \AKSRT\WidgetSystem\PostQuery::build(array_merge($instance, ['paginate' => '']), 1, []);
|
||||
return \AKSRT\WidgetSystem\PostRenderer::items($query, $instance, 'oneline_posts');
|
||||
}
|
||||
$payload = PostRenderer::payload($instance, $slug, 1);
|
||||
return '<div class="aksrt-widget-items" data-widget-items data-widget="' . esc_attr($slug) . '" data-instance="' . esc_attr($widget_id) . '" data-page="1" aria-live="polite" aria-busy="false">' . $payload['html'] . $payload['paginate_html'] . '</div>';
|
||||
$inner = self::ajaxpager($instance, $slug, $widget_id, 1, '');
|
||||
$html = '<div class="widget-main-post mb20 style-' . esc_attr($style) . '">';
|
||||
$html .= $style === 'mini' ? '<div class="zib-widget posts-mini-lists">' : '';
|
||||
$html .= $inner;
|
||||
$html .= $style === 'mini' ? '</div>' : '';
|
||||
return $html . '</div>';
|
||||
}
|
||||
|
||||
/** The .widget-ajaxpager container contract shared by posts and tab panes. */
|
||||
private static function ajaxpager(array $instance, string $slug, string $widget_id, int $page, string $segment): string {
|
||||
$attrs = ' data-widget-items data-widget="' . esc_attr($slug) . '" data-instance="' . esc_attr($widget_id) . '" data-page="' . (int) $page . '"';
|
||||
if ($segment !== '') {
|
||||
$attrs .= ' data-segment="' . esc_attr($segment) . '"';
|
||||
}
|
||||
if (($instance['load_mode'] ?? '') === 'ajax') {
|
||||
// The runtime (lazysizes + lazyload-action) reveals this container
|
||||
// and clicks the single .ias-btn trigger; frontend.js then loads
|
||||
// the first page through the REST client.
|
||||
$trigger = '<span class="hide aksrt-ias-trigger"><a class="ias-btn" no-scroll="true" href="javascript:void(0)" data-widget-ias="1" data-next-page="1" aria-hidden="true" tabindex="-1"></a></span>';
|
||||
return '<div class="widget-ajaxpager lazyload" lazyload-action="ias"' . $attrs . ' aria-live="polite" aria-busy="true">' . $trigger . '<div class="post_ajax_loader" role="status" aria-label="' . esc_attr__('正在加载', 'aksrt') . '">' . self::placeholder($instance) . '</div></div>';
|
||||
}
|
||||
$payload = PostRenderer::payload($instance, $slug, $page, $segment);
|
||||
return '<div class="widget-ajaxpager"' . $attrs . ' aria-live="polite" aria-busy="false">' . $payload['html'] . $payload['paginate_html'] . '</div>';
|
||||
}
|
||||
|
||||
private static function placeholder(array $instance): string {
|
||||
$style = (string) ($instance['style'] ?? 'list');
|
||||
$count = max(1, (int) ($instance['count'] ?? 6));
|
||||
if ($style === 'mini') {
|
||||
$mini_opt = (array) ($instance['mini_opt'] ?? []);
|
||||
if (in_array('show_thumb', $mini_opt, true)) {
|
||||
$item = '<div class="posts-mini "><div class="mr10"><div class="item-thumbnail placeholder"></div></div><div class="posts-mini-con flex xx flex1 jsb"><div class="placeholder t1"></div><div class="placeholder s1"></div></div></div>';
|
||||
return str_repeat($item, $count);
|
||||
}
|
||||
return str_repeat('<div class="posts-mini"><div class="placeholder k1"></div></div>', $count);
|
||||
}
|
||||
return aksrt_render_placeholder($style === 'card' ? 'card' : 'lists', $count);
|
||||
}
|
||||
|
||||
private static function tab_posts(array $instance, array $context): string {
|
||||
$style = in_array(($instance['style'] ?? 'mini'), ['list', 'card', 'mini'], true) ? (string) $instance['style'] : 'mini';
|
||||
$tabs = array_values((array) ($instance['tabs'] ?? []));
|
||||
if (!$tabs) {
|
||||
return RendererHelpers::empty_state(__('请配置文章栏目', 'aksrt'));
|
||||
return '';
|
||||
}
|
||||
$widget_id = (string) ($context['widget_id'] ?? 'preview');
|
||||
$buttons = '';
|
||||
$key = sanitize_html_class((string) ($context['widget_id'] ?? 'preview')) ?: 'preview';
|
||||
$panel_id = 'aksrt-tab-panel-' . $key;
|
||||
$ajax_url = rest_url('aksrt/v1/widgets/tab_posts/items');
|
||||
$first_tab_id = 'aksrt-tab-' . $key . '-0';
|
||||
$placeholder_type = $style === 'card' ? 'card' : 'lists';
|
||||
$placeholder = $style === 'mini'
|
||||
? str_repeat('<div class="posts-mini"><div class="placeholder k1"></div></div>', max(1, (int) ($instance['count'] ?? 6)))
|
||||
: aksrt_render_placeholder($placeholder_type, max(1, (int) ($instance['count'] ?? 6)));
|
||||
$panels = '';
|
||||
$key = sanitize_html_class($widget_id) ?: 'preview';
|
||||
$panel_id = 'aksrt-tab-panel-' . $key;
|
||||
$visible_index = 0;
|
||||
foreach ($tabs as $index => $tab) {
|
||||
if (!is_array($tab) || trim((string) ($tab['title'] ?? '')) === '') continue;
|
||||
@@ -126,16 +87,117 @@ final class ContentRenderer {
|
||||
$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>';
|
||||
if ($selected) {
|
||||
$payload = PostRenderer::payload($instance, 'tab_posts', 1, (string) $index);
|
||||
$body = $payload['html'] . $payload['paginate_html'] . '<div class="post_ajax_loader" style="display:none;">' . $placeholder . '</div>';
|
||||
$body = self::ajaxpager($instance, 'tab_posts', $widget_id, 1, (string) $index);
|
||||
} else {
|
||||
$body = '<span class="post_ajax_trigger hide"><a ajaxpager-target=".widget-ajaxpager" class="ajax_load ajax-next ajax-open" data-aksrt-load="1" no-scroll="true" href="' . esc_url(add_query_arg(['widget' => 'tab_posts', 'instance' => (string) ($context['widget_id'] ?? ''), 'page' => 1, 'segment' => $index], $ajax_url)) . '"></a></span><div class="post_ajax_loader" style="display:none;">' . $placeholder . '</div>';
|
||||
// Inactive panes load lazily through the REST client on switch.
|
||||
$body = '<div class="widget-ajaxpager" data-widget-items data-widget="tab_posts" data-instance="' . esc_attr($widget_id) . '" data-page="1" data-segment="' . (int) $index . '" aria-live="polite" aria-busy="false"></div>';
|
||||
}
|
||||
$panels .= '<div class="tab-pane fade' . ($selected ? ' active in' : '') . '" id="' . esc_attr($panel_id . '-' . $visible_index) . '" role="tabpanel" aria-labelledby="' . esc_attr($tab_id) . '"><div class="widget-ajaxpager" data-widget-items data-widget="tab_posts" data-instance="' . esc_attr((string) ($context['widget_id'] ?? '')) . '" data-segment="' . (int) $index . '" aria-live="polite" aria-busy="false">' . $body . '</div></div>';
|
||||
$body .= '<div class="post_ajax_loader" style="display:none;">' . self::placeholder($instance) . '</div>';
|
||||
$panels .= '<div class="tab-pane fade' . ($selected ? ' active in' : '') . '" id="' . esc_attr($panel_id . '-' . $visible_index) . '" role="tabpanel" aria-labelledby="' . esc_attr($tab_id) . '">' . $body . '</div>';
|
||||
$visible_index++;
|
||||
}
|
||||
if ($buttons === '') return RendererHelpers::empty_state(__('请配置文章栏目', 'aksrt'));
|
||||
return '<div class="widget-tab-post style-' . esc_attr($style) . ($style === 'mini' ? ' posts-mini-lists zib-widget' : ' index-tab') . '" data-widget="tab_posts" data-instance="' . esc_attr((string) ($context['widget_id'] ?? '')) . '"><div class="relative' . ($style === 'card' ? ' mb20' : '') . '"><ul class="list-inline scroll-x no-scrollbar' . ($style === 'mini' ? ' tab-nav-theme' : '') . '" role="tablist">' . $buttons . '</ul></div><div class="tab-content">' . $panels . '</div></div>';
|
||||
if ($buttons === '') return '';
|
||||
return '<div class="widget-tab-post style-' . esc_attr($style) . ($style === 'mini' ? ' posts-mini-lists zib-widget' : ' index-tab relative-h') . '" data-widget="tab_posts" data-instance="' . esc_attr($widget_id) . '"><div class="relative' . ($style === 'card' ? ' mb20' : '') . '"><ul class="list-inline scroll-x no-scrollbar' . ($style === 'mini' ? ' tab-nav-theme' : '') . '" role="tablist">' . $buttons . '</ul></div><div class="tab-content">' . $panels . '</div></div>';
|
||||
}
|
||||
|
||||
private static function term_cards(array $instance): string {
|
||||
$term_ids = array_values(array_filter(array_map('absint', (array) ($instance['term_id'] ?? []))));
|
||||
if (!$term_ids) {
|
||||
return '';
|
||||
}
|
||||
$terms = get_terms(['taxonomy' => 'category', 'include' => $term_ids, 'orderby' => 'include']);
|
||||
if (is_wp_error($terms) || !$terms) {
|
||||
return '';
|
||||
}
|
||||
$pc_row = max(1, (int) ($instance['pc_row'] ?? 2));
|
||||
$m_row = max(1, (int) ($instance['m_row'] ?? 2));
|
||||
$row_class = 'col-sm-' . (int) (12 / $pc_row);
|
||||
$row_class .= $m_row > 1 ? ' col-xs-' . (int) (12 / $m_row) : '';
|
||||
$is_row = count($terms) > 1;
|
||||
$target_blank = !empty($instance['target_blank']) ? '_blank' : '';
|
||||
$animation = (string) ($instance['obs_animation'] ?? '');
|
||||
$html = '';
|
||||
foreach ($terms as $term) {
|
||||
$count = (int) $term->count;
|
||||
$name = aksrt_widget_str_cut((string) $term->name, 16);
|
||||
$description = aksrt_widget_str_cut((string) $term->description, 24);
|
||||
$href = get_term_link($term);
|
||||
if (is_wp_error($href)) {
|
||||
continue;
|
||||
}
|
||||
$icon = '<i class="fa fa-folder-open-o mr6" aria-hidden="true"></i>';
|
||||
$card = [
|
||||
'type' => (string) ($instance['type'] ?? 'style-1'),
|
||||
'class' => 'mb10',
|
||||
'img' => aksrt_widget_term_cover_url((int) $term->term_id),
|
||||
'alt' => $name . '-' . $description,
|
||||
'link' => ['url' => esc_url((string) $href), 'target' => $target_blank],
|
||||
'text1' => $name,
|
||||
'text2' => $description,
|
||||
'text3' => $icon . $count . __('篇文章', 'aksrt'),
|
||||
'height_scale' => (int) ($instance['height_scale'] ?? 60),
|
||||
'mask_opacity' => (int) ($instance['mask_opacity'] ?? 10),
|
||||
];
|
||||
$type = (string) $card['type'];
|
||||
if ($type === 'style-2' || $type === 'style-4') {
|
||||
$card['text3'] = '<item data-toggle="tooltip" title="' . esc_attr(sprintf(__('共%d篇文章', 'aksrt'), $count)) . '">' . $icon . $count . '</item>';
|
||||
} elseif ($type === 'style-3') {
|
||||
$card['text1'] = $icon . $name;
|
||||
$card['text3'] = '<i class="fa mr6 fa-file-text-o" aria-hidden="true"></i>' . $count . __('篇文章', 'aksrt');
|
||||
}
|
||||
|
||||
$attr = '';
|
||||
$col_class = '';
|
||||
if ($animation !== '') {
|
||||
$col_class .= ' obs-animate ani-' . sanitize_html_class($animation);
|
||||
$attr .= ' data-animation="' . esc_attr($animation) . '"';
|
||||
if (!empty($instance['animation_repeat'])) {
|
||||
$attr .= ' data-animation-repeat="true"';
|
||||
}
|
||||
}
|
||||
if ($is_row) {
|
||||
$col_class .= ' ' . $row_class;
|
||||
}
|
||||
$html .= '<div class="' . esc_attr(trim($col_class)) . '"' . $attr . '>' . aksrt_graphic_card($card) . '</div>';
|
||||
}
|
||||
return '<div class="' . ($is_row ? 'mb10' : 'mb20') . '">' . ($is_row ? '<div class="row gutters-5">' : '') . $html . ($is_row ? '</div>' : '') . '</div>';
|
||||
}
|
||||
|
||||
private static function term_aggregation(array $instance): string {
|
||||
$term_ids = array_values(array_filter(array_map('absint', (array) ($instance['term_id'] ?? []))));
|
||||
if (!$term_ids) {
|
||||
return '';
|
||||
}
|
||||
$pc_row = max(1, min(2, (int) ($instance['pc_row'] ?? 2)));
|
||||
$row_class = 'col-sm-' . (int) (12 / $pc_row) . ' col-xs-12';
|
||||
$is_row = count($term_ids) > 1;
|
||||
$animation = (string) ($instance['obs_animation'] ?? '');
|
||||
$html = '';
|
||||
foreach ($term_ids as $key => $term_id) {
|
||||
$attr = '';
|
||||
$col_class = '';
|
||||
if ($animation !== '') {
|
||||
$col_class .= ' obs-animate ani-' . sanitize_html_class($animation);
|
||||
$attr .= ' data-animation="' . esc_attr($animation) . '"';
|
||||
$attr .= ' style=" --delay: ' . esc_attr((string) ($key * 0.15)) . 's; "';
|
||||
if (!empty($instance['animation_repeat'])) {
|
||||
$attr .= ' data-animation-repeat="true"';
|
||||
}
|
||||
}
|
||||
if ($is_row) {
|
||||
$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 .= '</div>';
|
||||
}
|
||||
return '<div class="clearfix mb6">' . ($is_row ? '<div class="row gutters-5">' : '') . $html . ($is_row ? '</div>' : '') . '</div>';
|
||||
}
|
||||
|
||||
private static function post_pay(array $instance): string {
|
||||
|
||||
@@ -25,7 +25,12 @@ final class GeneralRenderer {
|
||||
if (!is_singular('post')) {
|
||||
return '';
|
||||
}
|
||||
return '<nav class="aksrt-widget-toc' . (!empty($i['collapsed']) ? ' is-collapsed' : '') . '" data-min-headings="' . (int) $i['min_headings'] . '" data-scroll-offset="' . (int) $i['offset'] . '" aria-label="' . esc_attr__('文章目录', 'aksrt') . '"><button class="aksrt-widget-toc__toggle" type="button" aria-expanded="' . (empty($i['collapsed']) ? 'true' : 'false') . '"><span>' . esc_html__('文章目录', 'aksrt') . '</span><i class="fa fas fa-chevron-down" aria-hidden="true"></i></button><ol class="aksrt-widget-toc__list"></ol><p class="aksrt-widget-toc__empty">' . esc_html__('正在生成目录…', 'aksrt') . '</p></nav>';
|
||||
$title = esc_html((string) ($i['title'] ?? '文章目录'));
|
||||
if (!empty($i['mini_title'])) {
|
||||
$title .= '<small class="ml10">' . esc_html((string) $i['mini_title']) . '</small>';
|
||||
}
|
||||
$affix = !empty($i['in_affix']) ? ' data-affix="true"' : '';
|
||||
return '<div class="widget-container"><div' . $affix . ' class="posts-nav-box" data-title="' . esc_attr(wp_kses_post($title)) . '"></div></div>';
|
||||
}
|
||||
|
||||
private static function render_quote(array $i): string {
|
||||
@@ -112,44 +117,305 @@ final class GeneralRenderer {
|
||||
}
|
||||
|
||||
private static function render_media_cards(array $i): string {
|
||||
return self::media_grid((array) $i['cards']);
|
||||
}
|
||||
|
||||
private static function media_grid(array $items, string $class = ''): string {
|
||||
$html = '<div class="aksrt-widget-grid aksrt-widget-media-cards ' . esc_attr($class) . '">';
|
||||
foreach ($items as $item) {
|
||||
if (is_array($item)) {
|
||||
$html .= RendererHelpers::card_media($item);
|
||||
$pc_row = max(1, (int) ($i['pc_row'] ?? 4));
|
||||
$m_row = max(1, (int) ($i['m_row'] ?? 2));
|
||||
$row_class = 'col-sm-' . (int) (12 / $pc_row);
|
||||
$row_class .= $m_row > 1 ? ' col-xs-' . (int) (12 / $m_row) : '';
|
||||
$covers = (array) ($i['covers'] ?? []);
|
||||
$is_row = count($covers) > 1;
|
||||
$is_mobile = function_exists('wp_is_mobile') && wp_is_mobile();
|
||||
$style = '';
|
||||
$font_pc = (int) ($i['font_size_pc'] ?? 18);
|
||||
$font_m = (int) ($i['font_size_m'] ?? 14);
|
||||
if ($font_pc > 0 && $font_pc !== 14) $style .= '--font-size:' . $font_pc . 'px;';
|
||||
if ($font_m > 0 && $font_m !== 14) $style .= '--font-size-sm:' . $font_m . 'px;';
|
||||
if (!empty($i['font_bold'])) $style .= '--font-weight:bold;--font-weight-sm:bold;';
|
||||
$font_color = (string) ($i['font_color'] ?? '');
|
||||
if ($font_color !== '') $style .= '--color:' . esc_attr($font_color) . ';--color-sm:' . esc_attr($font_color) . ';';
|
||||
$animation = (string) ($i['obs_animation'] ?? '');
|
||||
$html = '';
|
||||
foreach ($covers as $cover) {
|
||||
if (!is_array($cover)) {
|
||||
continue;
|
||||
}
|
||||
$hide = (string) ($cover['hide'] ?? '');
|
||||
if (($is_mobile && $hide === 'm') || (!$is_mobile && $hide === 'pc')) {
|
||||
continue;
|
||||
}
|
||||
$title = (string) ($cover['title'] ?? '');
|
||||
$link = RendererHelpers::link((array) ($cover['link'] ?? []));
|
||||
$card = aksrt_graphic_card([
|
||||
'class' => 'noshadow mb10',
|
||||
'img' => (string) ($cover['image'] ?? ''),
|
||||
'video' => (string) ($cover['video'] ?? ''),
|
||||
'alt' => wp_strip_all_tags($title),
|
||||
'link' => ['url' => $link['url'], 'target' => $link['target']],
|
||||
'more' => trim($title) !== '' ? '<div class="abs-center text-center graphic-text this-font">' . wp_kses_post($title) . '</div>' : '',
|
||||
'height_scale' => (int) ($i['height_scale'] ?? 30),
|
||||
'mask_opacity' => (int) ($i['mask_opacity'] ?? 10),
|
||||
]);
|
||||
if ($card === '') {
|
||||
continue;
|
||||
}
|
||||
$attr = '';
|
||||
$col_class = '';
|
||||
if ($animation !== '') {
|
||||
$col_class .= ' obs-animate ani-' . sanitize_html_class($animation);
|
||||
$attr .= ' data-animation="' . esc_attr($animation) . '"';
|
||||
if (!empty($i['animation_repeat'])) {
|
||||
$attr .= ' data-animation-repeat="true"';
|
||||
}
|
||||
}
|
||||
if ($is_row) {
|
||||
$col_class .= ' ' . $row_class;
|
||||
}
|
||||
$html .= '<div class="' . esc_attr(trim($col_class)) . '"' . $attr . '>' . $card . '</div>';
|
||||
}
|
||||
return $html . '</div>';
|
||||
if ($html === '') {
|
||||
return '';
|
||||
}
|
||||
return '<div class="widget-graphic-cover ' . ($is_row ? 'mb10' : 'mb20') . '"><div' . ($style !== '' ? ' style="' . esc_attr($style) . '"' : '') . '>' . ($is_row ? '<div class="row gutters-5">' : '') . $html . ($is_row ? '</div>' : '') . '</div></div>';
|
||||
}
|
||||
|
||||
private static function render_partners(array $i): string {
|
||||
$items = '';
|
||||
foreach ((array) $i['items'] as $item) {
|
||||
if (is_array($item)) {
|
||||
$items .= RendererHelpers::card_media($item, 'aksrt-partner');
|
||||
$defaults = [
|
||||
'rows' => 1, 'm_rows' => 1, 'style_type' => 'card', 'card_width' => 220, 'card_height' => 90,
|
||||
'speed' => 3, 'direction' => 'alternate', 'pause_hover' => true, 'show_widget_bg' => true,
|
||||
'card_bg' => true, 'card_radius' => 10, 'partners' => [], 'link_type' => 'link',
|
||||
'link_orderby' => 'name', 'link_order' => 'ASC', 'link_cats' => [], 'link_limit' => 50,
|
||||
'go_link' => true, 'end_fade' => true,
|
||||
];
|
||||
$i = array_merge($defaults, $i);
|
||||
$is_mobile = function_exists('wp_is_mobile') && wp_is_mobile();
|
||||
$rows = max(1, (int) ($is_mobile ? $i['m_rows'] : $i['rows']));
|
||||
|
||||
if ((string) $i['link_type'] === 'link') {
|
||||
$args = [
|
||||
'orderby' => (string) ($i['link_orderby'] ?: 'name'),
|
||||
'order' => (string) ($i['page_links_order'] ?: 'ASC'),
|
||||
'limit' => (int) $i['link_limit'] > 0 ? (int) $i['link_limit'] : -1,
|
||||
];
|
||||
$cats = array_values(array_filter(array_map('absint', (array) $i['link_cats'])));
|
||||
if ($cats) {
|
||||
$args['category'] = $cats;
|
||||
}
|
||||
$partners = [];
|
||||
foreach ((array) get_bookmarks($args) as $bookmark) {
|
||||
$partners[] = [
|
||||
'title' => (string) $bookmark->link_name,
|
||||
'desc' => (string) $bookmark->link_description,
|
||||
'image' => (string) $bookmark->link_image,
|
||||
'link' => ['url' => (string) $bookmark->link_url, 'target' => (string) $bookmark->link_target],
|
||||
];
|
||||
}
|
||||
$i['partners'] = $partners;
|
||||
}
|
||||
return '<div class="aksrt-widget-partners" style="--aksrt-partner-rows:' . (int) $i['rows_pc'] . ';--aksrt-partner-rows-m:' . (int) $i['rows_mobile'] . ';--aksrt-partner-speed:' . (int) $i['speed'] . 's"><div class="aksrt-widget-partners__track">' . $items . $items . '</div></div>';
|
||||
|
||||
$partners = array_values(array_filter((array) $i['partners'], 'is_array'));
|
||||
if (!$partners) {
|
||||
return function_exists('is_super_admin') && is_super_admin()
|
||||
? '<div class="c-red muted-box mb20"><b>' . esc_html__('[合作伙伴]横向滚动合作伙伴模块:', 'aksrt') . '</b>' . esc_html__('当前配置下没有可显示的内容,请正确配置或移出该模块', 'aksrt') . '</div>'
|
||||
: '';
|
||||
}
|
||||
|
||||
$rows_data = array_fill(0, $rows, []);
|
||||
foreach ($partners as $index => $partner) {
|
||||
$rows_data[$index % $rows][] = $partner;
|
||||
}
|
||||
$rows_data = array_values(array_filter($rows_data, static fn(array $row): bool => $row !== []));
|
||||
|
||||
$style_vars = [
|
||||
'--zps-card-w:' . (int) $i['card_width'] . 'px',
|
||||
'--zps-card-h:' . (int) $i['card_height'] . 'px',
|
||||
'--zps-card-radius:' . (int) $i['card_radius'] . 'px',
|
||||
'--zps-speed:' . max(0.1, (float) $i['speed']),
|
||||
];
|
||||
$style_attr = ' style="' . esc_attr(implode(';', $style_vars)) . '"';
|
||||
|
||||
$wrap_class = 'zib-partners-scroll';
|
||||
$wrap_class .= ' zps-style-' . sanitize_html_class((string) $i['style_type']);
|
||||
$wrap_class .= !empty($i['pause_hover']) ? ' zps-pause-hover' : '';
|
||||
$wrap_class .= !empty($i['card_bg']) ? ' zps-card-bg' : '';
|
||||
$wrap_class .= !empty($i['end_fade']) ? ' zps-end-fade' : '';
|
||||
|
||||
$html = '';
|
||||
foreach ($rows_data as $row_index => $row_partners) {
|
||||
$direction = (string) $i['direction'];
|
||||
if ($direction === 'left' || $direction === 'right') {
|
||||
$dir = $direction;
|
||||
} else {
|
||||
$dir = $row_index % 2 === 0 ? 'left' : 'right';
|
||||
}
|
||||
$html .= '<div class="zps-row zps-dir-' . $dir . '" data-zps-row><div class="zps-track" data-zps-track><div class="zps-group flex shrink0">';
|
||||
foreach ($row_partners as $partner) {
|
||||
$html .= self::partners_card($partner, (string) $i['style_type'], !empty($i['go_link']));
|
||||
}
|
||||
$html .= '</div></div></div>';
|
||||
}
|
||||
if (trim($html) === '') {
|
||||
return '';
|
||||
}
|
||||
return '<div class="' . (!empty($i['show_widget_bg']) ? 'zib-widget' : 'mb20') . '"><div class="' . esc_attr($wrap_class) . '"' . $style_attr . '>' . $html . '</div></div>';
|
||||
}
|
||||
|
||||
private static function partners_card(array $partner, string $style_type, bool $go_link): string {
|
||||
$partner = wp_parse_args($partner, ['image' => '', 'link' => [], 'title' => '', 'desc' => '']);
|
||||
$title = trim((string) $partner['title']);
|
||||
$desc = trim((string) $partner['desc']);
|
||||
$image = esc_url((string) $partner['image']);
|
||||
if ($image === '' && $title === '') {
|
||||
return '';
|
||||
}
|
||||
$img_tag = $image !== '' ? '<img class="zps-img" src="' . $image . '" alt="' . esc_attr($title !== '' ? $title : __('合作伙伴', 'aksrt')) . '">' : '';
|
||||
|
||||
$inner = '';
|
||||
if ($style_type === 'logo') {
|
||||
if ($image === '') {
|
||||
return '';
|
||||
}
|
||||
$inner .= $img_tag;
|
||||
} elseif ($style_type === 'chip') {
|
||||
if ($img_tag !== '') {
|
||||
$inner .= '<span class="zps-chip-icon flex jc shrink0 mr6">' . $img_tag . '</span>';
|
||||
}
|
||||
$inner .= '<span class="zps-chip-title text-ellipsis">' . esc_html($title) . '</span>';
|
||||
} else {
|
||||
if ($img_tag !== '') {
|
||||
$inner .= '<div class="zps-card-icon flex jc shrink0 mr10">' . $img_tag . '</div>';
|
||||
}
|
||||
$inner .= '<div class="zps-card-text flex xx flex1">';
|
||||
if ($title !== '') {
|
||||
$inner .= '<div class="zps-card-title text-ellipsis font-bold">' . esc_html($title) . '</div>';
|
||||
}
|
||||
if ($desc !== '') {
|
||||
$inner .= '<div class="zps-card-desc text-ellipsis muted-color em09 mt3">' . esc_html($desc) . '</div>';
|
||||
}
|
||||
$inner .= '</div>';
|
||||
}
|
||||
|
||||
$link = RendererHelpers::link((array) ($partner['link'] ?? []));
|
||||
$url = $link['url'] !== '' ? $link['url'] : 'javascript:void(0)';
|
||||
if ($go_link && $url !== 'javascript:void(0)' && function_exists('aksrt_go_link')) {
|
||||
$url = esc_url(aksrt_go_link($link['url'], true));
|
||||
}
|
||||
$target = $link['target'] === '_blank' ? ' target="_blank" rel="noopener noreferrer"' : '';
|
||||
$base_class = 'zps-item flex ac shrink0';
|
||||
if ($url !== 'javascript:void(0)') {
|
||||
return '<a class="' . $base_class . '" href="' . $url . '"' . $target . '>' . $inner . '</a>';
|
||||
}
|
||||
return '<div class="' . $base_class . '">' . $inner . '</div>';
|
||||
}
|
||||
|
||||
private static function render_combo_info(array $i): string {
|
||||
$html = '<div class="aksrt-widget-grid aksrt-widget-combo">';
|
||||
foreach ((array) $i['items'] as $item) {
|
||||
if (!is_array($item)) {
|
||||
$i = array_merge([
|
||||
'pc_row' => 4, 'm_row' => 1, 'card_radius' => 12, 'top_band' => true, 'top_band_height' => 70,
|
||||
'show_widget_bg' => false, 'cta_text' => '', 'cards' => [], 'obs_animation' => '', 'animation_repeat' => false,
|
||||
'pc_gap' => 10, 'm_gap' => 5, 'go_link' => true, 'cta_color' => '',
|
||||
], $i);
|
||||
$is_mobile = function_exists('wp_is_mobile') && wp_is_mobile();
|
||||
$pc_row = max(1, (int) $i['pc_row']);
|
||||
$m_row = max(1, (int) $i['m_row']);
|
||||
$col_class = 'col-sm-' . max(1, (int) (12 / $pc_row)) . ' col-xs-' . max(1, (int) (12 / $m_row));
|
||||
|
||||
$style_vars = '--zci-radius:' . (int) $i['card_radius'] . 'px;--zci-band-h:' . (int) $i['top_band_height'] . 'px;';
|
||||
$wrap_class = 'zib-combo-info-card row gutters-' . (int) ($is_mobile ? $i['m_gap'] : $i['pc_gap']);
|
||||
$wrap_class .= !empty($i['top_band']) ? ' zci-has-band' : '';
|
||||
|
||||
$default_cta = (string) ($i['cta_text'] !== '' ? $i['cta_text'] : __('立即体验', 'aksrt'));
|
||||
$default_cta_color = (string) ($i['cta_color'] ?? '');
|
||||
$auto_palette = ['blue', 'cyan', 'green', 'yellow', 'pink', 'purple'];
|
||||
$auto_idx = 0;
|
||||
|
||||
$animation = (string) ($i['obs_animation'] ?? '');
|
||||
$html = '';
|
||||
foreach ((array) $i['cards'] as $key => $card) {
|
||||
if (!is_array($card)) {
|
||||
continue;
|
||||
}
|
||||
$tags = '';
|
||||
foreach (preg_split('/\s*[,,]\s*/', (string) ($item['tags'] ?? '')) ?: [] as $tag) {
|
||||
if ($tag !== '') {
|
||||
$tags .= '<span>' . esc_html($tag) . '</span>';
|
||||
$card_html = self::combo_card_item($card, $default_cta, $default_cta_color, $auto_palette, $auto_idx, !empty($i['go_link']));
|
||||
if ($card_html === '') {
|
||||
continue;
|
||||
}
|
||||
$attr = '';
|
||||
$item_class = $col_class;
|
||||
if ($animation !== '') {
|
||||
$item_class .= ' obs-animate ani-' . sanitize_html_class($animation);
|
||||
$attr .= ' data-animation="' . esc_attr($animation) . '"';
|
||||
$attr .= ' style=" --delay: ' . esc_attr((string) ($key * 0.15)) . 's; "';
|
||||
if (!empty($i['animation_repeat'])) {
|
||||
$attr .= ' data-animation-repeat="true"';
|
||||
}
|
||||
}
|
||||
$html .= '<article class="aksrt-combo-card">' . RendererHelpers::card_media($item) . ($tags ? '<div class="aksrt-combo-card__tags">' . $tags . '</div>' : '') . '</article>';
|
||||
$html .= '<div class="' . esc_attr($item_class) . '"' . $attr . '>' . $card_html . '</div>';
|
||||
}
|
||||
return $html . '</div>';
|
||||
if ($html === '') {
|
||||
return '';
|
||||
}
|
||||
return '<div class="mb20"><div class="' . esc_attr($wrap_class) . '" style="' . esc_attr($style_vars) . '">' . $html . '</div></div>';
|
||||
}
|
||||
|
||||
private static function combo_card_item(array $card, string $default_cta, string $default_cta_color, array $auto_palette, int &$auto_idx, bool $go_link): string {
|
||||
$card = wp_parse_args($card, [
|
||||
'icon_image' => '', 'icon' => '', 'title' => '', 'tags' => [], 'desc' => '',
|
||||
'link' => [], 'cta_text' => '', 'cta_color' => '', 'band_color' => 'auto',
|
||||
]);
|
||||
$title = trim(wp_strip_all_tags((string) $card['title']));
|
||||
$desc = trim((string) $card['desc']);
|
||||
if ($title === '' && $desc === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$band = (string) $card['band_color'];
|
||||
if ($band === '' || $band === 'auto') {
|
||||
$band = $auto_palette[$auto_idx % count($auto_palette)];
|
||||
$auto_idx++;
|
||||
}
|
||||
|
||||
$icon_html = '';
|
||||
$icon_image = esc_url((string) $card['icon_image']);
|
||||
if ($icon_image !== '') {
|
||||
$icon_html = '<div class="zci-icon flex jc shrink0 mb20"><img src="' . $icon_image . '" alt="' . esc_attr($title !== '' ? $title : __('图标', 'aksrt')) . '"></div>';
|
||||
} elseif (!empty($card['icon'])) {
|
||||
$icon_html = '<div class="zci-icon flex jc shrink0 mb20">' . self::icon_markup((string) $card['icon']) . '</div>';
|
||||
}
|
||||
|
||||
$tags_html = '';
|
||||
$tags = (array) ($card['tags'] ?? []);
|
||||
if ($tags) {
|
||||
$tags_html .= '<div class="zci-tags mb10 text-ellipsis">';
|
||||
foreach ($tags as $tag) {
|
||||
if (!is_array($tag)) {
|
||||
continue;
|
||||
}
|
||||
$tag_text = trim((string) ($tag['text'] ?? ''));
|
||||
if ($tag_text === '') {
|
||||
continue;
|
||||
}
|
||||
$tags_html .= '<span class="badg p2-10 radius px12 ' . esc_attr(sanitize_html_class((string) ($tag['color'] ?? ''))) . '">' . esc_html($tag_text) . '</span>';
|
||||
}
|
||||
$tags_html .= '</div>';
|
||||
}
|
||||
|
||||
$desc_html = $desc !== '' ? '<div class="zci-desc text-ellipsis-3 muted-color">' . wp_kses_post($desc) . '</div>' : '';
|
||||
|
||||
$cta_text = trim((string) ($card['cta_text'] ?? '')) !== '' ? trim((string) $card['cta_text']) : $default_cta;
|
||||
$cta_color = trim((string) ($card['cta_color'] ?? '')) !== '' ? trim((string) $card['cta_color']) : $default_cta_color;
|
||||
$link = RendererHelpers::link((array) ($card['link'] ?? []));
|
||||
$url = $link['url'];
|
||||
if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
|
||||
$url = esc_url(aksrt_go_link($link['url'], true));
|
||||
}
|
||||
$target = $link['target'] === '_blank' ? ' target="_blank" rel="noopener noreferrer"' : '';
|
||||
$cta_html = $cta_text !== '' && $url !== ''
|
||||
? '<a class="zci-cta inflex ac gap6 ' . esc_attr(sanitize_html_class($cta_color)) . '" href="' . $url . '"' . $target . '><span>' . esc_html($cta_text) . '</span><i class="fa fa-arrow-right zci-arrow" aria-hidden="true"></i></a>'
|
||||
: '';
|
||||
|
||||
$inner = '<div class="zci-body flex xx flex1">' . $icon_html;
|
||||
if ($title !== '') {
|
||||
$inner .= '<div class="zci-title font-bold text-ellipsis mb10">' . esc_html($title) . '</div>';
|
||||
}
|
||||
$inner .= $tags_html . $desc_html . $cta_html . '</div>';
|
||||
return '<div class="zci-card flex xx zci-band-' . esc_attr(sanitize_html_class($band)) . '">' . $inner . '</div>';
|
||||
}
|
||||
|
||||
private static function render_text_title(array $i): string {
|
||||
@@ -216,15 +482,11 @@ final class GeneralRenderer {
|
||||
}
|
||||
|
||||
private static function render_recent_comments(array $i): string {
|
||||
$comments = get_comments(['status' => 'approve', 'number' => (int) $i['count'], 'user__not_in' => RendererHelpers::ids((string) $i['exclude_users']), 'post__not_in' => RendererHelpers::ids((string) $i['exclude_posts'])]);
|
||||
if (!$comments) {
|
||||
$list = function_exists('aksrt_widget_comments_list') ? aksrt_widget_comments_list($i) : '';
|
||||
if ($list === '') {
|
||||
return RendererHelpers::empty_state(__('暂无评论', 'aksrt'));
|
||||
}
|
||||
$html = '<ul class="aksrt-widget-comments">';
|
||||
foreach ($comments as $comment) {
|
||||
$html .= '<li><a class="aksrt-widget-comments__avatar" href="' . esc_url(get_comment_link($comment)) . '">' . (function_exists('aksrt_get_avatar_html') ? aksrt_get_avatar_html($comment, 48) : get_avatar($comment, 48)) . '</a><div><strong>' . esc_html(get_comment_author($comment)) . '</strong><p>' . esc_html(wp_trim_words(wp_strip_all_tags((string) $comment->comment_content), 18)) . '</p>' . (!empty($i['show_source']) ? '<a class="aksrt-widget-comments__source" href="' . esc_url(get_comment_link($comment)) . '">' . esc_html(get_the_title((int) $comment->comment_post_ID)) . '</a>' : '') . '</div></li>';
|
||||
}
|
||||
return $html . '</ul>';
|
||||
return '<div class="box-body comment-mini-lists zib-widget">' . $list . '</div>';
|
||||
}
|
||||
|
||||
private static function render_links(array $i): string {
|
||||
|
||||
@@ -51,44 +51,59 @@ final class SliderRenderer {
|
||||
$option = (array) ($instance['option'] ?? []); $option['direction'] = 'horizontal'; $option['scale_height'] = true; $option['scale'] = (int) ($instance['slide_scale'] ?? 50);
|
||||
$slide_list = (array) ($instance['slides'] ?? []);
|
||||
$slider = !empty($slide_list[0]) ? self::slider($slide_list, $option) : '';
|
||||
$covers = '';
|
||||
$is_mobile = function_exists('wp_is_mobile') && wp_is_mobile();
|
||||
$cover_list = array_values((array) ($instance['covers'] ?? []));
|
||||
$is_row = count($cover_list) > 1;
|
||||
$pc_row = max(1, min(3, (int) ($instance['pc_row'] ?? 2)));
|
||||
$m_row = max(1, min(3, (int) ($instance['m_row'] ?? 2)));
|
||||
$layout_type = (string) ($instance['m_layout_type'] ?? 'vertical');
|
||||
$pc_row = max(1, min(4, (int) ($instance['pc_row'] ?? 2)));
|
||||
$m_row = $layout_type === 'default' ? $pc_row : max(1, min(6, (int) ($instance['m_row'] ?? 2)));
|
||||
$row_class = 'col-sm-' . (int) (12 / $pc_row) . ($m_row > 1 ? ' col-xs-' . (int) (12 / $m_row) : '');
|
||||
$m_cover_ratio = max(0, (int) ($instance['m_cover_ratio'] ?? 45));
|
||||
$mask = max(0, min(90, (int) ($instance['mask_opacity'] ?? 0))) / 100;
|
||||
$cover_style_vars = '--font-size:' . (int) ($instance['font_size_pc'] ?? 16) . 'px;--font-size-sm:' . (int) ($instance['font_size_m'] ?? 14) . 'px;';
|
||||
if (!empty($instance['font_bold'])) $cover_style_vars .= '--font-weight:bold;--font-weight-sm:bold;';
|
||||
$font_color = (string) ($instance['font_color'] ?? '');
|
||||
if ($font_color !== '') $cover_style_vars .= '--color:' . $font_color . ';--color-sm:' . $font_color . ';';
|
||||
$covers = '';
|
||||
foreach ($cover_list as $cover) {
|
||||
if (!is_array($cover)) continue;
|
||||
$hide = (string) ($cover['hide'] ?? '');
|
||||
if (($is_mobile && $hide === 'm') || (!$is_mobile && $hide === 'pc')) continue;
|
||||
$image = esc_url((string) ($cover['image'] ?? ''));
|
||||
$video = esc_url((string) ($cover['video'] ?? ''));
|
||||
if ($image === '' && ($video === '' || $is_mobile)) continue;
|
||||
$media = $video !== '' && !$is_mobile ? '<video autoplay loop muted playsinline class="fit-cover" src="' . $video . '"' . ($image !== '' ? ' poster="' . $image . '"' : '') . '></video>' : '<img class="fit-cover" src="' . $image . '" alt="' . esc_attr(wp_strip_all_tags((string) ($cover['title'] ?? ''))) . '">';
|
||||
$mask = max(0, min(90, (int) ($instance['mask_opacity'] ?? 0))) / 100;
|
||||
$card = $media . ($mask ? '<i class="graphic-mask" style="opacity:' . esc_attr((string) $mask) . '"></i>' : '') . (!empty($cover['title']) ? '<div class="abs-center text-center graphic-text this-font">' . wp_kses_post((string) $cover['title']) . '</div>' : '');
|
||||
$title = (string) ($cover['title'] ?? '');
|
||||
$link = RendererHelpers::link((array) ($cover['link'] ?? []));
|
||||
$card = $link['url'] !== '' ? '<a href="' . $link['url'] . '"' . RendererHelpers::link_attrs($link) . '>' . $card . '</a>' : '<span>' . $card . '</span>';
|
||||
$covers .= '<div class="' . ($is_row ? esc_attr($row_class) : '') . '"><div class="graphic-cover-item noshadow mb10">' . $card . '</div></div>';
|
||||
$card = aksrt_graphic_card([
|
||||
'class' => 'noshadow mb10',
|
||||
'img' => $image,
|
||||
'video' => $video,
|
||||
'alt' => wp_strip_all_tags($title),
|
||||
'link' => ['url' => $link['url'], 'target' => $link['target']],
|
||||
'more' => trim($title) !== '' ? '<div class="abs-center text-center graphic-text this-font">' . wp_kses_post($title) . '</div>' : '',
|
||||
'height_scale' => 0,
|
||||
'mask_opacity' => (int) ($instance['mask_opacity'] ?? 0),
|
||||
]);
|
||||
if ($card === '') continue;
|
||||
$covers .= '<div class="' . ($is_row ? esc_attr($row_class) : '') . '">' . $card . '</div>';
|
||||
}
|
||||
if ($covers !== '') {
|
||||
$cover_style = '--font-size:' . (int) ($instance['font_size_pc'] ?? 16) . 'px;--font-size-sm:' . (int) ($instance['font_size_m'] ?? 14) . 'px;';
|
||||
if (!empty($instance['font_bold'])) $cover_style .= '--font-weight:bold;--font-weight-sm:bold;';
|
||||
if (!empty($instance['font_color'])) $cover_style .= '--color:' . $instance['font_color'] . ';--color-sm:' . $instance['font_color'] . ';';
|
||||
$covers = '<div class="widget-graphic-cover" style="' . esc_attr($cover_style) . '">' . ($is_row ? '<div class="row gutters-5">' : '') . $covers . ($is_row ? '</div>' : '') . '</div>';
|
||||
$covers = '<div class="widget-graphic-cover" style="' . esc_attr($cover_style_vars) . '">' . ($is_row ? '<div class="row gutters-5">' : '') . $covers . ($is_row ? '</div>' : '') . '</div>';
|
||||
}
|
||||
if ($slider === '' && $covers === '') return RendererHelpers::empty_state(__('请配置幻灯片或卡片', 'aksrt'));
|
||||
$layout = max(0, min(100, (int) ($instance['layout_ratio'] ?? 60)));
|
||||
$slide_scale = max(0, (int) ($instance['slide_scale'] ?? 50));
|
||||
$rows = max(1, (int) ($instance['pc_row'] ?? 2));
|
||||
$rows = $pc_row;
|
||||
$columns = max(1, (int) ($instance['pc_col'] ?? 2));
|
||||
$base = $layout < 100 ? (($layout * $slide_scale) / (100 - $layout)) : 0;
|
||||
$cover_ratio = (($base - (($columns - 1) * 3)) / $columns) * $rows;
|
||||
$style = '--layout-ratio:' . $layout . '%;--cover-ratio:' . $cover_ratio . '%;--m-cover-ratio:' . (int) ($instance['m_cover_ratio'] ?? 45) . '%;--cover-cols:' . $rows . ';';
|
||||
$class = 'flex widget-slider-graphic m-' . sanitize_html_class((string) ($instance['m_layout_type'] ?? 'vertical'));
|
||||
$cover_ratio = $columns > 0 ? ((($base - (($columns - 1) * 3)) / $columns) * $rows) : 0;
|
||||
$style = '--layout-ratio:' . $layout . '%;--cover-ratio:' . $cover_ratio . '%;--m-cover-ratio:' . $m_cover_ratio . '%;';
|
||||
$animation = '';
|
||||
if (!empty($instance['obs_animation'])) {
|
||||
$animation = ' obs-animate ani-' . sanitize_html_class((string) $instance['obs_animation']) . (!empty($instance['animation_repeat']) ? ' obs-animate-repeat' : '');
|
||||
}
|
||||
$class = 'flex widget-slider-graphic m-' . sanitize_html_class($layout_type);
|
||||
$html = '<div class="' . esc_attr($class) . '" style="' . esc_attr($style) . '" data-row="' . $rows . '" data-col="' . $columns . '">';
|
||||
if ($slider !== '') $html .= '<div class="slider-col mb20">' . $slider . '</div>';
|
||||
if ($slider !== '') $html .= '<div class="slider-col mb20' . esc_attr($animation) . '">' . $slider . '</div>';
|
||||
if ($covers !== '') $html .= '<div class="cover-col mb10">' . $covers . '</div>';
|
||||
return $html . '</div>';
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ final class WidgetCatalog {
|
||||
['notice', '上下滚动公告', '自动滚动的公告信息列表', 'wide'],
|
||||
],
|
||||
'content' => [
|
||||
['term_cards', '分类图文卡片', '将分类或标签显示为图文卡片', 'wide'],
|
||||
['term_aggregation', '分类与标签聚合卡片', '聚合分类、标签与文章内容', 'wide'],
|
||||
['hot_posts', '热榜文章', '按浏览、评论、收藏等维度排行', 'sidebar'],
|
||||
['term_cards', '分类图文卡片', '将分类显示为图文卡片', 'wide'],
|
||||
['term_aggregation', '分类聚合卡片', '将分类显示为图文聚合卡片,并附带最新文章列表', 'wide'],
|
||||
['hot_posts', '热榜文章', '按浏览、点赞、评论、收藏等维度生成榜单', 'sidebar'],
|
||||
['post_pay', '付费购买', '显示当前文章的知识付费购买模块', 'sidebar'],
|
||||
['posts', '文章列表', '多条件筛选和多样式文章列表', 'wide'],
|
||||
['posts', '文章列表', '通过各种筛选、排序显示文章列表,支持多种显示模式', 'wide'],
|
||||
['tab_posts', '多栏目文章(新)', '通过多个栏目切换文章列表', 'wide'],
|
||||
['oneline_posts', '横向滚动文章', '单行横向滚动文章列表', 'wide'],
|
||||
['oneline_posts', '横向滚动文章', '显示文章列表,只显示一行,左右滚动', 'wide'],
|
||||
],
|
||||
'knowledge_product' => [
|
||||
['pricing_plans', '知识产品方案卡片', '课程与会员方案价格、权益和购买入口', 'wide'],
|
||||
@@ -49,7 +49,7 @@ final class WidgetCatalog {
|
||||
],
|
||||
'slider' => [
|
||||
['slider', '幻灯片(新)', '支持图片、视频、文字和视差图层', 'wide'],
|
||||
['slider_media_combo', '幻灯片加图文视频卡片', '幻灯片与图文视频卡片组合布局', 'wide'],
|
||||
['slider_media_combo', '幻灯片加图文视频卡片', '左侧幻灯片与右侧图文视频封面卡片横向组合展示', 'wide'],
|
||||
],
|
||||
'user' => [
|
||||
['user_card', '用户个人信息', '未登录时候显示登录注册按钮,登录后显示登录用户的个人信息', 'sidebar'],
|
||||
@@ -70,9 +70,9 @@ final class WidgetCatalog {
|
||||
'renderer' => $slug,
|
||||
'size' => $size,
|
||||
'common' => [
|
||||
'title' => !in_array($slug, ['post_pay', 'layout_gap', 'slider', 'slider_media_combo', 'buttons'], true),
|
||||
'affix' => $size === 'sidebar' || in_array($slug, ['buttons', 'notice'], true),
|
||||
'animation' => $slug !== 'buttons',
|
||||
'title' => !in_array($slug, ['post_pay', 'layout_gap', 'slider', 'slider_media_combo', 'buttons', 'posts_toc'], true),
|
||||
'affix' => ($size === 'sidebar' && $slug !== 'posts_toc') || in_array($slug, ['buttons', 'notice'], true),
|
||||
'animation' => !in_array($slug, ['buttons', 'term_cards', 'term_aggregation', 'media_cards', 'combo_info', 'slider_media_combo'], true),
|
||||
],
|
||||
'fields' => WidgetSchemas::for($slug),
|
||||
'assets' => self::assets($slug),
|
||||
|
||||
@@ -80,6 +80,10 @@ final class WidgetDefinition {
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
public function common(): array {
|
||||
return $this->common;
|
||||
}
|
||||
|
||||
public function assets(): array {
|
||||
return $this->assets;
|
||||
}
|
||||
|
||||
@@ -38,9 +38,9 @@ final class WidgetSchemas {
|
||||
];
|
||||
case 'posts_toc':
|
||||
return [
|
||||
['id' => 'min_headings', 'type' => 'spinner', 'label' => '最少标题数量', 'default' => 3, 'min' => 1, 'max' => 20],
|
||||
['id' => 'collapsed', 'type' => 'switcher', 'label' => '默认收起', 'default' => false],
|
||||
['id' => 'offset', 'type' => 'spinner', 'label' => '滚动偏移', 'default' => 70, 'min' => 0, 'max' => 300],
|
||||
['id' => 'title', 'type' => 'text', 'label' => '标题', 'default' => '文章目录'],
|
||||
['id' => 'mini_title', 'type' => 'text', 'label' => '副标题', 'default' => ''],
|
||||
['id' => 'in_affix', 'type' => 'switcher', 'label' => '侧栏随动(仅在侧边栏有效)', 'default' => false],
|
||||
];
|
||||
case 'quote':
|
||||
return [
|
||||
@@ -76,10 +76,11 @@ final class WidgetSchemas {
|
||||
];
|
||||
case 'recent_comments':
|
||||
return [
|
||||
['id' => 'count', 'type' => 'spinner', 'label' => '显示数量', 'default' => 6, 'min' => 1, 'max' => 50],
|
||||
['id' => 'exclude_users', 'type' => 'text', 'label' => '排除用户 ID', 'default' => '', 'sanitize' => 'ids'],
|
||||
['id' => 'exclude_posts', 'type' => 'text', 'label' => '排除文章 ID', 'default' => '', 'sanitize' => 'ids'],
|
||||
['id' => 'show_source', 'type' => 'switcher', 'label' => '显示评论来源', 'default' => true],
|
||||
['id' => 'limit', 'type' => 'spinner', 'label' => '显示数量', 'default' => 8, 'min' => 1, 'max' => 50, 'step' => 1],
|
||||
['id' => 'outer', 'type' => 'text', 'label' => '排除用户ID', 'default' => '', 'sanitize' => 'ids', 'description' => '填写需要排除的用户ID,用逗号分割,例如:55,66,100'],
|
||||
['id' => 'outpost', 'type' => 'text', 'label' => '排除文章ID', 'default' => '', 'sanitize' => 'ids', 'description' => '填写需要排除的文章ID,用逗号分割,例如:55,66,100'],
|
||||
['id' => 'show_source', 'type' => 'switcher', 'label' => '显示来源', 'default' => false],
|
||||
['id' => 'style', 'type' => 'radio', 'label' => '显示样式', 'default' => '', 'options' => ['' => '简约', 'style_2' => '风格2']],
|
||||
];
|
||||
case 'links':
|
||||
return [
|
||||
@@ -142,65 +143,137 @@ final class WidgetSchemas {
|
||||
];
|
||||
case 'partners':
|
||||
return [
|
||||
['id' => 'rows_pc', 'type' => 'spinner', 'label' => 'PC 行数', 'default' => 2, 'min' => 1, 'max' => 5],
|
||||
['id' => 'rows_mobile', 'type' => 'spinner', 'label' => '移动端行数', 'default' => 1, 'min' => 1, 'max' => 4],
|
||||
['id' => 'speed', 'type' => 'spinner', 'label' => '滚动速度', 'default' => 30, 'min' => 5, 'max' => 120],
|
||||
['id' => 'items', 'type' => 'group', 'label' => '合作伙伴', 'max_items' => 60, 'fields' => self::media_card_fields()],
|
||||
['id' => 'rows', 'type' => 'button_set', 'label' => 'PC端显示几行滚动', 'default' => 3, 'options' => [1 => '1行', 2 => '2行', 3 => '3行', 4 => '4行', 5 => '5行', 6 => '6行']],
|
||||
['id' => 'm_rows', 'type' => 'button_set', 'label' => '移动端显示几行滚动', 'default' => 3, 'options' => [1 => '1行', 2 => '2行', 3 => '3行', 4 => '4行', 5 => '5行', 6 => '6行']],
|
||||
['id' => 'style_type', 'type' => 'radio', 'label' => '选择合作伙伴卡片的呈现方式', 'default' => 'card', 'options' => ['card' => '图文卡片(适合正方形logo)', 'logo' => '纯 Logo', 'chip' => '精简胶囊']],
|
||||
['id' => 'card_width', 'type' => 'spinner', 'label' => '单卡片宽度', 'default' => 200, 'min' => 80, 'max' => 500, 'step' => 10, 'unit' => 'px', 'dependency' => ['style_type', '==', 'card'], 'description' => 'PC端每张卡片的宽度(纯 Logo 模式下宽度会自适应图片,此项不生效)'],
|
||||
['id' => 'card_height', 'type' => 'spinner', 'label' => '卡片高度', 'default' => 60, 'min' => 50, 'max' => 260, 'step' => 5, 'unit' => 'px', 'dependency' => ['style_type', '!=', 'chip'], 'description' => '每张卡片的高度,纯 Logo 模式建议 70-120'],
|
||||
['id' => 'card_bg', 'type' => 'switcher', 'label' => '显示每张合作伙伴卡片的背景', 'default' => true],
|
||||
['id' => 'card_radius', 'type' => 'slider', 'label' => '卡片圆角', 'default' => 16, 'min' => 0, 'max' => 60, 'step' => 1, 'unit' => 'px', 'dependency' => ['card_bg', '!=', '']],
|
||||
['id' => 'speed', 'type' => 'slider', 'label' => '滚动速度(数越小越快)', 'default' => 3, 'min' => 0.1, 'max' => 10, 'step' => 0.1, 'unit' => 's'],
|
||||
['id' => 'direction', 'type' => 'radio', 'label' => '滚动方向', 'default' => 'alternate', 'options' => ['alternate' => '奇数行左,偶数行右', 'left' => '全部向左', 'right' => '全部向右']],
|
||||
['id' => 'pause_hover', 'type' => 'switcher', 'label' => '鼠标悬停暂停', 'default' => true],
|
||||
['id' => 'show_widget_bg', 'type' => 'switcher', 'label' => '模块背景', 'default' => false, 'description' => '显示整个模块外层背景'],
|
||||
['id' => 'go_link', 'type' => 'switcher', 'label' => '外链重定向', 'default' => true, 'description' => '开启后,将链接转为内部go跳转链接'],
|
||||
['id' => 'end_fade', 'type' => 'switcher', 'label' => '两端渐隐', 'default' => true, 'description' => '开启后,卡片两端会渐隐,使卡片无缝连接'],
|
||||
['id' => 'link_type', 'type' => 'radio', 'label' => '合作伙伴获取方式', 'default' => 'link', 'options' => ['link' => '通过链接获取', 'manual' => '手动添加']],
|
||||
['id' => 'link_cats', 'type' => 'terms', 'label' => '选择链接分类', 'taxonomy' => 'link_category', 'default' => [], 'dependency' => ['link_type', '==', 'link'], 'description' => '需显示的链接分类,留空则为全部'],
|
||||
['id' => 'link_limit', 'type' => 'spinner', 'label' => '最大获取数量', 'default' => 50, 'min' => 0, 'step' => 5, 'unit' => '个', 'dependency' => ['link_type', '==', 'link']],
|
||||
['id' => 'link_orderby', 'type' => 'select', 'label' => '排序方式', 'default' => 'name', 'dependency' => ['link_type', '==', 'link'], 'options' => ['name' => '名称排序', 'updated' => '更新时间', 'rating' => '链接评分', 'rand' => '随机排序']],
|
||||
['id' => 'page_links_order', 'type' => 'radio', 'label' => '顺序', 'default' => 'ASC', 'dependency' => ['link_type', '==', 'link'], 'options' => ['ASC' => '升序', 'DESC' => '降序']],
|
||||
[
|
||||
'id' => 'partners', 'type' => 'group', 'label' => '添加合作伙伴', 'item_label' => '合作伙伴', 'max_items' => 60, 'dependency' => ['link_type', '==', 'manual'],
|
||||
'default' => [],
|
||||
'fields' => [
|
||||
['id' => 'title', 'type' => 'textarea', 'label' => '名称', 'default' => '', 'attributes' => ['rows' => 1], 'description' => '卡片第一行文案,建议简短'],
|
||||
['id' => 'desc', 'type' => 'textarea', 'label' => '简介', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 2], 'description' => '卡片第二行文案,支持 HTML(仅在图文卡片模式下显示)'],
|
||||
['id' => 'image', 'type' => 'upload', 'label' => 'Logo图片', 'default' => '', 'library' => 'image', 'description' => '如果样式选择为图文卡片,则建议全部为正方形图片'],
|
||||
['id' => 'link', 'type' => 'link', 'label' => '链接', 'default' => []],
|
||||
],
|
||||
],
|
||||
];
|
||||
case 'combo_info':
|
||||
return [['id' => 'items', 'type' => 'group', 'label' => '信息卡片', 'max_items' => 30, 'fields' => array_merge(self::media_card_fields(), [['id' => 'tags', 'type' => 'text', 'label' => '标签', 'default' => '']])]];
|
||||
return array_merge(self::obs_animation_fields(), [
|
||||
['id' => 'pc_row', 'type' => 'button_set', 'label' => 'PC端单行排列数量', 'default' => 4, 'options' => [2 => '2个', 3 => '3个', 4 => '4个', 6 => '6个']],
|
||||
['id' => 'm_row', 'type' => 'button_set', 'label' => '移动端单行排列数量', 'default' => 1, 'options' => [1 => '1个', 2 => '2个']],
|
||||
['id' => 'pc_gap', 'type' => 'button_set', 'label' => 'PC端卡片间距', 'default' => 10, 'options' => [5 => '10px', 10 => '20px']],
|
||||
['id' => 'm_gap', 'type' => 'button_set', 'label' => '移动端卡片间距', 'default' => 5, 'options' => [5 => '10px', 10 => '20px']],
|
||||
['id' => 'card_radius', 'type' => 'slider', 'label' => '卡片圆角', 'default' => 16, 'min' => 0, 'max' => 30, 'step' => 1, 'unit' => 'px'],
|
||||
['id' => 'top_band', 'type' => 'switcher', 'label' => '显示顶部渐变背景', 'default' => true],
|
||||
['id' => 'top_band_height', 'type' => 'slider', 'label' => '色带高度', 'default' => 150, 'min' => 100, 'max' => 400, 'step' => 20, 'unit' => 'px', 'dependency' => ['top_band', '==', '1']],
|
||||
['id' => 'go_link', 'type' => 'switcher', 'label' => '外链重定向', 'default' => true, 'description' => '开启后,将链接转为内部go跳转链接'],
|
||||
['id' => 'cta_text', 'type' => 'text', 'label' => '默认按钮文案', 'default' => '立即体验', 'description' => '每张卡片可单独覆盖'],
|
||||
['id' => 'cta_color', 'type' => 'palette', 'label' => '默认按钮颜色', 'default' => '', 'options' => self::cta_palette()],
|
||||
['id' => 'cards', 'type' => 'group', 'label' => '添加卡片', 'item_label' => '卡片', 'max_items' => 30, 'default' => [], 'fields' => [
|
||||
['id' => 'title', 'type' => 'textarea', 'label' => '卡片标题', 'default' => '', 'attributes' => ['rows' => 1]],
|
||||
['id' => 'icon', 'type' => 'icon', 'label' => '图标', 'default' => ''],
|
||||
['id' => 'icon_image', 'type' => 'upload', 'label' => '图片图标', 'default' => '', 'library' => 'image', 'description' => '图标和图片图标二选一,设置图片将覆盖图标'],
|
||||
[
|
||||
'id' => 'tags', 'type' => 'group', 'label' => '标签', 'item_label' => '标签', 'max_items' => 20, 'default' => [],
|
||||
'fields' => [
|
||||
['id' => 'text', 'type' => 'text', 'label' => '标签文字', 'default' => ''],
|
||||
['id' => 'color', 'type' => 'palette', 'label' => '标签颜色', 'default' => '', 'options' => self::tag_palette()],
|
||||
],
|
||||
],
|
||||
['id' => 'desc', 'type' => 'textarea', 'label' => '简介描述', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 3], 'description' => '卡片描述文案,超出会自动省略(默认3行)'],
|
||||
['id' => 'link', 'type' => 'link', 'label' => '链接', 'default' => []],
|
||||
['id' => 'cta_text', 'type' => 'text', 'label' => '按钮文案(可选)', 'default' => '', 'description' => '留空则使用默认按钮文案'],
|
||||
['id' => 'cta_color', 'type' => 'palette', 'label' => '按钮颜色(可选)', 'default' => '', 'options' => self::cta_palette()],
|
||||
['id' => 'band_color', 'type' => 'select', 'label' => '顶部色带配色', 'default' => 'auto', 'options' => ['auto' => '自动(按序循环)', 'blue' => '蓝紫', 'cyan' => '青蓝', 'green' => '青绿', 'yellow' => '橙黄', 'pink' => '粉红', 'purple' => '紫罗兰', 'gray' => '中性灰']],
|
||||
]],
|
||||
]);
|
||||
case 'media_cards':
|
||||
return array_merge(self::obs_animation_fields(), [
|
||||
['id' => 'pc_row', 'type' => 'button_set', 'label' => 'PC端单行排列数量', 'default' => 4, 'options' => self::row_options()],
|
||||
['id' => 'm_row', 'type' => 'button_set', 'label' => '移动端单行排列数量', 'default' => 2, 'options' => self::row_options(), 'description' => '请根据此模块放置位置的宽度合理调整单行数量,避免显示不佳'],
|
||||
['id' => 'mask_opacity', 'type' => 'slider', 'label' => '遮罩透明度', 'default' => 10, 'min' => 0, 'max' => 90, 'step' => 1, 'unit' => '%', 'description' => '图片上显示的黑色遮罩层的透明度'],
|
||||
['id' => 'height_scale', 'type' => 'spinner', 'label' => '封面长宽比例', 'default' => 30, 'min' => 5, 'max' => 300, 'step' => 5, 'unit' => '%'],
|
||||
['id' => 'font_size_pc', 'type' => 'spinner', 'label' => 'PC端字体大小', 'default' => 18, 'min' => 10, 'max' => 80, 'step' => 2, 'unit' => 'px'],
|
||||
['id' => 'font_size_m', 'type' => 'spinner', 'label' => '移动端字体大小', 'default' => 14, 'min' => 10, 'max' => 80, 'step' => 2, 'unit' => 'px'],
|
||||
['id' => 'font_bold', 'type' => 'switcher', 'label' => '粗体显示', 'default' => false],
|
||||
['id' => 'font_color', 'type' => 'color', 'label' => '文字颜色', 'default' => ''],
|
||||
['id' => 'covers', 'type' => 'group', 'label' => '添加封面', 'item_label' => '内容', 'max_items' => 40, 'default' => [], 'fields' => self::cover_fields(), 'description' => '<div class="c-yellow">由于移动端多数浏览器不支持视频背景功能,所以移动端不会显示视频!</div>'],
|
||||
]);
|
||||
case 'icon_cards':
|
||||
return self::icon_cards_fields();
|
||||
case 'icon_cover_cards':
|
||||
return self::icon_cover_cards_fields();
|
||||
case 'media_cards':
|
||||
default:
|
||||
return [['id' => 'cards', 'type' => 'group', 'label' => '卡片', 'max_items' => 40, 'fields' => self::media_card_fields()]];
|
||||
}
|
||||
}
|
||||
|
||||
private static function content(string $slug): array {
|
||||
if ($slug === 'term_cards' || $slug === 'term_aggregation') {
|
||||
return [
|
||||
['id' => 'taxonomy', 'type' => 'select', 'label' => '分类法', 'default' => 'category', 'options' => ['category' => '分类', 'post_tag' => '标签']],
|
||||
['id' => 'term_ids', 'type' => 'text', 'label' => '分类/标签 ID', 'default' => '', 'sanitize' => 'ids'],
|
||||
['id' => 'count', 'type' => 'spinner', 'label' => '数量', 'default' => 8, 'min' => 1, 'max' => 50],
|
||||
['id' => 'pc_row', 'type' => 'spinner', 'label' => 'PC 单行数量', 'default' => 4, 'min' => 1, 'max' => 12],
|
||||
['id' => 'm_row', 'type' => 'spinner', 'label' => '移动端单行数量', 'default' => 2, 'min' => 1, 'max' => 6],
|
||||
];
|
||||
switch ($slug) {
|
||||
case 'posts':
|
||||
return self::posts_fields();
|
||||
case 'tab_posts':
|
||||
return self::tab_posts_fields();
|
||||
case 'oneline_posts':
|
||||
return self::oneline_posts_fields();
|
||||
case 'hot_posts':
|
||||
return self::hot_posts_fields();
|
||||
case 'term_cards':
|
||||
return self::term_cards_fields();
|
||||
case 'term_aggregation':
|
||||
return self::term_aggregation_fields();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
$fields = self::post_query_fields();
|
||||
if ($slug === 'hot_posts') {
|
||||
$fields[] = ['id' => 'show_thumbnail', 'type' => 'switcher', 'label' => '显示缩略图', 'default' => true];
|
||||
}
|
||||
if ($slug === 'oneline_posts') {
|
||||
$fields[] = ['id' => 'autoplay', 'type' => 'switcher', 'label' => '自动滚动', 'default' => true];
|
||||
}
|
||||
if ($slug === 'tab_posts') {
|
||||
return self::tab_posts_fields();
|
||||
}
|
||||
return $fields;
|
||||
private static function posts_fields(): array {
|
||||
return [
|
||||
['id' => 'load_mode', 'type' => 'radio', 'label' => '模块加载方式', 'default' => 'ajax', 'options' => ['detail' => '直接加载', 'ajax' => 'ajax懒加载'], 'description' => 'ajax懒加载:当页面加载完后,根据用户需要在加载当前模块内容,可提高页面渲染效率'],
|
||||
['id' => 'cat', 'type' => 'text', 'label' => '分类限制', 'default' => '', 'sanitize' => 'reference_ids', 'description' => '限制请填写对应的ID,多个ID请用英文逗号隔开。如:1,2,3。支持负数进行排除,例如:-1,-2,-3。'],
|
||||
['id' => 'zibpay_type', 'type' => 'checkbox', 'label' => '商品类型筛选', 'default' => [], 'options' => self::pay_types(), 'description' => '不做其它筛选'],
|
||||
['id' => 'limit_day', 'type' => 'spinner', 'label' => '发布时间限制', 'default' => 0, 'min' => 0, 'step' => 5, 'unit' => '天', 'description' => '仅显示最近多少天发布的文章,为0则不限制'],
|
||||
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => '', 'options' => self::posts_orderby()],
|
||||
['id' => 'order', 'type' => 'radio', 'label' => '顺序', 'default' => 'desc', 'options' => ['desc' => '降序', 'asc' => '升序']],
|
||||
['id' => 'style', 'type' => 'radio', 'label' => '列表样式', 'default' => 'list', 'options' => ['list' => '列表样式', 'card' => '卡片样式', 'mini' => 'mini列表'], 'description' => '<div class="c-yellow">注意:不同样式尺寸不同,请根据放置的位置合理选择。例如:放在宽度较小的侧边栏,则需选择mini样式,否则显示效果不佳</div>'],
|
||||
['id' => 'mini_opt', 'type' => 'checkbox', 'label' => 'mini列表配置', 'default' => [], 'dependency' => ['style', '==', 'mini'], 'options' => self::mini_opt_options(), 'description' => '不做其它筛选'],
|
||||
['id' => 'count', 'type' => 'spinner', 'label' => '显示数量', 'default' => 12, 'min' => 4, 'max' => 20, 'step' => 1, 'unit' => '篇'],
|
||||
['id' => 'paginate', 'type' => 'radio', 'label' => '翻页按钮', 'default' => '', 'options' => ['' => '不翻页', 'ajax' => 'AJAX追加列表翻页', 'number' => '数字翻页按钮']],
|
||||
];
|
||||
}
|
||||
|
||||
private static function tab_posts_fields(): array {
|
||||
return [
|
||||
['id' => 'style', 'type' => 'radio', 'label' => '列表样式', 'default' => 'mini', 'options' => ['list' => '列表样式', 'card' => '卡片样式', 'mini' => 'mini列表']],
|
||||
['id' => 'mini_opt', 'type' => 'checkbox', 'label' => 'mini列表配置', 'default' => [], 'dependency' => ['style', '==', 'mini'], 'options' => ['show_thumb' => '显示缩略图', 'show_number' => '显示编号(开启翻页后,只在第一页有效)', 'show_meta' => '显示作者、时间、点赞等信息']],
|
||||
['id' => 'count', 'type' => 'spinner', 'label' => '显示数量', 'default' => 6, 'min' => 4, 'max' => 20, 'step' => 1],
|
||||
['id' => 'style', 'type' => 'radio', 'label' => '列表样式', 'default' => 'mini', 'options' => ['list' => '列表样式', 'card' => '卡片样式', 'mini' => 'mini列表'], 'description' => '<div class="c-yellow">注意:不同样式尺寸不同,请根据放置的位置合理选择。例如:放在宽度较小的侧边栏,则需选择mini样式,否则显示效果不佳</div>'],
|
||||
['id' => 'mini_opt', 'type' => 'checkbox', 'label' => 'mini列表配置', 'default' => [], 'dependency' => ['style', '==', 'mini'], 'options' => self::mini_opt_options(), 'description' => '不做其它筛选'],
|
||||
['id' => 'count', 'type' => 'spinner', 'label' => '显示数量', 'default' => 6, 'min' => 4, 'max' => 20, 'step' => 1, 'unit' => '篇'],
|
||||
['id' => 'paginate', 'type' => 'radio', 'label' => '翻页按钮', 'default' => '', 'options' => ['' => '不翻页', 'ajax' => 'AJAX追加列表翻页', 'number' => '数字翻页按钮']],
|
||||
[
|
||||
'id' => 'tabs', 'type' => 'group', 'label' => '栏目', 'max_items' => 10,
|
||||
'id' => 'tabs', 'type' => 'group', 'label' => '栏目', 'max_items' => 10, 'item_label' => '栏目',
|
||||
'default' => [
|
||||
['title' => '热门推荐', 'orderby' => 'views'],
|
||||
['title' => '最近更新', 'orderby' => 'modified'],
|
||||
['title' => '猜你喜欢', 'orderby' => 'rand'],
|
||||
],
|
||||
'fields' => [
|
||||
['id' => 'title', 'type' => 'textarea', 'label' => '标题(必填)', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 1]],
|
||||
['id' => 'title', 'type' => 'textarea', 'label' => '标题(必填)', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 1], 'description' => '栏目显示的标题,支持HTML代码,注意代码规范'],
|
||||
['id' => 'cat', 'type' => 'text', 'label' => '分类限制', 'default' => '', 'sanitize' => 'reference_ids'],
|
||||
['id' => 'topics', 'type' => 'text', 'label' => '专题限制', 'default' => '', 'sanitize' => 'reference_ids', 'description' => '多个 ID 用英文逗号隔开,支持负数排除。'],
|
||||
['id' => 'limit_day', 'type' => 'spinner', 'label' => '发布时间限制', 'default' => 0, 'min' => 0, 'step' => 5, 'max' => 3650],
|
||||
['id' => 'zibpay_type', 'type' => 'checkbox', 'label' => '商品类型筛选', 'default' => [], 'options' => self::pay_types(), 'description' => '不做其它筛选'],
|
||||
['id' => 'limit_day', 'type' => 'spinner', 'label' => '发布时间限制', 'default' => 0, 'min' => 0, 'step' => 5, 'unit' => '天', 'description' => '仅显示最近多少天发布的文章,为0则不限制'],
|
||||
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => '', 'options' => self::posts_orderby()],
|
||||
['id' => 'order', 'type' => 'radio', 'label' => '顺序', 'default' => 'desc', 'options' => ['desc' => '降序', 'asc' => '升序']],
|
||||
],
|
||||
@@ -208,26 +281,82 @@ final class WidgetSchemas {
|
||||
];
|
||||
}
|
||||
|
||||
private static function posts_orderby(): array {
|
||||
private static function oneline_posts_fields(): array {
|
||||
return [
|
||||
'' => '默认排序', 'modified' => '更新时间', 'date' => '发布时间', 'views' => '浏览数量',
|
||||
'like' => '点赞数量', 'comment_count' => '评论数量', 'favorite' => '收藏数量',
|
||||
'rand' => '随机排序',
|
||||
['id' => 'cat', 'type' => 'text', 'label' => '分类限制', 'default' => '', 'sanitize' => 'reference_ids', 'description' => '限制请填写对应的ID,多个ID请用英文逗号隔开。如:1,2,3。支持负数进行排除,例如:-1,-2,-3。'],
|
||||
['id' => 'zibpay_type', 'type' => 'checkbox', 'label' => '商品类型筛选', 'default' => [], 'options' => self::pay_types(), 'description' => '不做其它筛选'],
|
||||
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => '', 'options' => self::posts_orderby()],
|
||||
['id' => 'order', 'type' => 'radio', 'label' => '顺序', 'default' => 'desc', 'options' => ['desc' => '降序', 'asc' => '升序']],
|
||||
['id' => 'style', 'type' => 'radio', 'label' => '列表样式', 'default' => 'card', 'options' => ['list' => '列表样式', 'card' => '卡片样式', 'mini' => 'mini列表'], 'description' => '<div class="c-yellow">注意:不同样式尺寸不同,请根据放置的位置合理选择。例如:放在宽度较小的侧边栏,则需选择mini样式,否则显示效果不佳</div>'],
|
||||
['id' => 'count', 'type' => 'spinner', 'label' => '显示数量', 'default' => 12, 'min' => 4, 'max' => 20, 'step' => 1, 'unit' => '篇'],
|
||||
];
|
||||
}
|
||||
|
||||
private static function post_query_fields(): array {
|
||||
private static function hot_posts_fields(): array {
|
||||
return [
|
||||
['id' => 'category_ids', 'type' => 'text', 'label' => '分类 ID', 'default' => '', 'sanitize' => 'ids'],
|
||||
['id' => 'tag_ids', 'type' => 'text', 'label' => '标签 ID', 'default' => '', 'sanitize' => 'ids'],
|
||||
['id' => 'pay_type', 'type' => 'select', 'label' => '付费内容类型', 'default' => '', 'options' => self::pay_types()],
|
||||
['id' => 'limit_day', 'type' => 'spinner', 'label' => '最近天数', 'default' => 0, 'min' => 0, 'max' => 3650],
|
||||
['id' => 'orderby', 'type' => 'select', 'label' => '排序', 'default' => 'date', 'options' => ['date' => '发布时间', 'modified' => '更新时间', 'views' => '浏览量', 'comments' => '评论数', 'likes' => '点赞数', 'favorites' => '收藏数', 'downloads' => '下载量', 'rand' => '随机']],
|
||||
['id' => 'order', 'type' => 'radio', 'label' => '顺序', 'default' => 'DESC', 'options' => ['DESC' => '降序', 'ASC' => '升序']],
|
||||
['id' => 'count', 'type' => 'spinner', 'label' => '数量', 'default' => 8, 'min' => 1, 'max' => 50],
|
||||
['id' => 'style', 'type' => 'button_set', 'label' => '列表样式', 'default' => 'card', 'options' => ['list' => '列表', 'card' => '卡片', 'mini' => 'Mini']],
|
||||
['id' => 'paginate', 'type' => 'select', 'label' => '翻页方式', 'default' => 'none', 'options' => ['none' => '不翻页', 'numbered' => '数字分页', 'load_more' => '加载更多', 'infinite' => '无限加载']],
|
||||
['id' => 'load_mode', 'type' => 'select', 'label' => '加载方式', 'default' => 'sync', 'options' => ['sync' => '同步', 'ajax' => 'AJAX 延迟加载']],
|
||||
['id' => 'orderby', 'type' => 'radio', 'label' => '榜单类型', 'default' => 'views', 'options' => [
|
||||
'views' => '热门榜单(按阅读量排序)',
|
||||
'like' => '超赞榜单(按点赞量排序)',
|
||||
'comment_count' => '话题榜单(按评论量排序)',
|
||||
'favorite' => '收藏榜单(按收藏量排序)',
|
||||
]],
|
||||
['id' => 'limit_day', 'type' => 'spinner', 'label' => '限制时间(最近X天)', 'default' => 0, 'min' => 0, 'max' => 999999, 'step' => 1, 'unit' => '天', 'description' => '设置多少天内发布的文章有效,为0则不限制时间'],
|
||||
['id' => 'count', 'type' => 'spinner', 'label' => '最大显示数量', 'default' => 6, 'min' => 1, 'max' => 20, 'step' => 1, 'unit' => '篇'],
|
||||
['id' => 'target_blank', 'type' => 'switcher', 'label' => '新窗口打开', 'default' => false],
|
||||
];
|
||||
}
|
||||
|
||||
private static function term_cards_fields(): array {
|
||||
return array_merge(self::obs_animation_fields(), [
|
||||
['id' => 'term_id', 'type' => 'terms', 'label' => '添加分类', 'taxonomy' => 'category', 'default' => [], 'description' => '选择并排序需要的分类,如选择的分类下没有封面则不会显示'],
|
||||
['id' => 'type', 'type' => 'radio', 'label' => '卡片样式', 'default' => 'style-1', 'options' => ['style-1' => '简单样式', 'style-2' => '样式二', 'style-3' => '样式三', 'style-4' => '样式四']],
|
||||
['id' => 'height_scale', 'type' => 'spinner', 'label' => '卡片长宽比例', 'default' => 60, 'min' => 20, 'max' => 300, 'step' => 5, 'unit' => '%'],
|
||||
['id' => 'pc_row', 'type' => 'button_set', 'label' => 'PC端单行排列数量', 'default' => 2, 'options' => self::row_options()],
|
||||
['id' => 'm_row', 'type' => 'button_set', 'label' => '移动端单行排列数量', 'default' => 2, 'options' => self::row_options(), 'description' => '请根据此模块放置位置的宽度合理调整单行数量,避免显示不佳'],
|
||||
['id' => 'mask_opacity', 'type' => 'slider', 'label' => '遮罩透明度', 'default' => 10, 'min' => 0, 'max' => 90, 'step' => 1, 'unit' => '%', 'description' => '图片上显示的黑色遮罩层的透明度'],
|
||||
['id' => 'target_blank', 'type' => 'switcher', 'label' => '新窗口打开', 'default' => false],
|
||||
]);
|
||||
}
|
||||
|
||||
private static function term_aggregation_fields(): array {
|
||||
return array_merge(self::obs_animation_fields(), [
|
||||
['id' => 'pc_row', 'type' => 'slider', 'label' => '单行布局', 'default' => 2, 'min' => 1, 'max' => 2, 'step' => 1, 'unit' => '个', 'description' => '请根据此模块放置位置的宽度合理调整单行数量'],
|
||||
['id' => 'term_id', 'type' => 'terms', 'label' => '添加分类', 'taxonomy' => 'category', 'default' => [], 'description' => '选择并排序需要的分类,如选择的分类下没有文章则不会显示'],
|
||||
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => 'modified', 'options' => self::posts_orderby(), 'dependency' => ['term_id', '!=', '']],
|
||||
['id' => 'order', 'type' => 'radio', 'label' => '顺序', 'default' => 'desc', 'options' => ['desc' => '降序', 'asc' => '升序'], 'dependency' => ['term_id', '!=', '']],
|
||||
['id' => 'count', 'type' => 'spinner', 'label' => '最大文章数量', 'default' => 4, 'min' => 1, 'max' => 20, 'step' => 1, 'unit' => '篇', 'dependency' => ['term_id', '!=', ''], 'description' => '<div class="c-yellow">请确保所选的分类内的文章数量均超过此数量,否则会出现布局错位的问题!</div>'],
|
||||
['id' => 'target_blank', 'type' => 'switcher', 'label' => '新窗口打开', 'default' => false, 'dependency' => ['term_id', '!=', '']],
|
||||
]);
|
||||
}
|
||||
|
||||
private static function obs_animation_fields(): array {
|
||||
return [
|
||||
['id' => 'obs_animation', 'type' => 'select', 'label' => '入场动画', 'default' => '', 'options' => self::animation_options(), 'description' => '页面滚动到此模块时触发的入场动画[注意:需合理配置适当启用]'],
|
||||
['id' => 'animation_repeat', 'type' => 'switcher', 'label' => '重复动画', 'default' => false, 'dependency' => ['obs_animation', '!=', ''], 'description' => '每次滚动到此模块都触发动画[注意:有性能消耗,部分老旧浏览器会有闪烁问题]'],
|
||||
];
|
||||
}
|
||||
|
||||
private static function row_options(): array {
|
||||
return [1 => '1个', 2 => '2个', 3 => '3个', 4 => '4个', 6 => '6个', 12 => '12个'];
|
||||
}
|
||||
|
||||
private static function mini_opt_options(): array {
|
||||
return [
|
||||
'show_thumb' => '显示缩略图',
|
||||
'show_number' => '显示编号(开启翻页后,只在第一页有效)',
|
||||
'show_meta' => '显示作者、时间、点赞等信息',
|
||||
];
|
||||
}
|
||||
|
||||
private static function posts_orderby(): array {
|
||||
return [
|
||||
'modified' => '更新时间',
|
||||
'date' => '发布时间',
|
||||
'views' => '浏览数量',
|
||||
'like' => '点赞数量',
|
||||
'comment_count' => '评论数量',
|
||||
'favorite' => '收藏数量',
|
||||
'rand' => '随机排序',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -319,25 +448,34 @@ final class WidgetSchemas {
|
||||
['id' => 'button', 'type' => 'switcher', 'label' => '显示翻页按钮', 'default' => true],
|
||||
['id' => 'pagination', 'type' => 'switcher', 'label' => '显示指示器', 'default' => true],
|
||||
['id' => 'effect', 'type' => 'select', 'label' => '切换动画', 'default' => 'slide', 'options' => ['slide' => '滑动', 'fade' => '淡出淡入', 'cube' => '3D方块', 'coverflow' => '3D滑入', 'flip' => '3D翻转']],
|
||||
['id' => 'spacebetween', 'type' => 'spinner', 'label' => '幻灯片间距', 'default' => 20, 'min' => 0, 'max' => 500, 'step' => 5],
|
||||
['id' => 'speed', 'type' => 'spinner', 'label' => '切换速度', 'default' => 0, 'min' => 0, 'max' => 3000, 'step' => 100],
|
||||
['id' => 'spacebetween', 'type' => 'spinner', 'label' => '幻灯片间距', 'default' => 20, 'min' => 0, 'max' => 500, 'step' => 5, 'unit' => 'PX'],
|
||||
['id' => 'speed', 'type' => 'spinner', 'label' => '切换速度', 'default' => 0, 'min' => 0, 'max' => 3000, 'step' => 100, 'unit' => '毫秒', 'description' => '设置为“0”,则为自动模式:根据幻灯片大小自动设置最佳速度'],
|
||||
['id' => 'autoplay', 'type' => 'switcher', 'label' => '自动播放', 'default' => true],
|
||||
['id' => 'interval', 'type' => 'spinner', 'label' => '停顿时间', 'default' => 4, 'min' => 0, 'max' => 20, 'dependency' => ['autoplay', '!=', '']],
|
||||
['id' => 'interval', 'type' => 'spinner', 'label' => '停顿时间', 'default' => 4, 'min' => 0, 'max' => 20, 'unit' => '秒', 'dependency' => ['autoplay', '!=', '']],
|
||||
];
|
||||
$fields = [
|
||||
['id' => 'layout_ratio', 'type' => 'slider', 'label' => '幻灯片宽度占比', 'default' => 60, 'min' => 0, 'max' => 100],
|
||||
['id' => 'slide_scale', 'type' => 'spinner', 'label' => '幻灯片长宽比', 'default' => 50, 'min' => 0, 'max' => 500, 'step' => 5],
|
||||
['id' => 'obs_animation', 'type' => 'select', 'label' => '入场动画', 'default' => '', 'options' => self::animation_options(), 'description' => '页面滚动到封面区域时触发的入场动画[注意:需合理配置适当启用]'],
|
||||
['id' => 'animation_repeat', 'type' => 'switcher', 'label' => '重复动画', 'default' => false, 'dependency' => ['obs_animation', '!=', ''], 'description' => '每次滚动到封面区域都触发动画[注意:有性能消耗,部分老旧浏览器会有闪烁问题]'],
|
||||
['type' => 'subheading', 'content' => 'PC端布局配置'],
|
||||
['id' => 'layout_ratio', 'type' => 'slider', 'label' => '幻灯片宽度占比', 'default' => 60, 'min' => 0, 'max' => 100, 'step' => 1, 'unit' => '%'],
|
||||
['id' => 'slide_scale', 'type' => 'spinner', 'label' => '幻灯片长宽比', 'default' => 50, 'min' => 0, 'max' => 500, 'step' => 5, 'unit' => '%'],
|
||||
['id' => 'pc_row', 'type' => 'button_set', 'label' => '右侧卡片列数', 'default' => 2, 'options' => [1 => '1列', 2 => '2列', 3 => '3列', 4 => '4列']],
|
||||
['id' => 'pc_col', 'type' => 'button_set', 'label' => '右侧卡片行数', 'default' => 2, 'options' => [1 => '1行', 2 => '2行', 3 => '3行', 4 => '4行']],
|
||||
['id' => 'm_layout_type', 'type' => 'radio', 'label' => '移动端布局方式', 'default' => 'vertical', 'options' => ['default' => '保持PC端布局', 'vertical' => '上下布局', 'hide_slide' => '隐藏幻灯片', 'hide_cover' => '隐藏卡片']],
|
||||
['type' => 'subheading', 'content' => '移动端布局方式'],
|
||||
['id' => 'm_layout_type', 'type' => 'radio', 'label' => '布局方式', 'default' => 'vertical', 'options' => ['default' => '保持PC端布局', 'vertical' => '上下布局', 'hide_slide' => '隐藏幻灯片', 'hide_cover' => '隐藏卡片']],
|
||||
['id' => 'm_row', 'type' => 'button_set', 'label' => '移动端卡片单行数量', 'default' => 2, 'dependency' => ['m_layout_type', 'any', 'hide_slide,vertical'], 'options' => [1 => '1个', 2 => '2个', 3 => '3个', 4 => '4个', 6 => '6个']],
|
||||
['id' => 'm_cover_ratio', 'type' => 'spinner', 'label' => '移动端卡片长宽比', 'default' => 45, 'min' => 5, 'max' => 300, 'step' => 5, 'dependency' => ['m_layout_type', 'any', 'hide_slide,vertical']],
|
||||
['id' => 'slides', 'type' => 'group', 'label' => '幻灯片内容', 'max_items' => 30, 'min_items' => 1, 'default' => $combo_slides, 'fields' => $slide_fields],
|
||||
['id' => 'm_cover_ratio', 'type' => 'spinner', 'label' => '移动端卡片长宽比', 'default' => 45, 'min' => 5, 'max' => 300, 'step' => 5, 'unit' => '%', 'dependency' => ['m_layout_type', 'any', 'hide_slide,vertical']],
|
||||
['type' => 'subheading', 'content' => '左侧幻灯片'],
|
||||
['id' => 'slides', 'type' => 'group', 'label' => '幻灯片内容', 'item_label' => '幻灯片', 'max_items' => 30, 'min_items' => 1, 'default' => $combo_slides, 'fields' => $slide_fields, 'description' => '注意:当添加的幻灯片内容只有一个的时候,则不会以幻灯片的形式展示(不能滑动切换,相当于单个图片或视频)<div class="c-yellow">由于移动端多数浏览器不支持视频背景功能,所以移动端不会显示视频!</div>'],
|
||||
['id' => 'option', 'type' => 'accordion', 'label' => '幻灯片设置', 'default' => ['button' => true, 'pagination' => true, 'effect' => 'slide', 'spacebetween' => 20, 'speed' => 0, 'interval' => 4, 'autoplay' => true, 'loop' => true], 'fields' => $combo_option],
|
||||
['id' => 'covers', 'type' => 'group', 'label' => '右侧图文视频卡片', 'max_items' => 30, 'default' => array_fill(0, 4, ['image' => $asset_uri . 'user_t.jpg', 'title' => '']), 'fields' => [
|
||||
['id' => 'image', 'type' => 'upload', 'label' => '图片背景', 'default' => '', 'library' => 'image'], ['id' => 'video', 'type' => 'upload', 'label' => '视频背景', 'default' => '', 'library' => 'video'], ['id' => 'hide', 'type' => 'radio', 'label' => '显示规则', 'default' => '', 'options' => ['' => '全部显示', 'pc' => 'PC端不显示', 'm' => '移动端不显示']], ['id' => 'title', 'type' => 'textarea', 'label' => '文字', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 1]], ['id' => 'link', 'type' => 'link', 'label' => '跳转链接', 'default' => []],
|
||||
]],
|
||||
['id' => 'mask_opacity', 'type' => 'slider', 'label' => '遮罩透明度', 'default' => 0, 'min' => 0, 'max' => 90], ['id' => 'font_size_pc', 'type' => 'spinner', 'label' => 'PC端字体大小', 'default' => 16, 'min' => 10, 'max' => 80, 'step' => 2], ['id' => 'font_size_m', 'type' => 'spinner', 'label' => '移动端字体大小', 'default' => 14, 'min' => 10, 'max' => 80, 'step' => 2], ['id' => 'font_bold', 'type' => 'switcher', 'label' => '粗体显示', 'default' => false], ['id' => 'font_color', 'type' => 'color', 'label' => '文字颜色', 'default' => ''],
|
||||
['type' => 'subheading', 'content' => '右侧图文视频卡片'],
|
||||
['id' => 'covers', 'type' => 'group', 'label' => '右侧图文视频卡片', 'item_label' => '卡片', 'max_items' => 30, 'default' => array_fill(0, 4, ['image' => $asset_uri . 'user_t.jpg', 'title' => '']), 'fields' => self::cover_fields(), 'description' => '<div class="c-yellow">注意:由于移动端多数浏览器不支持视频背景功能,所以移动端不会显示视频!</div>'],
|
||||
['type' => 'subheading', 'content' => '卡片样式配置'],
|
||||
['id' => 'mask_opacity', 'type' => 'slider', 'label' => '遮罩透明度', 'default' => 0, 'min' => 0, 'max' => 90, 'step' => 1, 'unit' => '%', 'description' => '图片上显示的黑色遮罩层的透明度'],
|
||||
['id' => 'font_size_pc', 'type' => 'spinner', 'label' => 'PC端字体大小', 'default' => 16, 'min' => 10, 'max' => 80, 'step' => 2, 'unit' => 'px'],
|
||||
['id' => 'font_size_m', 'type' => 'spinner', 'label' => '移动端字体大小', 'default' => 14, 'min' => 10, 'max' => 80, 'step' => 2, 'unit' => 'px'],
|
||||
['id' => 'font_bold', 'type' => 'switcher', 'label' => '粗体显示', 'default' => false],
|
||||
['id' => 'font_color', 'type' => 'color', 'label' => '文字颜色', 'default' => ''],
|
||||
];
|
||||
}
|
||||
return $fields;
|
||||
@@ -447,6 +585,32 @@ final class WidgetSchemas {
|
||||
];
|
||||
}
|
||||
|
||||
/** 图文视频封面卡片单元(移动端不显示视频)。 */
|
||||
private static function cover_fields(): array {
|
||||
return [
|
||||
['id' => 'image', 'type' => 'upload', 'label' => '图片背景', 'default' => '', 'library' => 'image'],
|
||||
['id' => 'video', 'type' => 'upload', 'label' => '视频背景', 'default' => '', 'library' => 'video', 'description' => '(必填)图片背景、视频背景至少二选一,如果同时设置则PC端视频优先(PC端视频加载失败则显示图片),移动端只显示图片'],
|
||||
['id' => 'hide', 'type' => 'radio', 'label' => '显示规则', 'default' => '', 'options' => ['' => '全部显示', 'pc' => 'PC端不显示', 'm' => '移动端不显示']],
|
||||
['id' => 'title', 'type' => 'textarea', 'label' => '文字', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 1], 'description' => '支持HTML代码'],
|
||||
['id' => 'link', 'type' => 'link', 'label' => '跳转链接', 'default' => []],
|
||||
];
|
||||
}
|
||||
|
||||
private static function cta_palette(): array {
|
||||
return ['' => '默认'] + self::text_palette();
|
||||
}
|
||||
|
||||
private static function tag_palette(): array {
|
||||
return ['' => '默认'] + self::zib_palette();
|
||||
}
|
||||
|
||||
private static function text_palette(): array {
|
||||
return [
|
||||
'c-red' => '红色', 'c-red-2' => '深红', 'c-yellow' => '黄色', 'c-yellow-2' => '深黄', 'c-cyan' => '青色',
|
||||
'c-blue' => '蓝色', 'c-blue-2' => '深蓝', 'c-green' => '绿色', 'c-green-2' => '深绿', 'c-purple' => '紫色', 'c-purple-2' => '深紫',
|
||||
];
|
||||
}
|
||||
|
||||
private static function card_fields(bool $link = false): array {
|
||||
$fields = [['id' => 'title', 'type' => 'text', 'label' => '标题', 'default' => ''], ['id' => 'description', 'type' => 'textarea', 'label' => '说明', 'default' => '']];
|
||||
if ($link) {
|
||||
@@ -478,13 +642,12 @@ final class WidgetSchemas {
|
||||
private static function notice_post_fields(): array {
|
||||
return [
|
||||
['id' => 'cat', 'type' => 'text', 'label' => '分类限制', 'default' => '', 'sanitize' => 'reference_ids'],
|
||||
['id' => 'topics', 'type' => 'text', 'label' => '专题限制', 'default' => '', 'sanitize' => 'reference_ids'],
|
||||
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => '', 'options' => self::posts_orderby()],
|
||||
['id' => 'order', 'type' => 'radio', 'label' => '顺序', 'default' => 'desc', 'options' => ['desc' => '升序', 'asc' => '降序']],
|
||||
['id' => 'order', 'type' => 'radio', 'label' => '顺序', 'default' => 'desc', 'options' => ['desc' => '降序', 'asc' => '升序']],
|
||||
];
|
||||
}
|
||||
|
||||
private static function pay_types(): array {
|
||||
return ['' => '全部', 'reading' => '付费阅读', 'download' => '付费下载', 'gallery' => '付费图库', 'video' => '付费视频', 'vip' => 'VIP 专属'];
|
||||
return ['1' => '付费阅读', '2' => '付费下载', '5' => '付费图片', '6' => '付费视频'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ test('icon card renderers keep vertical and horizontal legacy DOM contracts sepa
|
||||
assert.doesNotMatch(helper, /aksrt-icon-cover-card/);
|
||||
assert.match(widgets, /'customize_icon' => \$card\['customize_icon'\] \?\? ''/);
|
||||
assert.doesNotMatch(widgets, /\$card\['custom_icon'\]/);
|
||||
assert.doesNotMatch(widgets, /\$card\['icon_image'\]/);
|
||||
// combo_info legitimately renders icon_image cards; icon cards must not leak media-card fields.
|
||||
assert.doesNotMatch(widgets, /\$card\['description'\]/);
|
||||
assert.match(header, /empty\(\$card\['custom_icon'\]\)/);
|
||||
});
|
||||
|
||||
@@ -45,7 +45,7 @@ test('paid article and sidebar widget use the Zibll purchase-card DOM', () => {
|
||||
assert.match(pay, /支付金额/);
|
||||
assert.match(pay, /hidden-box show/);
|
||||
assert.match(widget, /'layout' => 'widget'/);
|
||||
assert.match(shell, /\['post_pay', 'tab_posts'/);
|
||||
assert.match(shell, /'post_pay', 'tab_posts', 'posts'/);
|
||||
assert.doesNotMatch(single, /aksrt-panel pay-box pay-type-download/);
|
||||
assert.doesNotMatch(single, /get_template_part\('templates\/parts\/resource-download-box'\)/);
|
||||
assert.match(single, /aksrt_get_paid_box_html\(\$post\)/);
|
||||
|
||||
@@ -8,9 +8,10 @@ const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('modern widget schemas expose Zibll field names and defaults', () => {
|
||||
const source = read('src/WidgetSystem/WidgetSchemas.php');
|
||||
for (const field of ['mini_opt', 'paginate', 'tabs', "'id' => 'cat'", "'id' => 'topics'", 'layout_ratio', 'slide_scale', 'm_layout_type', 'm_cover_ratio', 'image_layer', 'image_select']) {
|
||||
for (const field of ['mini_opt', 'paginate', 'tabs', "'id' => 'cat'", "'id' => 'zibpay_type'", 'layout_ratio', 'slide_scale', 'm_layout_type', 'm_cover_ratio', 'image_layer', 'image_select']) {
|
||||
assert.match(source, new RegExp(field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
}
|
||||
assert.doesNotMatch(source, /'id' => 'topics'/);
|
||||
assert.match(source, /'热门推荐'.*'orderby' => 'views'/s);
|
||||
assert.match(source, /'最近更新'.*'orderby' => 'modified'/s);
|
||||
assert.match(source, /'猜你喜欢'.*'orderby' => 'rand'/s);
|
||||
@@ -19,13 +20,16 @@ test('modern widget schemas expose Zibll field names and defaults', () => {
|
||||
test('tab and slider renderers retain reference DOM contracts', () => {
|
||||
const content = read('src/WidgetSystem/Renderers/ContentRenderer.php');
|
||||
const slider = read('src/WidgetSystem/Renderers/SliderRenderer.php');
|
||||
for (const token of ['widget-tab-post', 'posts-mini-lists', 'index-tab', 'tab-nav-theme', 'tab-pane fade', 'post_ajax_trigger', 'post_ajax_loader', 'widget-ajaxpager']) assert.match(content, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
for (const token of ['widget-tab-post', 'posts-mini-lists', 'index-tab', 'tab-nav-theme', 'tab-pane fade', 'ias-btn', 'post_ajax_loader', 'widget-ajaxpager']) assert.match(content, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
assert.doesNotMatch(content, /post_ajax_trigger/);
|
||||
for (const token of ['relative zib-slider', 'new-swiper slide-widget', 'swiper-wrapper', 'swiper-slide', 'slide-layer', 'data-swiper-parallax', 'swiper-button-prev', 'swiper-pagination', 'widget-slider-graphic', 'slider-col', 'cover-col', 'widget-graphic-cover']) assert.match(slider, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
});
|
||||
|
||||
test('reference slider CSS includes responsive height and combo selectors', () => {
|
||||
const css = read('assets/widgets/frontend.css');
|
||||
for (const token of ['.new-swiper.slide-widget.scale-height', '--scale-height', '--pc-height', '--m-height', '.widget-slider-graphic', '.m-hide_slide', '.m-hide_cover', '--m-cover-ratio']) assert.match(css, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
const mainCss = read('assets/css/main.css');
|
||||
for (const token of ['.new-swiper.slide-widget.scale-height', '--scale-height', '--pc-height', '--m-height', '.widget-slider-graphic', '.m-hide_slide', '.m-hide_cover']) assert.match(css, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
for (const token of ['--m-cover-ratio', '--cover-ratio', '--layout-ratio']) assert.match(mainCss + css, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
});
|
||||
|
||||
test('icon card widgets expose Zibll vertical and horizontal contracts', () => {
|
||||
@@ -71,9 +75,9 @@ test('vertical notice supports only custom and article sources with Zibll swiper
|
||||
assert.match(schema, new RegExp(field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
}
|
||||
assert.match(schema, /'options' => \['' => '自定义', 'posts' => '文章'\]/);
|
||||
assert.doesNotMatch(schema, /forums_opts|notice_forum_fields|zibpay_type|zibpay_price|zibpay_points_price|sales_volume/);
|
||||
assert.doesNotMatch(schema, /forums_opts|notice_forum_fields|zibpay_price|zibpay_points_price|sales_volume/);
|
||||
assert.doesNotMatch(renderer, /zib_bbs_get_posts_query|forums_opts/);
|
||||
assert.doesNotMatch(postQuery, /zibpay_type|zibpay_price|zibpay_points_price|sales_volume/);
|
||||
assert.doesNotMatch(postQuery, /zibpay_price|zibpay_points_price|sales_volume/);
|
||||
assert.doesNotMatch(form + controller + adminJs, /aksrt_widget_remote_options|aksrt-widget-remote-select|aksrt-widget-remote-picker/);
|
||||
for (const token of ['swiper-bulletin mb20 ', 'notice-slide', 'new-swiper', 'data-direction="vertical"', 'data-loop="true"', 'data-autoplay="1"', 'bulletin-icon flex jc shrink0']) {
|
||||
assert.match(renderer, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
|
||||
Reference in New Issue
Block a user