/**
* PRAutoBlogger admin JavaScript.
*
* Handles: tab switching, "Generate Now" with pipeline progress stages,
* "Test Connections" with spinner, review queue actions, save feedback.
* Uses vanilla jQuery (WordPress admin already loads it).
*
* @see admin/class-admin-page.php — Localizes prautobloggerAdmin object.
*/
(function ($) {
'use strict';
var config = window.prautobloggerAdmin || {};
/*
* ── Settings page: tab switching ───────────────────────────────────
* Switches tabs client-side without page reload. Updates URL hash
* so the browser back button works and refreshes keep the active tab.
*/
$(document).on('click', '.ab-tab', function (e) {
e.preventDefault();
var tab = $(this).data('tab');
if (!tab) return;
// Switch tab nav
$('.ab-tab').removeClass('ab-tab-active').attr('aria-selected', 'false');
$(this).addClass('ab-tab-active').attr('aria-selected', 'true');
// Switch panel
$('.ab-panel').removeClass('ab-panel-active');
$('.ab-panel[data-tab="' + tab + '"]').addClass('ab-panel-active');
// Update URL without reload
if (window.history.replaceState) {
var url = new URL(window.location);
url.searchParams.set('tab', tab);
window.history.replaceState({}, '', url);
}
});
/*
* ── Save Settings feedback ─────────────────────────────────────────
* Shows "Saving…" on submit, auto-hides success notice after a delay.
*/
$(document).on('submit', '#ab-settings-form', function () {
var $btn = $(this).find('input[type="submit"]');
$btn.val('Saving…').prop('disabled', true);
});
// Auto-dismiss save notice after 6 seconds.
$(function () {
var $notice = $('#ab-save-notice');
if ($notice.length) {
setTimeout(function () {
$notice.fadeOut(400, function () { $(this).remove(); });
}, 6000);
}
});
/*
* ── Helpers ─────────────────────────────────────────────────────────
*/
/**
* Show a status message in the admin page.
*
* @param {string} message Text to display.
* @param {string} type 'success' or 'error'.
*/
function showStatus(message, type) {
var $el = $('#prautoblogger-status-message');
$el.removeClass('hidden error')
.text(message);
if (type === 'error') {
$el.addClass('error');
}
hideProgress();
}
/**
* Show a progress stage message with spinner.
*
* @param {string} message The stage text to display.
*/
function showProgress(message) {
var $el = $('#prautoblogger-progress-stage');
$el.html(
'' +
'' + message + ''
).show();
// Hide the status message while progress is shown.
$('#prautoblogger-status-message').addClass('hidden');
}
/** Hide the progress stage message. */
function hideProgress() {
$('#prautoblogger-progress-stage').hide();
}
/**
* Set a button into loading state with spinner.
*
* @param {jQuery} $btn The button element.
* @param {string} text The loading text to show.
*/
function setButtonLoading($btn, text) {
$btn.addClass('ab-btn-loading')
.prop('disabled', true);
$btn.find('.ab-btn-label').text(text);
}
/**
* Reset a button back to its default state.
*
* @param {jQuery} $btn The button element.
* @param {string} text The default text to restore.
*/
function resetButton($btn, text) {
$btn.removeClass('ab-btn-loading')
.prop('disabled', false);
$btn.find('.ab-btn-label').text(text);
}
/*
* ── Generate Now ───────────────────────────────────────────────────
* Shows pipeline stages while the request is in-flight so the user
* knows it hasn't hung.
*/
var generateStages = [
{ text: 'Collecting sources from Reddit…', delay: 0 },
{ text: 'Analyzing topics and scoring…', delay: 8000 },
{ text: 'Selecting best topic…', delay: 16000 },
{ text: 'Generating article draft via AI…', delay: 24000 },
{ text: 'Running editorial pass…', delay: 50000 },
{ text: 'Saving and publishing…', delay: 75000 },
{ text: 'Almost done — finalizing…', delay: 100000 }
];
var stageTimers = [];
$(document).on('click', '#prautoblogger-generate-now', function () {
var $btn = $(this);
if ($btn.prop('disabled')) return;
setButtonLoading($btn, config.generatingText || 'Generating…');
// Start pipeline stage messages.
stageTimers = [];
$.each(generateStages, function (i, stage) {
var t = setTimeout(function () { showProgress(stage.text); }, stage.delay);
stageTimers.push(t);
});
$.ajax({
url: config.ajaxUrl,
method: 'POST',
data: {
action: 'prautoblogger_generate_now',
nonce: config.generateNonce,
force: '1' // Always clear stale locks on manual runs.
},
timeout: 300000, // 5 minutes — generation can be slow.
success: function (response) {
if (response.success) {
var d = response.data;
showStatus(
'Generation complete: ' + d.generated + ' generated, ' +
d.published + ' published, ' + d.rejected + ' rejected. ' +
'Cost: $' + parseFloat(d.cost).toFixed(4),
'success'
);
} else {
showStatus(
'Generation failed: ' + (response.data && response.data.message || 'Unknown error'),
'error'
);
}
},
error: function (xhr, status) {
showStatus(
'Request failed: ' + status + '. Check server error logs.',
'error'
);
},
complete: function () {
// Clear all stage timers.
$.each(stageTimers, function (_, t) { clearTimeout(t); });
stageTimers = [];
hideProgress();
resetButton($btn, config.generateText || 'Generate Now');
}
});
});
/*
* ── Test Connections ───────────────────────────────────────────────
*/
$(document).on('click', '#prautoblogger-test-connection', function () {
var $btn = $(this);
if ($btn.prop('disabled')) return;
setButtonLoading($btn, config.testingText || 'Testing…');
showProgress('Testing API connections…');
$.ajax({
url: config.ajaxUrl,
method: 'POST',
data: {
action: 'prautoblogger_test_connection',
nonce: config.testNonce,
service: 'all'
},
timeout: 30000,
success: function (response) {
if (response.success) {
var messages = [];
var hasError = false;
$.each(response.data, function (service, result) {
messages.push(service + ': ' + result.message);
if (result.status === 'error') hasError = true;
});
showStatus(messages.join(' | '), hasError ? 'error' : 'success');
} else {
showStatus('Connection test failed.', 'error');
}
},
error: function () {
showStatus('Connection test request failed.', 'error');
},
complete: function () {
resetButton($btn, config.testText || 'Test Connections');
}
});
});
/*
* Review Queue — inline approve/reject via AJAX.
* Removes the row on success so the user sees immediate feedback.
*/
/**
* Show a status message on the review queue page.
*
* @param {string} message Text to display.
* @param {string} type 'success' or 'error'.
*/
function showQueueStatus(message, type) {
var $el = $('#prautoblogger-queue-status');
$el.removeClass('hidden error').text(message);
if (type === 'error') {
$el.addClass('error');
}
}
/**
* Handle inline "Approve" button click on review queue.
*/
$(document).on('click', '.prautoblogger-approve-btn', function () {
var $btn = $(this);
var postId = $btn.data('post-id');
if ($btn.prop('disabled')) return;
$btn.prop('disabled', true).text('Publishing…');
$.ajax({
url: config.ajaxUrl,
method: 'POST',
data: {
action: 'prautoblogger_approve_post',
nonce: config.reviewNonce,
post_id: postId
},
timeout: 15000,
success: function (response) {
if (response.success) {
$btn.closest('tr').fadeOut(300, function () { $(this).remove(); });
showQueueStatus(response.data.message, 'success');
} else {
showQueueStatus(response.data && response.data.message || 'Approve failed.', 'error');
$btn.prop('disabled', false).text('Approve');
}
},
error: function () {
showQueueStatus('Request failed. Check server logs.', 'error');
$btn.prop('disabled', false).text('Approve');
}
});
});
/**
* Handle inline "Reject" button click on review queue.
*/
$(document).on('click', '.prautoblogger-reject-btn', function () {
var $btn = $(this);
var postId = $btn.data('post-id');
if ($btn.prop('disabled')) return;
if (!confirm('Reject this post? It will be moved to trash.')) return;
$btn.prop('disabled', true).text('Rejecting…');
$.ajax({
url: config.ajaxUrl,
method: 'POST',
data: {
action: 'prautoblogger_reject_post',
nonce: config.reviewNonce,
post_id: postId
},
timeout: 15000,
success: function (response) {
if (response.success) {
$btn.closest('tr').fadeOut(300, function () { $(this).remove(); });
showQueueStatus(response.data.message, 'success');
} else {
showQueueStatus(response.data && response.data.message || 'Reject failed.', 'error');
$btn.prop('disabled', false).text('Reject');
}
},
error: function () {
showQueueStatus('Request failed. Check server logs.', 'error');
$btn.prop('disabled', false).text('Reject');
}
});
});
/**
* Handle "Select All" checkbox on review queue.
*/
$(document).on('change', '#prautoblogger-select-all', function () {
$('input[name="prautoblogger_post_ids[]"]').prop('checked', this.checked);
});
/*
* ── Log viewer ─────────────────────────────────────────────────────
*/
/** Toggle log entry meta detail. */
$(document).on('click', '.ab-log-meta-toggle', function () {
$(this).next('.ab-log-meta').slideToggle(150);
$(this).find('.dashicons').toggleClass('dashicons-arrow-down-alt2 dashicons-arrow-up-alt2');
});
/** Clear old logs button. */
$(document).on('click', '#prautoblogger-clear-logs', function () {
var $btn = $(this);
if ($btn.prop('disabled')) return;
if (!confirm('Delete log entries older than 30 days?')) return;
$btn.prop('disabled', true);
var nonce = $btn.data('nonce');
$.ajax({
url: config.ajaxUrl,
method: 'POST',
data: { action: 'prautoblogger_clear_logs', nonce: nonce, days: 30 },
timeout: 15000,
success: function (response) {
var $status = $('#prautoblogger-log-status');
if (response.success) {
$status.removeClass('hidden error').text(response.data.message);
setTimeout(function () { window.location.reload(); }, 1200);
} else {
$status.removeClass('hidden').addClass('error').text(response.data && response.data.message || 'Failed.');
}
},
complete: function () { $btn.prop('disabled', false); }
});
});
})(jQuery);