diff --git a/assets/css/admin/pay-meta-box.css b/assets/css/admin/pay-meta-box.css index 210a8342..025c7cfa 100644 --- a/assets/css/admin/pay-meta-box.css +++ b/assets/css/admin/pay-meta-box.css @@ -63,8 +63,7 @@ .aksrt-pay-meta__inline, .aksrt-pay-meta__number, .aksrt-pay-meta__upload, -.aksrt-pay-meta__buttons, -.aksrt-pay-meta__group--inline { +.aksrt-pay-meta__buttons { display: flex; align-items: center; gap: 7px; @@ -120,20 +119,202 @@ display: grid; gap: 7px; margin: 0 0 10px; - padding: 10px; + padding: 0; + overflow: hidden; border: 1px solid #e3e5e8; border-radius: 4px; background: #f8f9fa; } .aksrt-pay-meta__group--inline { - grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; + display: flex; + align-items: center; + gap: 7px; + margin: 0 0 10px; + padding: 10px; + border: 1px solid #e3e5e8; + border-radius: 4px; + background: #f8f9fa; } -.aksrt-pay-meta__group > .button-link-delete { +.aksrt-pay-meta__group-head { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 10px; + margin: 0; + background: #fff; + border-bottom: 1px solid #e3e5e8; +} + +.aksrt-pay-meta__group.is-open .aksrt-pay-meta__group-head { + border-bottom-color: #e3e5e8; +} + +.aksrt-pay-meta__group-drag { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + color: #8c8f94; + cursor: move; + border-radius: 3px; +} + +.aksrt-pay-meta__group-drag:hover { + color: #1d2327; + background: #f0f0f1; +} + +.aksrt-pay-meta__group-drag .dashicons { + font-size: 18px; + width: 18px; + height: 18px; +} + +.aksrt-pay-meta__group-toggle { + display: flex; + flex: 1; + align-items: center; + gap: 6px; + min-width: 0; + padding: 0; + font-size: 13px; + font-weight: 600; + color: #1d2327; + text-align: left; + background: none; + border: 0; + box-shadow: none; + cursor: pointer; +} + +.aksrt-pay-meta__group-toggle:focus { + outline: 1px solid #2271b1; + outline-offset: 2px; +} + +.aksrt-pay-meta__group-title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.aksrt-pay-meta__group-caret { + flex: 0 0 auto; + color: #8c8f94; + font-size: 16px; + width: 16px; + height: 16px; + transition: transform 0.15s ease; +} + +.aksrt-pay-meta__group.is-open .aksrt-pay-meta__group-caret { + transform: rotate(180deg); +} + +.aksrt-pay-meta__group-body { + display: none; + gap: 10px; + padding: 12px 10px; +} + +.aksrt-pay-meta__group.is-open .aksrt-pay-meta__group-body { + display: grid; +} + +.aksrt-pay-meta__group-body > .button-link-delete { justify-self: start; } +.aksrt-pay-meta__group-body > .description { + margin: -4px 0 0; + color: #777; + font-size: 12px; + line-height: 1.55; +} + +.aksrt-pay-meta__group-field { + display: grid; + gap: 4px; +} + +.aksrt-pay-meta__group-field > label { + color: #1d2327; + font-size: 12px; + font-weight: 600; +} + +.aksrt-pay-meta__group-field--split { + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: 10px; +} + +.aksrt-pay-meta__group-field--split > div { + display: grid; + gap: 4px; +} + +.aksrt-pay-meta__group-field input[type="text"], +.aksrt-pay-meta__group-field input[type="url"], +.aksrt-pay-meta__group-field textarea { + width: 100%; +} + +.aksrt-pay-meta__group-field .aksrt-icon-picker { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.aksrt-pay-meta__group-field .aksrt-icon-picker__preview { + display: inline-flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + font-size: 16px; + color: #1d2327; + background: #fff; + border: 1px solid #dcdcde; + border-radius: 3px; +} + +.aksrt-pay-meta__group-field .aksrt-icon-picker__value { + flex: 1; + min-width: 160px; +} + +.aksrt-pay-meta__class-swatches { + display: flex; + flex-wrap: wrap; + gap: 5px; +} + +.aksrt-pay-meta__class-swatch { + width: 26px; + height: 22px; + padding: 0; + border: 2px solid #fff; + border-radius: 3px; + box-shadow: 0 0 0 1px #dcdcde; + cursor: pointer; +} + +.aksrt-pay-meta__class-swatch:hover { + box-shadow: 0 0 0 1px #8c8f94; +} + +.aksrt-pay-meta__class-swatch.is-active { + box-shadow: 0 0 0 2px #2271b1; +} + +.aksrt-pay-meta__group.is-dragging { + opacity: 0.5; +} + .aksrt-pay-meta__notice { margin: 16px 18px; padding: 12px 14px; @@ -157,6 +338,10 @@ } .aksrt-pay-meta__group--inline { + flex-wrap: wrap; + } + + .aksrt-pay-meta__group-field--split { grid-template-columns: 1fr; } } diff --git a/assets/css/frontend/resource-meta/part.css b/assets/css/frontend/resource-meta/part.css index fca8faeb..b8e05418 100644 --- a/assets/css/frontend/resource-meta/part.css +++ b/assets/css/frontend/resource-meta/part.css @@ -1071,6 +1071,30 @@ background: linear-gradient(135deg, #333333 0%, #1a1a1a 100%); } +/* 后台自定义按钮配色:元素同时带 legacy .but 的 b-*/jb-* 类, + 它们提供 --this-bg / --this-color,这里读取即可覆盖云服务渐变 */ +.resource-download-box__btn--custom { + background: var(--this-bg, var(--color-primary, #3B82F6)); + color: var(--this-color, #fff); +} + +.resource-download-box__btn--custom:hover { + background: var(--this-bg, var(--color-primary-active, #2563EB)); + color: var(--this-color, #fff); + filter: brightness(0.94); +} + +.resource-download-box__btn--custom .resource-download-box__service-badge { + background: rgb(255 255 255 / 25%); +} + +/* 移动端下载条的 hover 规则作用域更窄,同作用域再声明一次以保持自定义色 */ +.resource-mobile-download-bar .resource-download-box__btn--custom:hover { + background: var(--this-bg, var(--color-primary-active, #2563EB)); + color: var(--this-color, #fff); + filter: brightness(0.94); +} + /* 云服务徽章 */ .resource-download-box__service-badge { display: inline-block; diff --git a/assets/js/admin-pay-meta-box.js b/assets/js/admin-pay-meta-box.js index 4e1c5aa5..d8e07560 100644 --- a/assets/js/admin-pay-meta-box.js +++ b/assets/js/admin-pay-meta-box.js @@ -4,6 +4,9 @@ var root = document.querySelector('[data-aksrt-pay-meta]'); if (!root) return; + var cfg = window.aksrtPayMetaBox || {}; + var rowTitleFallback = (cfg.i18n && cfg.i18n.downloadUrl) || '下载地址'; + function selected(name) { var input = root.querySelector('input[name="aksrt_pay[' + name + ']"]:checked'); return input ? input.value : ''; @@ -79,6 +82,74 @@ frame.open(); } + function rowTitle(row) { + var label = row.querySelector('[data-group-label]'); + var url = row.querySelector('[data-group-url]'); + var text = label ? String(label.value || '').trim() : ''; + if (text === '') { + text = url ? String(url.value || '').trim() : ''; + } + return text !== '' ? text : rowTitleFallback; + } + + function syncRowTitle(row) { + var title = row.querySelector('[data-group-title]'); + if (title) title.textContent = rowTitle(row); + } + + function syncRowColor(row) { + var input = row.querySelector('[data-group-class-input]'); + if (!input) return; + var value = String(input.value || ''); + row.querySelectorAll('[data-group-class]').forEach(function (swatch) { + var active = swatch.getAttribute('data-group-class') === value; + swatch.classList.toggle('is-active', active); + swatch.setAttribute('aria-pressed', active ? 'true' : 'false'); + }); + } + + // Restore contiguous input names so PHP receives an ordered array after any + // reorder / removal instead of relying on DOM order. Anchored to this list's + // own group so sibling groups (attributes, video_episode) are never touched. + function reindexRows(list) { + var group = list.getAttribute('data-group-list') || '[^\\]]+'; + var pattern = new RegExp('aksrt_pay\\[' + group + '\\]\\[\\d+\\]'); + list.querySelectorAll('[data-group-row]').forEach(function (row, index) { + row.querySelectorAll('[name]').forEach(function (input) { + input.name = input.name.replace(pattern, 'aksrt_pay[' + group + '][' + index + ']'); + }); + }); + } + + function toggleRow(row, force) { + var open = typeof force === 'boolean' ? force : !row.classList.contains('is-open'); + row.classList.toggle('is-open', open); + var toggle = row.querySelector('[data-group-toggle]'); + if (toggle) toggle.setAttribute('aria-expanded', open ? 'true' : 'false'); + } + + function initRow(row) { + syncRowTitle(row); + syncRowColor(row); + var drag = row.querySelector('[data-group-drag]'); + if (drag) drag.draggable = true; + } + + function dragAfterElement(list, y) { + var rows = Array.prototype.slice.call(list.querySelectorAll('[data-group-row]:not(.is-dragging)')); + var best = null; + var bestOffset = -Infinity; + rows.forEach(function (row) { + var box = row.getBoundingClientRect(); + var offset = y - box.top - box.height / 2; + if (offset < 0 && offset > bestOffset) { + bestOffset = offset; + best = row; + } + }); + return best; + } + function addGroup(button) { var id = button.getAttribute('data-group-add'); var list = root.querySelector('[data-group-list="' + id + '"]'); @@ -86,6 +157,11 @@ if (!list || !template) return; var index = list.querySelectorAll('[data-group-row]').length; list.insertAdjacentHTML('beforeend', template.innerHTML.replaceAll('__INDEX__', String(index))); + var added = list.lastElementChild; + if (added && added.hasAttribute('data-group-row')) { + initRow(added); + toggleRow(added, false); + } } root.addEventListener('click', function (event) { @@ -122,15 +198,88 @@ addGroup(add); return; } + var toggle = target.closest('[data-group-toggle]'); + if (toggle) { + event.preventDefault(); + var toggleRowEl = toggle.closest('[data-group-row]'); + if (toggleRowEl) toggleRow(toggleRowEl); + return; + } + var swatch = target.closest('[data-group-class]'); + if (swatch) { + event.preventDefault(); + var swatchRow = swatch.closest('[data-group-row]'); + var classInput = swatchRow && swatchRow.querySelector('[data-group-class-input]'); + if (classInput) { + classInput.value = swatch.getAttribute('data-group-class') || ''; + syncRowColor(swatchRow); + } + return; + } var remove = target.closest('[data-group-remove]'); if (remove) { event.preventDefault(); var row = remove.closest('[data-group-row]'); + var list = remove.closest('[data-group-list]'); if (row) row.remove(); + if (list) reindexRows(list); } }); - root.addEventListener('change', syncDependencies); + root.addEventListener('change', function (event) { + syncDependencies(); + var row = event.target.closest && event.target.closest('[data-group-row]'); + if (row && (event.target.hasAttribute('data-group-url') || event.target.hasAttribute('data-group-label'))) { + syncRowTitle(row); + } + }); + + root.addEventListener('input', function (event) { + var row = event.target.closest('[data-group-row]'); + if (!row) return; + if (event.target.hasAttribute('data-group-label') || event.target.hasAttribute('data-group-url')) { + syncRowTitle(row); + } + }); + + root.addEventListener('dragstart', function (event) { + var row = event.target.closest && event.target.closest('[data-group-row]'); + if (!row) return; + row.classList.add('is-dragging'); + if (event.dataTransfer) { + event.dataTransfer.effectAllowed = 'move'; + try { + event.dataTransfer.setData('text/plain', ''); + } catch (error) { /* older IE */ } + } + }); + + root.addEventListener('dragend', function (event) { + var row = event.target.closest && event.target.closest('[data-group-row]'); + if (!row) return; + row.classList.remove('is-dragging'); + var list = row.closest('[data-group-list]'); + if (list) reindexRows(list); + }); + + root.addEventListener('dragover', function (event) { + var list = event.target.closest ? event.target.closest('[data-group-list]') : null; + if (!list) return; + var dragging = list.querySelector('[data-group-row].is-dragging'); + if (!dragging) return; + event.preventDefault(); + var after = dragAfterElement(list, event.clientY); + if (after == null) { + list.appendChild(dragging); + } else if (after !== dragging) { + list.insertBefore(dragging, after); + } + }); + + root.querySelectorAll('[data-group-list]').forEach(function (list) { + list.querySelectorAll('[data-group-row]').forEach(initRow); + reindexRows(list); + }); renderGallery(); syncDependencies(); })(); diff --git a/inc/helpers/aksrt-pay.php b/inc/helpers/aksrt-pay.php index 0712fced..2d50dd6b 100644 --- a/inc/helpers/aksrt-pay.php +++ b/inc/helpers/aksrt-pay.php @@ -1478,13 +1478,18 @@ if (!function_exists('aksrt_get_paid_download_actions_html')) { $service_key = sanitize_html_class((string) ($service['key'] ?? 'other')) ?: 'other'; $button_class = trim((string) ($download['class'] ?? '')); $button_class = $button_class !== '' ? $button_class : 'b-theme ' . $service_key; + $icon = trim((string) ($download['icon'] ?? '')); + $icon_html = $icon !== '' && function_exists('aksrt_icon_markup') ? aksrt_icon_markup($icon) : ''; + if ($icon_html === '') { + $icon_html = ''; + } $copy_value = trim((string) ($download['copy_val'] ?? '')); $copy_key = trim((string) ($download['copy_key'] ?? '')); $copy_label = $extract_code !== '' ? $extract_code : ($copy_key !== '' ? $copy_key : __('更多内容', 'aksrt')); $buttons .= '