diff --git a/.gitignore b/.gitignore index 62efbd9f..959bef15 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ !/tests/widget-system/auth-source-contract.test.cjs !/tests/widget-system/user-badges-contract.test.cjs !/tests/widget-system/user-features-contract.test.cjs +!/tests/widget-system/zibll-modern-widgets-contract.test.cjs !/tests/widget-system/notification-preferences-contract.test.cjs !/tests/widget-system/message-center-contract.test.cjs !/tests/widget-system/message-center-url-harness.php diff --git a/assets/widgets/admin-core.js b/assets/widgets/admin-core.js index fcb5b3b8..4023fac7 100644 --- a/assets/widgets/admin-core.js +++ b/assets/widgets/admin-core.js @@ -19,9 +19,21 @@ function dependencyVisible(rule, current) { rule = parseDependency(rule); if (rule.length < 3) return true; - var expected = String(rule[2]); - var actual = String(current == null ? '' : current); - return rule[1] === '==' ? actual === expected : actual !== expected; + if (Array.isArray(rule[0])) { + return rule.every(function (part) { return dependencyVisible(part, current && typeof current === 'object' ? current[part[0]] : ''); }); + } + var ids = String(rule[0]).split('|'); + var operators = String(rule[1]).split('|'); + var expected = String(rule[2]).split('|'); + var values = current && typeof current === 'object' ? current : Object.fromEntries([[ids[0], current]]); + return ids.every(function (id, index) { + var actual = String(values[id] == null ? '' : values[id]); + var wanted = String(expected[index] == null ? expected[0] : expected[index]); + var operator = operators[index] || operators[0] || '!='; + if (operator === '==') return actual === wanted; + if (operator === 'any') return wanted.split(',').indexOf(actual) !== -1; + return actual !== wanted; + }); } function replaceRepeaterIndexes(name, rowIndexes) { diff --git a/assets/widgets/admin.js b/assets/widgets/admin.js index ef764a5e..b97437c9 100644 --- a/assets/widgets/admin.js +++ b/assets/widgets/admin.js @@ -81,12 +81,15 @@ } function evaluateDependencies($form) { - $form.find('[data-dependency]').each(function () { + $form.find('[data-dependency]').each(function () { const rule = core.parseDependency(this.dataset.dependency || '[]'); const $row = $(this).closest('.aksrt-widget-repeater__row'); - const current = fieldValue($row.length ? $row : $form, String(rule[0])); + const scope = $row.length ? $row : $form; + const ids = Array.isArray(rule[0]) ? rule[0].map((part) => part[0]) : String(rule[0] || '').split('|'); + const current = {}; + ids.forEach((id) => { current[id] = fieldValue(scope, id); }); this.hidden = !core.dependencyVisible(rule, current); - }); + }); } $(document) @@ -116,7 +119,7 @@ }) .on('click', '.aksrt-widget-upload__choose', function () { const $wrap = $(this).closest('.aksrt-widget-upload'); - const frame = wp.media({ title: '选择媒体', multiple: false }); + const frame = wp.media({ title: $wrap.data('media-library') === 'video' ? '选择视频' : '选择图片', library: $wrap.data('media-library') ? { type: $wrap.data('media-library') } : undefined, multiple: false }); frame.on('select', () => { const attachment = frame.state().get('selection').first().toJSON(); $wrap.find('input[type="url"]').val(attachment.url).trigger('change'); diff --git a/assets/widgets/frontend.css b/assets/widgets/frontend.css index 856e0725..f640dabe 100644 --- a/assets/widgets/frontend.css +++ b/assets/widgets/frontend.css @@ -150,4 +150,57 @@ .aksrt-widget :where(a, button, input, select, textarea, [tabindex]):focus-visible { outline-color: Highlight !important; } } .aksrt-widget--animate { animation: aksrt-widget-fade .55s both; }.aksrt-widget--animate-slideup { animation-name: aksrt-widget-slideup; }.aksrt-widget--animate-slidedown { animation-name: aksrt-widget-slidedown; }.aksrt-widget--animate-slideright { animation-name: aksrt-widget-slideright; }.aksrt-widget--animate-slideleft { animation-name: aksrt-widget-slideleft; }.aksrt-widget--animate-zoomin { animation-name: aksrt-widget-zoomin; }.aksrt-widget--animate-zoomout { animation-name: aksrt-widget-zoomout; }.aksrt-widget--animate-repeat { animation-iteration-count: infinite; animation-duration: 2s; } + +/* Zibll modern widget contracts */ +.widget-tab-post .tab-content > .tab-pane { display: none; } +.widget-tab-post .tab-content > .tab-pane.active { display: block; } +.widget-tab-post .list-inline { margin: 0 0 15px; padding: 0; list-style: none; } +.widget-tab-post .list-inline > li { display: inline-block; margin: 0 15px 0 0; } +.widget-tab-post .list-inline > li > a { color: inherit; text-decoration: none; cursor: pointer; } +.widget-tab-post .list-inline > li.active > a { color: var(--focus-color, #f04494); } +.widget-tab-post .tab-nav-theme > li { position: relative; padding-bottom: 5px; } +.widget-tab-post .tab-nav-theme > li.active::before { content: ''; position: absolute; right: 0; bottom: 0; left: 0; height: 2px; background: var(--focus-color, #f04494); } +.widget-tab-post .post_ajax_loader { min-height: 40px; } +.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); } +.new-swiper.slide-widget.scale-height { height: 0; padding-bottom: var(--scale-height, 35%); } +.new-swiper.slide-widget.scale-height .swiper-wrapper { position: absolute; inset: 0; height: 100%; } +.new-swiper.slide-widget .swiper-slide > a, .new-swiper.slide-widget .swiper-slide > span { position: relative; display: block; width: 100%; height: 100%; } +.new-swiper.slide-widget .swiper-slide img, .new-swiper.slide-widget .swiper-slide video { width: 100%; height: 100%; object-fit: cover; } +.new-swiper.slide-widget .slide-layer { z-index: 1; } +.new-swiper.slide-widget .slide-text { z-index: 2; } +.new-swiper.slide-widget .slide-text.left-bottom { left: 8%; bottom: 10%; } +.new-swiper.slide-widget .slide-text.left-conter { left: 8%; top: 50%; transform: translateY(-50%); } +.new-swiper.slide-widget .slide-text.conter-conter, .new-swiper.slide-widget .slide-text.conter-bottom { left: 50%; transform: translateX(-50%); text-align: center; } +.new-swiper.slide-widget .slide-text.conter-conter { top: 50%; transform: translate(-50%, -50%); } +.new-swiper.slide-widget .slide-text.conter-bottom { bottom: 10%; } +.new-swiper.slide-widget .slide-text.right-conter { right: 8%; top: 50%; transform: translateY(-50%); text-align: right; } +.new-swiper.slide-widget .slide-text.right-bottom { right: 8%; bottom: 10%; text-align: right; } +.new-swiper.slide-widget .slide-title { font-size: var(--text-size-pc, 30px); } +.new-swiper.slide-widget .slide-desc { margin-top: .6em; font-size: 60%; } +@media (max-width: 767px) { + .new-swiper.slide-widget .swiper-wrapper { height: var(--m-height, auto); } + .new-swiper.slide-widget .slide-title { font-size: var(--text-size-m, 20px); } + .widget-slider-graphic { display: block; } + .widget-slider-graphic.m-default { display: flex; } + .widget-slider-graphic.m-default .slider-col { width: var(--layout-ratio, 60%); } + .widget-slider-graphic.m-default .cover-col { width: calc(100% - var(--layout-ratio, 60%)); } + .widget-slider-graphic .slider-col, .widget-slider-graphic .cover-col { width: 100%; } + .widget-slider-graphic.m-hide_slide .slider-col, .widget-slider-graphic.m-hide_cover .cover-col { display: none; } + .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); } } diff --git a/assets/widgets/frontend.js b/assets/widgets/frontend.js index 60f1ddb6..3a8ffa24 100644 --- a/assets/widgets/frontend.js +++ b/assets/widgets/frontend.js @@ -191,18 +191,25 @@ 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('[data-segment]'); + 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); }); - var panel = tabs.querySelector('[data-widget-items]'); + 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]'); panel.dataset.segment = button.dataset.segment; panel.setAttribute('aria-labelledby', button.id); - loadItems(panel, 1, false); + loadItems(panel, 1, false); }); } @@ -261,7 +268,7 @@ 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').forEach(initTabs); + 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); diff --git a/src/WidgetSystem/FieldSchema.php b/src/WidgetSystem/FieldSchema.php index 099ebab5..15992fa5 100644 --- a/src/WidgetSystem/FieldSchema.php +++ b/src/WidgetSystem/FieldSchema.php @@ -13,7 +13,7 @@ final class FieldSchema { public static function supported_types(): array { return [ 'text', 'textarea', 'number', 'spinner', 'slider', 'switcher', 'radio', - 'button_set', 'select', 'checkbox', 'color', 'palette', 'icon', 'upload', + 'button_set', 'select', 'checkbox', 'color', 'palette', 'image_select', 'icon', 'upload', 'link', 'spacing', 'background', 'group', 'fieldset', 'accordion', 'content', 'subheading', 'submessage', ]; @@ -59,6 +59,7 @@ final class FieldSchema { case 'button_set': case 'select': case 'palette': + case 'image_select': return self::choice($field, $value); case 'checkbox': return self::checkbox($field, $value); @@ -86,6 +87,11 @@ final class FieldSchema { if (($field['sanitize'] ?? '') === 'ids') { return implode(',', array_values(array_unique(array_filter(array_map('absint', preg_split('/[,,\s]+/', (string) $value)))))); } + if (($field['sanitize'] ?? '') === 'reference_ids') { + $tokens = preg_split('/[,,\s]+/', trim((string) $value), -1, PREG_SPLIT_NO_EMPTY); + $tokens = array_map(static fn($token): string => preg_match('/^-?\d+$/', (string) $token) ? (string) (int) $token : '', $tokens ?: []); + return implode(',', array_values(array_unique(array_filter($tokens, static fn($token): bool => $token !== '')))); + } return !empty($field['allow_html']) ? wp_kses_post((string) $value) : sanitize_text_field((string) $value); } } diff --git a/src/WidgetSystem/FormRenderer.php b/src/WidgetSystem/FormRenderer.php index 45cb83d2..c7ebd254 100644 --- a/src/WidgetSystem/FormRenderer.php +++ b/src/WidgetSystem/FormRenderer.php @@ -51,6 +51,7 @@ final class FormRenderer { case 'radio': case 'button_set': case 'palette': + case 'image_select': self::radios($field_id, $field_name, (array) ($field['options'] ?? []), (string) $value, $type); break; case 'select': @@ -65,10 +66,11 @@ final class FormRenderer { self::number($field_id, $field_name, $field, $value, $type); break; case 'textarea': - echo ''; + $rows = max(1, (int) ($field['attributes']['rows'] ?? 4)); + echo ''; break; case 'upload': - self::upload($field_id, $field_name, (string) $value); + self::upload($field_id, $field_name, (string) $value, (string) ($field['library'] ?? '')); break; case 'color': echo ''; @@ -113,7 +115,13 @@ final class FormRenderer { echo '
'; } @@ -148,13 +156,14 @@ final class FormRenderer { echo ''; } - private static function upload(string $id, string $name, string $value): void { - echo ''; } private static function link(SchemaWidget $widget, string $key, array $value): void { @@ -207,7 +216,11 @@ final class FormRenderer { } private static function group_row(SchemaWidget $widget, array $field, array $row, string $key, string $index): void { - echo '