jQuery(document).ready(function($) { const $results = $('#installation-results'); $('.bpi-tab').on('click', function() { $('.bpi-tab').removeClass('active'); $(this).addClass('active'); var tab = $(this).data('tab'); $('.bpi-tab-content').removeClass('active').hide(); $('#' + tab).addClass('active').show(); }); $('.bpi-select').on('change', function() { const $form = $(this).closest('.bpi-form'); const selectedType = $(this).val(); $form.find('.source-input').removeClass('active').hide(); $form.find('textarea[name="items"]').val(''); $form.find('input[type="file"]').val(''); $form.find('.selected-files').empty(); $form.find('.' + selectedType + '-source').addClass('active').show(); }); $('.file-upload-container').each(function() { const $container = $(this); const $fileInput = $container.find('input[type="file"]'); const $selectedFiles = $container.find('.selected-files'); $container.on('click', function(e) { if (e.target === this || $(e.target).hasClass('upload-instructions')) { $fileInput.trigger('click'); } }); $container.on('dragover', function(e) { e.preventDefault(); e.stopPropagation(); $(this).addClass('dragover'); }); $container.on('dragleave drop', function(e) { e.preventDefault(); e.stopPropagation(); $(this).removeClass('dragover'); }); $container.on('drop', function(e) { const files = e.originalEvent.dataTransfer.files; $fileInput[0].files = files; handleFiles(files); }); $fileInput.on('change', function() { handleFiles(this.files); }); function handleFiles(files) { $selectedFiles.empty(); Array.from(files).forEach(file => { if (file.type === 'application/zip' || file.name.endsWith('.zip')) { const $fileElement = $(`
${escapeHtml(file.name)}
`); $selectedFiles.append($fileElement); } }); } $selectedFiles.on('click', '.remove-file', function() { $(this).closest('.selected-file').remove(); if ($selectedFiles.children().length === 0) { $fileInput.val(''); } }); }); $('#bulk-plugin-form, #bulk-theme-form').on('submit', function(e) { e.preventDefault(); const $form = $(this); const action = $form.attr('id') === 'bulk-plugin-form' ? 'bpi_install_plugins' : 'bpi_install_themes'; const type = $form.find('.bpi-select').val(); const $submitButton = $form.find('button[type="submit"]'); let items = []; let errorMessage = ''; if (type === 'upload') { const $fileInput = $form.find('input[type="file"]'); const files = $fileInput[0].files; if (!files || files.length === 0) { errorMessage = 'Please select at least one ZIP file.'; } else { items = Array.from(files).map(file => file.name); } } else { const $textarea = $form.find('.' + type + '-source textarea[name="items"]'); items = $textarea.val().split('\n') .map(item => item.trim()) .filter(item => item.length > 0); if (items.length === 0) { errorMessage = (type === 'repository' || type === 'wenpai') ? 'Please enter at least one slug.' : 'Please enter at least one URL.'; } } if (errorMessage) { alert(errorMessage); return; } $submitButton.prop('disabled', true).text('Installing...'); $results.html(`

Installation in progress... (Large ZIP files may take some time)

0/${items.length} completed (0% done, ${items.length} remaining)
`); const $list = $results.find('.installation-list'); const $progress = $results.find('.progress-count'); let completed = 0; if (type === 'upload') { const formData = new FormData($form[0]); formData.append('action', action); formData.append('nonce', bpiAjax.nonce); formData.append('install_type', type); $.ajax({ url: bpiAjax.ajaxurl, type: 'POST', data: formData, processData: false, contentType: false, success: function(response) { console.log('Upload response:', response); // 调试输出 if (response.success) { Object.keys(response.data).forEach((item, index) => { handleResponse(response, item, index); }); } else { $list.append(`
  • Upload Error✗ ${escapeHtml(response.data || 'Unknown upload error')}
  • `); } installationComplete(); }, error: function(xhr, status, error) { console.log('Upload error:', xhr, status, error); // 调试输出 $list.append(`
  • Upload Error✗ ${escapeHtml(xhr.responseText || error)}
  • `); installationComplete(); } }); } else { processNextItem(0); } function processNextItem(index) { if (index >= items.length) { installationComplete(); return; } const item = items[index]; $list.append(`
  • ${escapeHtml(item)}
  • `); $.ajax({ url: bpiAjax.ajaxurl, type: 'POST', data: { action: action, nonce: bpiAjax.nonce, items: JSON.stringify([item]), install_type: type }, success: function(response) { console.log('Item response:', response); // 调试输出 handleResponse(response, item, index); processNextItem(index + 1); }, error: function(xhr, status, error) { console.log('Item error:', xhr, status, error); // 调试输出 handleError(xhr, status, error, item, index); processNextItem(index + 1); } }); } function handleResponse(response, item, index) { const $item = $(`#item-${index}`) || $list.find('li:last'); $item.find('.spinner').removeClass('is-active'); if (response.success && response.data[item]) { const result = response.data[item]; $item.addClass(result.success ? 'success' : 'error'); let statusHtml = ''; if (result.success) { statusHtml = result.skipped ? 'ⓘ ' + escapeHtml(result.message) : '✓ ' + escapeHtml(result.message); } else { statusHtml = '✗ ' + escapeHtml(result.message); if (result.retry) { statusHtml += ' '; } } $item.find('.status').html(statusHtml); } else { $item.addClass('error') .find('.status') .html('✗ ' + escapeHtml(response.data || 'Unknown error') + ' '); } completed++; const percentage = Math.round((completed / items.length) * 100); const remaining = items.length - completed; $progress.text(`${completed}/${items.length} completed (${percentage}% done, ${remaining} remaining)`); } function handleError(xhr, status, error, item, index) { const $item = $(`#item-${index}`) || $list.find('li:last'); $item.find('.spinner').removeClass('is-active') .addClass('error') .find('.status') .html(`✗ ${escapeHtml(xhr.responseText || 'Installation failed: ' + error)} `); completed++; const percentage = Math.round((completed / items.length) * 100); const remaining = items.length - completed; $progress.text(`${completed}/${items.length} completed (${percentage}% done, ${remaining} remaining)`); } function installationComplete() { $submitButton.prop('disabled', false).text(`Install ${action === 'bpi_install_plugins' ? 'Plugins' : 'Themes'}`); const $notice = $results.find('.notice').removeClass('notice-info').addClass('notice-success'); $notice.find('p').html('Installation completed! Check the results below. Failed items can be retried using the "Retry" buttons if applicable.'); } }); $results.on('click', '.retry-btn', function() { const $button = $(this); const item = $button.data('item'); const type = $button.data('type'); const action = $('#bulk-plugin-form').is(':visible') ? 'bpi_install_plugins' : 'bpi_install_themes'; const $li = $button.closest('li'); $li.find('.spinner').addClass('is-active'); $li.find('.status').html(''); $.ajax({ url: bpiAjax.ajaxurl, type: 'POST', data: { action: action, nonce: bpiAjax.nonce, items: JSON.stringify([item]), install_type: type }, success: function(response) { console.log('Retry response:', response); // 调试输出 $li.find('.spinner').removeClass('is-active'); if (response.success && response.data[item]) { const result = response.data[item]; $li.removeClass('error success').addClass(result.success ? 'success' : 'error'); let statusHtml = ''; if (result.success) { statusHtml = result.skipped ? 'ⓘ ' + escapeHtml(result.message) : '✓ ' + escapeHtml(result.message); } else { statusHtml = '✗ ' + escapeHtml(result.message); if (result.retry) { statusHtml += ' '; } } $li.find('.status').html(statusHtml); } else { $li.addClass('error') .find('.status') .html('✗ ' + escapeHtml(response.data || 'Unknown error') + ' '); } }, error: function(xhr, status, error) { console.log('Retry error:', xhr, status, error); // 调试输出 $li.find('.spinner').removeClass('is-active') .addClass('error') .find('.status') .html(`✗ ${escapeHtml(xhr.responseText || 'Retry failed: ' + error)} `); } }); }); $('#bpi-settings-form').on('submit', function(e) { e.preventDefault(); const $form = $(this); const $submitButton = $form.find('button[type="submit"]'); const $status = $('#settings-status'); $submitButton.prop('disabled', true).text('Saving...'); $status.removeClass('notice-success notice-error').addClass('notice-info').text('Saving...').show(); $.ajax({ url: bpiAjax.ajaxurl, type: 'POST', data: { action: 'bpi_save_settings', nonce: bpiAjax.nonce, bpi_allowed_roles: $form.find('input[name="bpi_allowed_roles[]"]:checked').map(function() { return this.value; }).get(), bpi_custom_domains: $form.find('textarea[name="bpi_custom_domains"]').val() }, success: function(response) { if (response.success) { $status.removeClass('notice-info').addClass('notice-success').text(response.data || 'Settings saved successfully!').show().delay(3000).fadeOut(); } else { $status.removeClass('notice-info').addClass('notice-error').text(response.data || 'Failed to save settings.').show(); } $submitButton.prop('disabled', false).text('Save Settings'); }, error: function(xhr, status, error) { $status.removeClass('notice-info').addClass('notice-error').text('An error occurred while saving settings: ' + (xhr.responseText || error)).show(); $submitButton.prop('disabled', false).text('Save Settings'); } }); }); function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } });