The theme was renamed Zibll/子比 → AKSRT → Jio, but upstream branding survived the sweeps in CSS classes, the SVG sprite prefix, a persisted widget field ID, JS events, admin Vue class names, test infrastructure and docblocks. Renames: - zib-widget / zib-widget-wrap / zib-slider → jio-widget-box / jio-widget-box-wrap / jio-slider-box (jio-widget is taken by the modern widget shell, jio-box-* by the card primitives) - zib-partners-scroll + zps-* → jio-partners-scroll + jps-* - zib-combo-info-card + zci-* → jio-combo-info-card + jci-* - zibsvg- → jiosvg-, including the exported picker helpers and the fa-zibsvg- family-prefix guard - zibpay_type → pay_type; the dual-read fallback in PostQuery collapses to a single key - [zibajax="submit"] dropped (no PHP emitter); zib_ajax.success → jio_ajax.success - aks-* admin Vue classes → jio-* - zib_palette() → palette() User-facing vendor traces are gone: the floating "more" button default no longer ships a zibll.com link or 子比主题 marketing copy, the notice widget ships no preset promo entries, and the four 官网教程 links are removed from the post-list admin schema. Test infrastructure renamed to neutral names (docker-compose.reference.yml, Caddyfile, article-baseline.json, modern-widgets-contract.test.cjs) with their DB/volume/env/slug identifiers following. Kept intentionally, documented in tests/fixtures/README.md: the golden fixture reference fingerprint (a SHA-256 record of an external directory, read by validate-widget-golden / generate-widget-golden-fixture / audit-widget-system), the doesNotMatch guards that prove the old tokens are gone, and the reference theme's own meta key in the visual fixtures. No backward-compat or data migration: saved widget configs holding old icon keys or the old field ID silently lose them, per the project's rewrite-over-compatibility rule. Verified: 132/132 node contract tests, php tests/widget-system/run.php (146 assertions), audit-admin-contracts, audit-psr4-paths and validate-widget-golden all green; tracked bundles regenerated and in sync. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
326 lines
15 KiB
JavaScript
326 lines
15 KiB
JavaScript
/* Sidebar toolbar: per-area export / import / template import. */
|
|
(function ($) {
|
|
'use strict';
|
|
|
|
const config = window.jioWidgetAdmin || {};
|
|
const t = (key) => (config.i18n && config.i18n[key]) || key;
|
|
|
|
function post(action, data) {
|
|
return $.ajax({
|
|
url: config.ajaxUrl,
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
data: $.extend({ action, nonce: config.transferNonce }, data || {}),
|
|
});
|
|
}
|
|
|
|
function failMessage(res, xhr, fallbackKey) {
|
|
return res?.data?.message || xhr?.responseJSON?.data?.message || t(fallbackKey);
|
|
}
|
|
|
|
function clearResult($result) {
|
|
return $result.removeClass('is-ok is-warn is-err').empty();
|
|
}
|
|
|
|
function showError($result, message) {
|
|
clearResult($result).addClass('is-err').text(message);
|
|
}
|
|
|
|
function reloadButton() {
|
|
return $('<p class="jio-wie-modal__reload-wrap"></p>').append(
|
|
$('<button type="button" class="button button-primary jio-wie-modal__reload"></button>').text(t('reloadPage'))
|
|
);
|
|
}
|
|
|
|
function renderImportResult($result, res, autoReload) {
|
|
clearResult($result);
|
|
if (!res?.success || !res.data) {
|
|
showError($result, failMessage(res, null, 'importFailed'));
|
|
return;
|
|
}
|
|
const warnings = Array.isArray(res.data.warnings) ? res.data.warnings : [];
|
|
$result.addClass(warnings.length ? 'is-warn' : 'is-ok');
|
|
[res.data.message || t('importSuccess')].concat(warnings).forEach((line, index) => {
|
|
if (index) $result.append('<br>');
|
|
$result.append(document.createTextNode(String(line)));
|
|
});
|
|
$result.append(reloadButton());
|
|
if (autoReload) window.setTimeout(() => window.location.reload(), 200);
|
|
}
|
|
|
|
function withButtonLoading($button, loadingText, idleText, request) {
|
|
$button.prop('disabled', true).text(loadingText);
|
|
return request().always(() => $button.prop('disabled', false).text(idleText));
|
|
}
|
|
|
|
function importModeHtml(name) {
|
|
return `<div class="jio-wie-modal__import-mode">
|
|
<div class="jio-wie-modal__import-mode-title description"></div>
|
|
<label class="jio-wie-modal__import-mode-row"><input type="radio" name="${name}" value="replace"> <span data-mode-label="replace"></span></label>
|
|
<label class="jio-wie-modal__import-mode-row"><input type="radio" name="${name}" value="append" checked> <span data-mode-label="append"></span></label>
|
|
</div>`;
|
|
}
|
|
|
|
function createModal(opts) {
|
|
let $modal = null;
|
|
|
|
function ensure() {
|
|
if ($modal) return $modal;
|
|
$modal = $(`<div id="${opts.id}" class="jio-wie-modal${opts.extraClass ? ' ' + opts.extraClass : ''}" aria-hidden="true">
|
|
<div class="jio-wie-modal__backdrop" data-jio-wie-close></div>
|
|
<div class="jio-wie-modal__dialog" role="dialog" aria-modal="true">
|
|
<button type="button" class="jio-wie-modal__close" data-jio-wie-close aria-label="${t('close')}">×</button>
|
|
${opts.bodyHtml}
|
|
${opts.actionsHtml}
|
|
<p class="jio-wie-modal__result" aria-live="polite"></p>
|
|
</div>
|
|
</div>`).appendTo('body');
|
|
$modal.find('.jio-wie-modal__import-mode-title').text(t('importModeLabel'));
|
|
$modal.find('[data-mode-label="replace"]').text(t('importModeReplace'));
|
|
$modal.find('[data-mode-label="append"]').text(t('importModeAppend'));
|
|
$modal.find('.jio-wie-modal__cancel').text(t('close'));
|
|
$modal.on('click', '[data-jio-wie-close], .jio-wie-modal__cancel', close);
|
|
if (opts.onInit) opts.onInit($modal);
|
|
return $modal;
|
|
}
|
|
|
|
function open() {
|
|
ensure().find('input[name$="-import-mode"][value="append"]').prop('checked', true);
|
|
clearResult($modal.find('.jio-wie-modal__result'));
|
|
$modal.addClass('is-open').attr('aria-hidden', 'false');
|
|
}
|
|
|
|
function close() {
|
|
if ($modal) $modal.removeClass('is-open').attr('aria-hidden', 'true');
|
|
}
|
|
|
|
return { ensure, open, close, $result: () => ensure().find('.jio-wie-modal__result') };
|
|
}
|
|
|
|
const state = { mode: 'export', sidebarId: '', templateSidebarId: '' };
|
|
|
|
const ioModal = createModal({
|
|
id: 'jio-wie-modal',
|
|
bodyHtml: `<h3 class="jio-wie-modal__title"></h3>
|
|
<p class="jio-wie-modal__hint description"></p>
|
|
<textarea class="jio-wie-modal__textarea" spellcheck="false"></textarea>
|
|
${importModeHtml('jio-wie-import-mode')}`,
|
|
actionsHtml: `<p class="jio-wie-modal__actions">
|
|
<button type="button" class="button jio-wie-modal__copy"></button>
|
|
<button type="button" class="button button-primary jio-wie-modal__submit"></button>
|
|
<button type="button" class="button jio-wie-modal__cancel"></button>
|
|
</p>`,
|
|
onInit($modal) {
|
|
$modal.find('.jio-wie-modal__copy').on('click', function () {
|
|
const textarea = $modal.find('.jio-wie-modal__textarea')[0];
|
|
textarea.select();
|
|
textarea.setSelectionRange(0, textarea.value.length);
|
|
try {
|
|
document.execCommand('copy');
|
|
$(this).text(t('copyDone'));
|
|
} catch (_error) {
|
|
window.alert(t('copyFail'));
|
|
}
|
|
});
|
|
$modal.find('.jio-wie-modal__submit').on('click', submitImport);
|
|
},
|
|
});
|
|
|
|
const templateModal = createModal({
|
|
id: 'jio-wie-template-modal',
|
|
extraClass: 'jio-wie-template-layer',
|
|
bodyHtml: `<h3 class="jio-wie-modal__title"></h3>
|
|
<div class="jio-wie-modal__hint description"></div>
|
|
<div class="jio-wie-template-list-wrap"><p class="jio-wie-template-list-status description"></p><div class="jio-wie-template-list"></div></div>
|
|
${importModeHtml('jio-wie-template-import-mode')}`,
|
|
actionsHtml: `<p class="jio-wie-modal__actions">
|
|
<button type="button" class="button button-primary jio-wie-template-submit"></button>
|
|
<button type="button" class="button jio-wie-modal__cancel"></button>
|
|
</p>`,
|
|
onInit($modal) {
|
|
$modal.find('.jio-wie-modal__title').text(t('templateModalTitle'));
|
|
// templateHint is server-built, escaped translation markup.
|
|
$modal.find('.jio-wie-modal__hint').html(t('templateHint'));
|
|
$modal.find('.jio-wie-template-submit').text(t('templateSubmit')).on('click', submitTemplate);
|
|
},
|
|
});
|
|
|
|
function openIoModal(mode, sidebarId) {
|
|
state.mode = mode;
|
|
state.sidebarId = sidebarId;
|
|
const $modal = ioModal.ensure();
|
|
const isExport = mode === 'export';
|
|
$modal.find('.jio-wie-modal__title').text(t(isExport ? 'exportTitle' : 'importTitle'));
|
|
$modal.find('.jio-wie-modal__hint').text(t(isExport ? 'exportHint' : 'importHint'));
|
|
$modal.find('.jio-wie-modal__textarea').prop('readonly', isExport).val('');
|
|
$modal.find('.jio-wie-modal__copy').toggle(isExport).text(t('copy'));
|
|
$modal.find('.jio-wie-modal__submit').toggle(!isExport).text(t('submitImport')).prop('disabled', false);
|
|
$modal.find('.jio-wie-modal__import-mode').toggle(!isExport);
|
|
ioModal.open();
|
|
if (isExport) fetchExport();
|
|
}
|
|
|
|
function fetchExport() {
|
|
const $textarea = ioModal.ensure().find('.jio-wie-modal__textarea').val(t('loadingExport'));
|
|
post('jio_widgets_export_sidebar', { sidebar_id: state.sidebarId })
|
|
.done((res) => {
|
|
if (res?.success && res.data?.json_text) {
|
|
$textarea.val(res.data.json_text);
|
|
return;
|
|
}
|
|
$textarea.val('');
|
|
showError(ioModal.$result(), failMessage(res, null, 'exportFailed'));
|
|
})
|
|
.fail((xhr) => {
|
|
$textarea.val('');
|
|
showError(ioModal.$result(), failMessage(null, xhr, 'networkError'));
|
|
});
|
|
}
|
|
|
|
function submitImport() {
|
|
const $modal = ioModal.ensure();
|
|
const mode = $modal.find('input[name="jio-wie-import-mode"]:checked').val() || 'append';
|
|
if (!window.confirm(t(mode === 'append' ? 'confirmImportAppend' : 'confirmImportReplace'))) return;
|
|
const raw = String($modal.find('.jio-wie-modal__textarea').val() || '').trim();
|
|
if (!raw) {
|
|
showError(ioModal.$result(), t('pasteJsonFirst'));
|
|
return;
|
|
}
|
|
withButtonLoading($modal.find('.jio-wie-modal__submit'), t('loadingImport'), t('submitImport'), () =>
|
|
post('jio_widgets_import_sidebar', { sidebar_id: state.sidebarId, json: raw, import_mode: mode })
|
|
.done((res) => {
|
|
if (res?.success) $modal.find('.jio-wie-modal__textarea').val('');
|
|
renderImportResult(ioModal.$result(), res, false);
|
|
})
|
|
.fail((xhr) => showError(ioModal.$result(), failMessage(null, xhr, 'networkError')))
|
|
);
|
|
}
|
|
|
|
function templateItem(template) {
|
|
const $meta = $('<span class="jio-wie-template-item-meta"></span>')
|
|
.append($('<span class="jio-wie-template-item-title"></span>').text(template.title || template.id));
|
|
if (template.desc) $meta.append($('<span class="jio-wie-template-item-desc"></span>').text(template.desc));
|
|
if (template.remind) {
|
|
$meta.append($('<span class="jio-wie-template-item-remind"></span>')
|
|
.append('<span class="dashicons dashicons-info-outline" aria-hidden="true"></span>')
|
|
.append(document.createTextNode(template.remind)));
|
|
}
|
|
if (Array.isArray(template.modules) && template.modules.length) {
|
|
$meta.append($('<span class="jio-wie-template-item-modules"></span>').text(template.modules.map((module) => module.title).join(' / ')));
|
|
}
|
|
const $label = $('<label class="jio-wie-template-item-label"></label>')
|
|
.append($('<input type="radio" name="jio-wie-template-id">').val(template.id))
|
|
.append($meta);
|
|
return $('<div class="jio-wie-template-item"></div>').append($('<div class="jio-wie-template-item-row"></div>').append($label));
|
|
}
|
|
|
|
function openTemplateModal(sidebarId) {
|
|
state.templateSidebarId = sidebarId;
|
|
const $modal = templateModal.ensure();
|
|
const $list = $modal.find('.jio-wie-template-list').empty();
|
|
const $status = $modal.find('.jio-wie-template-list-status').text(t('templateLoadingList'));
|
|
templateModal.open();
|
|
post('jio_widgets_list_templates')
|
|
.done((res) => {
|
|
const templates = res?.success ? res.data?.templates : null;
|
|
$status.text(Array.isArray(templates) && templates.length ? '' : t('templateEmpty'));
|
|
(templates || []).forEach((template) => $list.append(templateItem(template)));
|
|
})
|
|
.fail((xhr) => $status.text(failMessage(null, xhr, 'networkError')));
|
|
}
|
|
|
|
function submitTemplate() {
|
|
const $modal = templateModal.ensure();
|
|
const $picked = $modal.find('input[name="jio-wie-template-id"]:checked');
|
|
if (!$picked.length) {
|
|
window.alert(t('templatePick'));
|
|
return;
|
|
}
|
|
const templateId = String($picked.val());
|
|
const mode = $modal.find('input[name="jio-wie-template-import-mode"]:checked').val() || 'append';
|
|
const title = $picked.closest('.jio-wie-template-item').find('.jio-wie-template-item-title').text().trim() || templateId;
|
|
const message = t('confirmTemplate').replace('{title}', title).replace('{mode}', t(mode === 'append' ? 'importModeAppend' : 'importModeReplace'));
|
|
if (!window.confirm(message)) return;
|
|
withButtonLoading($modal.find('.jio-wie-template-submit'), t('loadingImport'), t('templateSubmit'), () =>
|
|
post('jio_widgets_import_template', { sidebar_id: state.templateSidebarId, template_id: templateId, import_mode: mode })
|
|
.done((res) => renderImportResult(templateModal.$result(), res, true))
|
|
.fail((xhr) => showError(templateModal.$result(), failMessage(null, xhr, 'networkError')))
|
|
);
|
|
}
|
|
|
|
function injectToolbar($anchor, sidebarId) {
|
|
if (!$anchor?.length || !sidebarId || $anchor.next('.jio-wie-toolbar').length) return;
|
|
const $toolbar = $('<div class="jio-wie-toolbar"></div>');
|
|
[['export', 'exportModules'], ['import', 'importModules'], ['template', 'importTemplate']].forEach(([action, label]) => {
|
|
$('<button type="button" class="button button-small"></button>')
|
|
.text(t(label))
|
|
.attr('data-jio-wie-action', action)
|
|
.attr('data-sidebar-id', sidebarId)
|
|
.appendTo($toolbar);
|
|
});
|
|
$anchor.after($toolbar);
|
|
}
|
|
|
|
function scanWidgetsScreen() {
|
|
$('.widgets-holder-wrap:not(#available-widgets):not(.inactive-sidebar):not(.is-jio-wie)').each(function () {
|
|
const $wrap = $(this);
|
|
const $sortables = $wrap.find('.widgets-sortables').first();
|
|
const sidebarId = String($sortables.attr('id') || '');
|
|
if (!sidebarId) return;
|
|
const $description = $sortables.find('.sidebar-description .description').first();
|
|
$wrap.addClass('is-jio-wie');
|
|
injectToolbar($description.length ? $description : $sortables.find('.description').first(), sidebarId);
|
|
});
|
|
}
|
|
|
|
function scanCustomizer() {
|
|
const prefix = 'sub-accordion-section-sidebar-widgets-';
|
|
$('.control-section-sidebar:not(.is-jio-wie)').each(function () {
|
|
const $section = $(this);
|
|
const sectionId = String($section.attr('id') || '');
|
|
if (!sectionId.startsWith(prefix) || sectionId.length === prefix.length) return;
|
|
$section.addClass('is-jio-wie');
|
|
const $description = $section.find('.section-meta .description').first();
|
|
injectToolbar($description.length ? $description : $section.find('.accordion-section-title, .customize-section-title').first(), sectionId.slice(prefix.length));
|
|
});
|
|
$('.customize-control-widget_form:not(.is-jio-wie)').each(function () {
|
|
const $nav = $(this).find('.widget-reorder-nav').first();
|
|
if (!$nav.length) return;
|
|
$('<span class="jio-wie-btn-delete dashicons-trash" tabindex="0" role="button"></span>').text(t('removeModule')).appendTo($nav);
|
|
$(this).addClass('is-jio-wie');
|
|
});
|
|
}
|
|
|
|
$(document)
|
|
.on('click', '[data-jio-wie-action]', function () {
|
|
const action = String($(this).data('jio-wie-action'));
|
|
const sidebarId = String($(this).data('sidebar-id') || '');
|
|
if (!sidebarId) return;
|
|
if (action === 'template') openTemplateModal(sidebarId);
|
|
else openIoModal(action, sidebarId);
|
|
})
|
|
.on('click keydown', '.jio-wie-btn-delete', function (event) {
|
|
if (event.type === 'keydown' && event.key !== 'Enter' && event.key !== ' ') return;
|
|
event.preventDefault();
|
|
$(this).closest('.customize-control-widget_form').find('.widget-control-remove').first().trigger('click');
|
|
})
|
|
.on('click', '.jio-wie-modal__reload', () => window.location.reload())
|
|
.on('keydown', (event) => {
|
|
if (event.key !== 'Escape') return;
|
|
ioModal.close();
|
|
templateModal.close();
|
|
})
|
|
.on('widget-added', () => {
|
|
if (document.body.classList.contains('wp-customizer')) scanCustomizer();
|
|
});
|
|
|
|
$(function () {
|
|
if (!config.ajaxUrl) return;
|
|
if (document.body.classList.contains('widgets-php')) scanWidgetsScreen();
|
|
if (document.body.classList.contains('wp-customizer')) {
|
|
window.wp?.customize?.bind?.('ready', () => window.setTimeout(scanCustomizer, 300));
|
|
$(document).on('click', '.control-panel-widgets .control-section-sidebar', () => window.setTimeout(scanCustomizer, 100));
|
|
}
|
|
});
|
|
})(jQuery);
|