- rewrite schemas for posts, tab_posts, oneline_posts, hot_posts, term_cards, term_aggregation, media_cards, combo_info, partners, recent_comments, posts_toc and slider_media_combo to mirror the modern reference modules field by field (options, defaults, dependencies, min/max/step, subheadings) - port reference renderers: hot ranking with TOP badges, category aggregation card, graphic cover cards, partners CSS marquee, combo info cards with auto band palette, recent comment list - rename 分类与标签聚合卡片 to 分类聚合卡片 and restrict it to categories (no tags/topics branch in this project) - drop topics filter fields and the topics query branch; align orderby value domain with supported post meta - add searchable sortable terms admin field type and spinner unit suffix rendering - unify widget pagination and lazy loading on the REST client with the runtime-owned ias reveal trigger; remove the legacy post_ajax_trigger dual path - align section-navs TOC with the reference contract (wznav ids, 3-heading minimum, zib-widget box)
203 lines
9.3 KiB
JavaScript
203 lines
9.3 KiB
JavaScript
(function ($) {
|
||
'use strict';
|
||
|
||
const config = window.aksrtWidgetAdmin || {};
|
||
const core = window.AKSRTWidgetAdminCore;
|
||
|
||
function initForm(root) {
|
||
const $root = $(root);
|
||
$root.find('.aksrt-widget-color').wpColorPicker();
|
||
$root.find('.aksrt-widget-repeater__rows').sortable({
|
||
handle: '.dashicons-move',
|
||
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; });
|
||
});
|
||
evaluateDependencies($root);
|
||
updateWidgetTitle($root);
|
||
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();
|
||
if (!title) return;
|
||
$root.closest('.widget').find('.widget-title h3, .widget-title-action + h3').first().contents().filter(function () {
|
||
return this.nodeType === Node.TEXT_NODE;
|
||
}).first().replaceWith(`${title} `);
|
||
}
|
||
|
||
function updatePlacementWarning(root) {
|
||
const $widget = $(root).closest('.widget');
|
||
const $hint = $widget.find('.aksrt-widget-placement-hint').first();
|
||
if (!$hint.length) return;
|
||
const size = String($hint.data('widget-size') || 'any');
|
||
const area = String($widget.closest('.widgets-sortables').attr('id') || '');
|
||
const isSidebar = /sidebar/.test(area);
|
||
$hint.toggleClass('is-warning', (size === 'wide' && isSidebar) || (size === 'sidebar' && !isSidebar && area !== ''));
|
||
}
|
||
|
||
function loadForm(container) {
|
||
const $container = $(container);
|
||
if ($container.data('loading') || $container.data('loaded')) return;
|
||
$container.data('loading', true).find('.aksrt-widget-form-lazy__status').text(config.i18n?.loading || 'Loading…');
|
||
$.post(config.ajaxUrl, {
|
||
action: config.action,
|
||
nonce: config.nonce,
|
||
id_base: $container.data('id-base'),
|
||
widget_number: $container.data('widget-number'),
|
||
}).done((response) => {
|
||
if (!response?.success || !response.data?.html) throw new Error('Invalid response');
|
||
const $form = $(response.data.html);
|
||
$container.replaceWith($form);
|
||
initForm($form);
|
||
}).fail(() => {
|
||
$container.data('loading', false).find('.aksrt-widget-form-lazy__status').text(config.i18n?.error || 'Failed to load.');
|
||
});
|
||
}
|
||
|
||
function renumberRepeaters(_event, ui) {
|
||
const $form = $(ui?.item || this).closest('.aksrt-widget-form');
|
||
$form.find('.aksrt-widget-repeater__rows').each(function () {
|
||
$(this).children('.aksrt-widget-repeater__row').each(function (index) {
|
||
this.dataset.rowIndex = String(index);
|
||
});
|
||
});
|
||
$form.find('[name]').each(function () {
|
||
const rowIndexes = $(this).parents('.aksrt-widget-repeater__row').get().reverse().map((row) => Number(row.dataset.rowIndex || 0));
|
||
if (!rowIndexes.length) return;
|
||
this.name = core.replaceRepeaterIndexes(this.name, rowIndexes);
|
||
});
|
||
}
|
||
|
||
function fieldValue($form, id) {
|
||
const $field = $form.find(`.aksrt-widget-field[data-field-id="${CSS.escape(id)}"]`).first();
|
||
const $checked = $field.find('input:checked').first();
|
||
if ($checked.length) return String($checked.val());
|
||
return String($field.find('input,select,textarea').first().val() ?? '');
|
||
}
|
||
|
||
function evaluateDependencies($form) {
|
||
$form.find('[data-dependency]').each(function () {
|
||
const rule = core.parseDependency(this.dataset.dependency || '[]');
|
||
const $row = $(this).closest('.aksrt-widget-repeater__row');
|
||
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)
|
||
.on('click', '.aksrt-widget-form-lazy__load', function () { loadForm($(this).closest('.aksrt-widget-form-lazy')); })
|
||
.on('click', '.widget.open .aksrt-widget-form-lazy', function () { loadForm(this); })
|
||
.on('change input', '.aksrt-widget-form input, .aksrt-widget-form select, .aksrt-widget-form textarea', function () {
|
||
const $form = $(this).closest('.aksrt-widget-form');
|
||
evaluateDependencies($form);
|
||
updateWidgetTitle($form);
|
||
})
|
||
.on('click', '.aksrt-widget-repeater__add', function () {
|
||
const $repeater = $(this).closest('.aksrt-widget-repeater');
|
||
const max = Number($repeater.data('max-items') || 50);
|
||
const $rows = $repeater.children('.aksrt-widget-repeater__rows');
|
||
if ($rows.children().length >= max) return;
|
||
const index = $rows.children().length;
|
||
const token = String($repeater.data('index-token') || '__index__');
|
||
const html = $repeater.children('template').html().replaceAll(token, String(index));
|
||
const $row = $(html).appendTo($rows);
|
||
initForm($row);
|
||
renumberRepeaters.call($rows[0]);
|
||
})
|
||
.on('click', '.aksrt-widget-repeater__remove', function () {
|
||
const $rows = $(this).closest('.aksrt-widget-repeater__rows');
|
||
$(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 });
|
||
frame.on('select', () => {
|
||
const attachment = frame.state().get('selection').first().toJSON();
|
||
$wrap.find('input[type="url"]').val(attachment.url).trigger('change');
|
||
let $image = $wrap.find('img');
|
||
if (!$image.length) $image = $('<img alt="">').prependTo($wrap);
|
||
$image.attr('src', attachment.url);
|
||
});
|
||
frame.open();
|
||
})
|
||
.on('click', '.aksrt-widget-upload__remove', function () {
|
||
const $wrap = $(this).closest('.aksrt-widget-upload');
|
||
$wrap.find('input[type="url"]').val('').trigger('change');
|
||
$wrap.find('img').remove();
|
||
})
|
||
.on('click', '.aksrt-widget-template-preview', function () {
|
||
const $form = $(this).closest('form');
|
||
const $output = $('.aksrt-widget-template-preview-output').first();
|
||
$output.prop('hidden', false).html('<p>正在生成预览…</p>');
|
||
$.post(config.ajaxUrl, {
|
||
action: 'aksrt_widgets_template_preview',
|
||
nonce: config.transferNonce,
|
||
template: $form.find('[name="template"]').val(),
|
||
}).done((response) => {
|
||
if (!response?.success) throw new Error('Invalid response');
|
||
$output.html(response.data.html || '<p>模板为空。</p>');
|
||
}).fail(() => $output.html('<p class="notice notice-error">预览加载失败。</p>'));
|
||
});
|
||
|
||
$(document).on('widget-added widget-updated', function (_event, widget) {
|
||
$(widget).find('.aksrt-widget-form-lazy').each(function () { loadForm(this); });
|
||
updatePlacementWarning(widget);
|
||
});
|
||
|
||
$(function () {
|
||
$('.aksrt-widget-form').each(function () { initForm(this); });
|
||
$('.widget.open .aksrt-widget-form-lazy').each(function () { loadForm(this); });
|
||
});
|
||
})(jQuery);
|