Files
wordpress-theme-aksrt/assets/widgets/admin.js
T
kate522 b639d57c5b feat(widgets): replicate 12 reference widgets for full Zibll parity
- 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)
2026-09-05 11:28:00 +08:00

203 lines
9.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(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);