jQuery(document).ready(function($) {
// ========================================
// UNIQUE URL GENERATOR FOR DIRECT CONTENT
// ========================================
// Show/hide generate button based on checkbox
$('#mxchat-unique-url-toggle').on('change', function() {
const $button = $('#mxchat-generate-unique-url');
const $urlInput = $('#article_url');
if ($(this).is(':checked')) {
$button.show();
// If URL field is empty and checkbox is enabled, generate immediately
if (!$urlInput.val()) {
generateUniqueUrl();
}
} else {
$button.hide();
}
});
// Generate unique URL when button is clicked
$('#mxchat-generate-unique-url').on('click', function() {
generateUniqueUrl();
});
// Function to generate unique URL
function generateUniqueUrl() {
const $urlInput = $('#article_url');
let baseUrl = $urlInput.val().trim();
// If no URL provided, use a default
if (!baseUrl) {
baseUrl = window.location.origin;
}
// Split URL into base and hash fragment
let hashFragment = '';
if (baseUrl.includes('#')) {
const parts = baseUrl.split('#');
baseUrl = parts[0];
hashFragment = '#' + parts[1];
}
// Remove any existing ref parameter (matches ref=anything up to & or end of string)
baseUrl = baseUrl.replace(/[?&]ref=[^&]+(&|$)/, function(match, ending) {
// If it ends with &, keep it; otherwise remove the whole thing
return ending === '&' ? '&' : '';
});
// Clean up any trailing ? or & from URL
baseUrl = baseUrl.replace(/[?&]$/, '');
// Generate unique reference (timestamp only for cleaner URLs)
const timestamp = Date.now();
const uniqueRef = timestamp;
// Add the unique reference as a query parameter (before hash)
const separator = baseUrl.includes('?') ? '&' : '?';
const uniqueUrl = baseUrl + separator + 'ref=' + uniqueRef + hashFragment;
// Update the input field
$urlInput.val(uniqueUrl);
// Visual feedback
$urlInput.css('background-color', '#e7f7e7');
setTimeout(function() {
$urlInput.css('background-color', '');
}, 1000);
}
// ========================================
// QUEUE PROCESSING SYSTEM
// ========================================
let isProcessingQueue = false;
let currentQueueId = null;
let currentQueueType = null;
// Process 5 items at a time
const BATCH_SIZE = 5;
// Check if we should start queue processing on page load
checkForActiveQueues();
// Form submission handler - triggers queue processing
$('#mxchat-url-form').on('submit', function(e) {
//console.log('MxChat: Form submitted, queue will be created');
// Don't prevent default - let form submit normally
// But schedule a check after redirect
localStorage.setItem('mxchat_check_queue_after_submit', Date.now().toString());
});
// Check if we just submitted a form and need to start processing
const justSubmitted = localStorage.getItem('mxchat_check_queue_after_submit');
if (justSubmitted) {
const submitTime = parseInt(justSubmitted);
const now = Date.now();
// If submitted within last 10 seconds, wait for queue to be created
if (now - submitTime < 10000) {
//console.log('MxChat: Form was just submitted, waiting for queue creation...');
localStorage.removeItem('mxchat_check_queue_after_submit');
// Show processing message
if ($('.mxchat-processing-message').length === 0) {
const message = $('
β³ Queue created! Processing will start in a moment...
');
$('.mxchat-import-section').after(message);
}
// Check for queue multiple times with increasing delays
setTimeout(function() { checkForActiveQueues(); }, 1000);
setTimeout(function() { checkForActiveQueues(); }, 2000);
setTimeout(function() { checkForActiveQueues(); }, 3000);
setTimeout(function() { checkForActiveQueues(); }, 5000);
}
}
function checkForActiveQueues() {
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'mxchat_get_status_updates',
nonce: mxchatAdmin.status_nonce
},
success: function(response) {
//console.log('MxChat: Checking for active queues...', response);
if (response.sitemap_queue_id && response.sitemap_status) {
if (response.sitemap_status.status === 'processing') {
//console.log('MxChat: Found active sitemap queue:', response.sitemap_queue_id);
startQueueProcessing(response.sitemap_queue_id, 'sitemap');
} else if (response.sitemap_status.status === 'complete') {
//console.log('MxChat: Sitemap queue already complete');
// ADD THIS LINE:
$('.mxchat-processing-message').remove();
// Show completed status card (no auto-refresh)
updateSitemapStatus(response.sitemap_status);
}
}
if (response.pdf_queue_id && response.pdf_status) {
if (response.pdf_status.status === 'processing') {
//console.log('MxChat: Found active PDF queue:', response.pdf_queue_id);
startQueueProcessing(response.pdf_queue_id, 'pdf');
} else if (response.pdf_status.status === 'complete') {
//console.log('MxChat: PDF queue already complete');
// ADD THIS LINE:
$('.mxchat-processing-message').remove();
// Show completed status card (no auto-refresh)
updatePdfStatus(response.pdf_status);
}
}
// ADD THIS: If no queues found at all, remove the message
if (!response.sitemap_queue_id && !response.pdf_queue_id) {
$('.mxchat-processing-message').remove();
}
},
error: function(xhr, status, error) {
console.error('MxChat: Error checking for active queues:', error);
// ADD THIS: Remove message on error too
$('.mxchat-processing-message').remove();
}
});
}
/**
* Start processing a queue
*/
function startQueueProcessing(queueId, queueType) {
if (isProcessingQueue) {
//console.log('MxChat: Already processing a queue, skipping');
return;
}
isProcessingQueue = true;
currentQueueId = queueId;
currentQueueType = queueType;
//console.log('MxChat: Starting queue processing:', queueId, queueType);
// Remove any "waiting" messages
$('.mxchat-processing-message').remove();
// Create or update status card
createOrUpdateStatusCard(queueType);
// Start real-time entries polling if function exists
if (typeof startEntriesPolling === 'function') {
startEntriesPolling();
}
// Start the batch processing loop
processNextBatch();
}
/**
* Process the next batch of items (5 at a time)
*/
function processNextBatch() {
if (!isProcessingQueue) {
//console.log('MxChat: Processing stopped');
return;
}
// Fetch the next batch of items
const fetchPromises = [];
for (let i = 0; i < BATCH_SIZE; i++) {
const promise = $.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'mxchat_get_next_queue_item',
nonce: mxchatAdmin.queue_nonce,
queue_id: currentQueueId
}
});
fetchPromises.push(promise);
}
// Wait for all fetch requests to complete
Promise.all(fetchPromises).then(function(responses) {
// Filter out completed/error responses and extract items
const items = [];
let queueComplete = false;
for (let response of responses) {
if (response.success && response.data && !response.data.complete) {
items.push(response.data.item);
} else if (response.data && response.data.complete) {
queueComplete = true;
}
}
// If no items to process, queue is done
if (items.length === 0) {
if (queueComplete) {
handleQueueComplete();
} else {
verifyQueueCompletion();
}
return;
}
// Process all items in this batch simultaneously
const processPromises = items.map(item => processQueueItem(item));
// Wait for all items to finish processing
Promise.all(processPromises).then(function() {
// Update progress after batch completes
updateQueueProgress();
// If we got fewer items than batch size, queue might be done
if (items.length < BATCH_SIZE || queueComplete) {
verifyQueueCompletion();
} else {
// Process next batch immediately
processNextBatch();
}
}).catch(function(error) {
console.error('MxChat: Error processing batch:', error);
// Continue anyway
updateQueueProgress();
setTimeout(function() {
processNextBatch();
}, 1000);
});
}).catch(function(error) {
console.error('MxChat: Error fetching batch:', error);
// Verify queue status before retrying
setTimeout(function() {
verifyQueueCompletion();
}, 2000);
});
}
/**
* Verify if queue is actually complete
* Prevents infinite loops when last item fails
*/
function verifyQueueCompletion() {
//console.log('MxChat: Verifying queue completion status...');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'mxchat_get_queue_status',
nonce: mxchatAdmin.queue_nonce,
queue_id: currentQueueId
},
success: function(response) {
if (response.success) {
const status = response.data;
// If no pending or processing items, queue is done
if (status.pending === 0 && status.processing === 0) {
//console.log('MxChat: Queue verified as complete');
handleQueueComplete();
} else {
// Still has items, try to continue
//console.log('MxChat: Queue still has pending items, continuing...');
processNextBatch();
}
} else {
// Can't verify, assume complete to prevent infinite loop
//console.log('MxChat: Could not verify queue status, assuming complete');
handleQueueComplete();
}
},
error: function() {
// Can't verify, assume complete to prevent infinite loop
//console.log('MxChat: Network error verifying queue, assuming complete');
handleQueueComplete();
}
});
}
/**
* Process a single queue item
* Returns a Promise that resolves when processing is complete
*/
function processQueueItem(item) {
return $.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'mxchat_process_queue_item',
nonce: mxchatAdmin.queue_nonce,
item_id: item.id,
item_type: item.type,
item_data: item.data,
bot_id: item.bot_id
}
}).then(function(response) {
if (response.success) {
// Item processed successfully
//console.log('MxChat: Item processed successfully:', item.id);
return true;
} else {
// Item failed but we KEEP GOING
console.warn('MxChat: Item processing failed (will continue):', item.type, item.id);
console.warn('MxChat: Error details:', response.data);
return false;
}
}).catch(function(xhr, status, error) {
// Network error - log it but KEEP GOING
console.error('MxChat: AJAX/Network error processing item:', item.id, error);
return false;
});
}
/**
* Update queue progress (fetches latest stats)
*/
function updateQueueProgress() {
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'mxchat_get_queue_status',
nonce: mxchatAdmin.queue_nonce,
queue_id: currentQueueId
},
success: function(response) {
if (response.success) {
const status = response.data;
// Update the appropriate status card
if (currentQueueType === 'pdf') {
updatePdfStatusFromQueue(status);
} else {
updateSitemapStatusFromQueue(status);
}
}
}
});
}
/**
* Handle queue completion
* NO AUTO-REFRESH - Show completed card with errors until dismissed
*/
function handleQueueComplete() {
isProcessingQueue = false;
// Stop real-time entries polling if function exists
if (typeof stopEntriesPolling === 'function') {
stopEntriesPolling();
}
// Refresh the knowledge base table with properly grouped entries
refreshKnowledgeBaseTable();
// Get final status with error details
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'mxchat_get_queue_status',
nonce: mxchatAdmin.queue_nonce,
queue_id: currentQueueId
},
success: function(response) {
if (response.success) {
const status = response.data;
// Show completed status card (NO REFRESH)
if (currentQueueType === 'pdf') {
showCompletedPdfCard(status);
} else {
showCompletedSitemapCard(status);
}
// Mark the queue as complete on server
markQueueAsComplete(currentQueueId);
// Show notification based on results
if (status.failed > 0) {
showNotification('warning',
`Processing completed: ${status.completed} succeeded, ${status.failed} failed. ` +
`Review errors below and dismiss when ready.`
);
} else {
showNotification('success',
`Processing completed successfully! All ${status.completed} items processed. ` +
`Dismiss the status card when ready.`
);
}
// NO AUTO-REFRESH - User must manually dismiss
} else {
// Couldn't get final status, just show generic completion
showNotification('success', 'Processing completed! Refresh page to see final results.');
}
},
error: function() {
// Error getting final status
showNotification('success', 'Processing completed! Refresh page to see final results.');
}
});
}
/**
* Show completed PDF card with full error details (NO RETRY BUTTON)
*/
function showCompletedPdfCard(status) {
let $card = $('.mxchat-status-card:contains("PDF Processing")');
if ($card.length === 0) {
return;
}
// Remove processing UI elements
$card.find('.mxchat-stop-form').remove();
$card.find('.mxchat-status-warning').remove();
// Update header with completion badge
$card.find('.mxchat-status-badge').remove();
if (status.failed > 0) {
$card.find('.mxchat-status-header h4').after(
'β οΈ Completed with ' +
status.failed + ' failures - Refresh to view entries'
);
} else {
$card.find('.mxchat-status-header h4').after(
'β Complete - Refresh to view entries'
);
}
// Add dismiss button
addDismissButton($card);
// Update progress bar to 100%
$card.find('.mxchat-progress-fill').css('width', '100%');
// Update details with final stats
let detailsHtml = '
';
}
$card.find('.mxchat-status-details').html(detailsHtml);
}
/**
* Mark queue as complete on server side
* This prevents it from auto-starting on page refresh
*/
function markQueueAsComplete(queueId) {
// This is a fire-and-forget call to update queue status
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'mxchat_mark_queue_complete',
nonce: mxchatAdmin.queue_nonce,
queue_id: queueId
},
success: function(response) {
//console.log('MxChat: Queue marked as complete on server');
},
error: function() {
//console.log('MxChat: Could not mark queue as complete, but continuing');
}
});
}
/**
* Stop processing button handler
*/
$(document).on('submit', '.mxchat-stop-form', function() {
//console.log('MxChat: Stop processing requested');
isProcessingQueue = false;
currentQueueId = null;
currentQueueType = null;
});
/**
* Create or update status card
*/
function createOrUpdateStatusCard(queueType) {
const cardTitle = queueType === 'pdf' ? 'PDF Processing Status' : 'Sitemap Processing Status';
let $card = $('.mxchat-status-card:contains("' + cardTitle + '")');
if ($card.length === 0) {
// Create new card
let html = '
';
html += '
';
html += '
' + cardTitle + '
';
html += '
';
html += 'β οΈ Keep this tab open - Processing runs in your browser';
html += '
';
html += '';
html += '
';
html += '
';
html += '';
html += '
';
html += '
';
html += '
Initializing...
';
html += '
';
html += '
';
// Insert card
let $importSection = $('.mxchat-import-section');
if ($importSection.length > 0) {
$importSection.after($(html));
}
}
}
/**
* Update PDF status from queue data (DURING PROCESSING)
*/
function updatePdfStatusFromQueue(status) {
let $card = $('.mxchat-status-card:contains("PDF Processing")');
if ($card.length === 0) {
return;
}
// Update progress bar
$card.find('.mxchat-progress-fill').css('width', status.percentage + '%');
// Update details
let detailsHtml = '
';
$tbody.prepend(noticeHtml);
// Handle refresh link click
$tbody.find('.mxchat-refresh-table-link').on('click', function(e) {
e.preventDefault();
location.reload();
});
} else if ($refreshNotice.length > 0) {
// Update the count in existing notice
$refreshNotice.find('strong').text(newCount + ' entries in Pinecone.');
}
}
} else {
// WordPress DB - Track new entries and show refresh notice
// Don't add rows individually during processing as they need to be grouped by source_url
if (response.data.entries && response.data.entries.length > 0) {
// Update last ID to track progress
if (response.data.max_id > lastEntryId) {
lastEntryId = response.data.max_id;
}
// Show/update refresh notice (similar to Pinecone handling)
var $tbody = $('#mxchat-entries-tbody');
var $refreshNotice = $tbody.find('.mxchat-wordpress-refresh-notice');
var newCount = response.data.total_count || 0;
if ($refreshNotice.length === 0 && newCount > 0) {
var noticeHtml = '
');
// Add highlight class and prepend to tbody
$row.addClass('mxchat-new-entry');
$tbody.prepend($row);
$row.slideDown(300);
// Remove highlight after animation
setTimeout(function() {
$row.removeClass('mxchat-new-entry');
}, 2000);
});
}
// Initialize entry ID tracking
initializeLastEntryId();
// Check on page load if there's already active processing (e.g., page was refreshed during processing)
if ($('.mxchat-status-card').length > 0) {
// Check if processing is active via AJAX
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'mxchat_get_status_updates',
nonce: mxchatAdmin.status_nonce
},
success: function(response) {
if (response.is_processing) {
startEntriesPolling();
}
}
});
}
// Expose functions for external access
window.mxchatEntriesPolling = {
start: startEntriesPolling,
stop: stopEntriesPolling,
fetch: fetchNewEntries
};
// ========================================
// SITEMAP DETECTION FUNCTIONALITY
// ========================================
let sitemapDetectionInitialized = false;
/**
* Initialize sitemap detection when Sitemap Import is clicked
*/
function initSitemapDetection() {
if (sitemapDetectionInitialized) return;
const loadingEl = document.getElementById('mxchat-sitemaps-loading');
const detectedEl = document.getElementById('mxchat-detected-sitemaps');
const noSitemapsEl = document.getElementById('mxchat-no-sitemaps');
const listEl = document.getElementById('mxchat-sitemaps-list');
const refreshBtn = document.getElementById('mxchat-refresh-sitemaps');
const nonceEl = document.getElementById('mxchat-detect-sitemaps-nonce');
if (!loadingEl || !nonceEl) return;
sitemapDetectionInitialized = true;
function detectSitemaps() {
// Show loading
loadingEl.style.display = 'block';
if (detectedEl) detectedEl.style.display = 'none';
if (noSitemapsEl) noSitemapsEl.style.display = 'none';
// Disable refresh button
if (refreshBtn) {
refreshBtn.disabled = true;
var refreshIcon = refreshBtn.querySelector('.dashicons');
if (refreshIcon) refreshIcon.classList.add('spin');
}
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'mxchat_detect_sitemaps',
nonce: nonceEl.value
},
timeout: 60000, // 60 second timeout for slow servers
success: function(data) {
loadingEl.style.display = 'none';
// Re-enable refresh button
if (refreshBtn) {
refreshBtn.disabled = false;
var refreshIcon = refreshBtn.querySelector('.dashicons');
if (refreshIcon) refreshIcon.classList.remove('spin');
}
if (data.success && data.data && data.data.sitemaps && data.data.sitemaps.length > 0) {
renderSitemaps(data.data.sitemaps);
if (detectedEl) detectedEl.style.display = 'block';
} else {
if (noSitemapsEl) {
noSitemapsEl.style.display = 'block';
$(noSitemapsEl).data('was-shown', true);
}
}
},
error: function(xhr, status, error) {
loadingEl.style.display = 'none';
if (noSitemapsEl) {
noSitemapsEl.style.display = 'block';
$(noSitemapsEl).data('was-shown', true);
}
if (refreshBtn) {
refreshBtn.disabled = false;
var refreshIcon = refreshBtn.querySelector('.dashicons');
if (refreshIcon) refreshIcon.classList.remove('spin');
}
}
});
}
function renderSitemaps(sitemaps) {
if (!listEl) return;
var html = '';
var botIdEl = document.getElementById('mxchat-sitemap-bot-id');
var botId = botIdEl ? botIdEl.value : '';
sitemaps.forEach(function(sitemap) {
if (sitemap.type === 'index' && sitemap.sub_sitemaps && sitemap.sub_sitemaps.length > 0) {
// Render sitemap index with sub-sitemaps
html += '
';
html += '
';
html += '
';
html += '';
html += '';
html += '
';
html += 'Sitemap Index';
html += '' + sitemap.source + '';
html += '
';
html += '
';
html += '';
html += sitemap.sub_sitemaps.length + ' sitemaps';
html += '';
html += '
';
html += '
';
sitemap.sub_sitemaps.forEach(function(sub) {
html += renderSitemapRow(sub, botId, true);
});
html += '
';
html += '
';
} else if (sitemap.type !== 'index') {
// Render standalone sitemap
html += renderSitemapRow(sitemap, botId, false);
}
});
listEl.innerHTML = html;
// Add click handlers for group toggles
$(listEl).find('.mxchat-sitemap-group-header').on('click', function() {
var $group = $(this).parent();
var $subList = $group.find('.mxchat-sitemap-sub-list');
var $arrow = $(this).find('.dashicons-arrow-right-alt2');
$group.toggleClass('expanded');
if ($group.hasClass('expanded')) {
$subList.slideDown(200);
$arrow.css('transform', 'rotate(90deg)');
} else {
$subList.slideUp(200);
$arrow.css('transform', 'rotate(0deg)');
}
});
// Add click handlers for process buttons
$(listEl).find('.mxchat-process-sitemap-btn').on('click', function() {
var url = $(this).data('url');
var type = $(this).data('sitemap-type');
processSitemap(url, type, this);
});
}
function renderSitemapRow(sitemap, botId, isSubItem) {
var typeLabels = {
'content': 'Content',
'taxonomy': 'Taxonomy',
'author': 'Authors'
};
var typeLabel = typeLabels[sitemap.type] || sitemap.type;
var displayName = sitemap.name || sitemap.url.split('/').pop();
var urlCount = sitemap.url_count || 0;
var paddingLeft = isSubItem ? '40px' : '16px';
var html = '
';
html += '
';
html += '';
html += '
';
html += '
';
html += displayName;
html += '
';
html += '
';
html += '' + typeLabel + '';
if (urlCount > 0) {
html += urlCount + ' URLs';
}
html += '
';
html += '
';
html += '
';
html += '';
html += '
';
return html;
}
function processSitemap(url, type, buttonEl) {
var $button = $(buttonEl);
var originalHtml = $button.html();
// Update button to show loading
$button.prop('disabled', true);
$button.html(' Processing...');
// Fill in the sitemap URL form and submit
var $form = $('#mxchat-url-form');
var $urlInput = $('#sitemap_url');
var $importType = $('#import_type');
if ($urlInput.length) {
$urlInput.val(url);
}
if ($importType.length) {
$importType.val('sitemap');
}
// Add a hidden submit field if not present (required by the PHP handler)
if ($form.find('input[name="submit_sitemap"]').length === 0) {
$form.append('');
}
// Submit the form
$form.submit();
}
// Refresh button handler
if (refreshBtn) {
$(refreshBtn).on('click', detectSitemaps);
}
// Start detection
detectSitemaps();
}
// Expose initSitemapDetection globally so it can be called from the import options handler
window.mxchatInitSitemapDetection = initSitemapDetection;
// ========================================
// ADMIN NOTICE DISMISS FUNCTIONALITY
// ========================================
// Initialize dismissible notices - add dismiss button if missing
function initDismissibleNotices() {
$('.notice.is-dismissible').each(function() {
var $notice = $(this);
// Skip if already has a dismiss button
if ($notice.find('.notice-dismiss').length > 0) {
return;
}
// Add dismiss button
var $dismissButton = $('');
$notice.append($dismissButton);
});
}
// Initialize on page load
initDismissibleNotices();
// Use event delegation for dismiss button clicks - works for existing and dynamically added notices
$(document).on('click', '.notice.is-dismissible .notice-dismiss', function(e) {
e.preventDefault();
e.stopPropagation();
var $notice = $(this).closest('.notice');
$notice.fadeTo(100, 0, function() {
$notice.slideUp(100, function() {
$notice.remove();
});
});
});
// Re-initialize when new notices are added dynamically (e.g., via AJAX)
$(document).on('DOMNodeInserted', function(e) {
if ($(e.target).hasClass('notice') && $(e.target).hasClass('is-dismissible')) {
setTimeout(initDismissibleNotices, 10);
}
});
// ========================================
// CUSTOM META-KEY DISCOVERY PICKER (plan-mxchat-20260709-fe8e4e)
// Mirror the ACF picker's discover-and-click UX for non-ACF post meta: scan
// published content for meta keys, render clickable chips, and append chosen
// keys into the existing whitelist textarea (its autosave then persists them).
// ========================================
(function() {
var $scanBtn = $('#mxchat-scan-meta-btn');
if (!$scanBtn.length) { return; }
var $results = $('#mxchat-meta-scan-results');
var $textarea = $('#mxchat_custom_meta_whitelist');
var $internal = $('#mxchat-scan-internal');
var $label = $scanBtn.find('.mxchat-scan-label');
var defaultLabel = $label.text();
function currentKeys() {
return ($textarea.val() || '')
.split('\n')
.map(function(k) { return k.trim(); })
.filter(function(k) { return k.length; });
}
function addKey(key) {
var keys = currentKeys();
if (keys.indexOf(key) !== -1) { return false; }
keys.push(key);
$textarea.val(keys.join('\n'));
// Fire the existing autosave (bound on 'change' in mxchat-admin.js).
$textarea.trigger('change');
return true;
}
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, function(c) {
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
});
}
function humanize(key) {
var h = key.replace(/^_+/, '').replace(/[_-]+/g, ' ').trim();
return h.length ? h.charAt(0).toUpperCase() + h.slice(1) : key;
}
function renderChips(keys) {
if (!keys || !keys.length) {
$results.html('
' +
'No custom meta keys found on your published content. Try enabling internal keys, or add keys manually below.' +
'
');
return;
}
var existing = currentKeys();
var html = '
';
keys.forEach(function(item) {
var already = existing.indexOf(item.key) !== -1;
html += '';
});
html += '
');
}).always(function() {
$scanBtn.prop('disabled', false);
$label.text(defaultLabel);
});
});
// Chip click -> add the raw key to the whitelist textarea (dedup) + autosave.
$results.on('click', '.mxchat-meta-chip', function() {
var $chip = $(this);
if ($chip.hasClass('is-added')) { return; }
var key = String($chip.data('key'));
if (addKey(key)) {
$chip.addClass('is-added').attr('aria-pressed', 'true');
$chip.find('.mxchat-meta-chip-tick').text('β Added');
}
});
})();
// ========================================
// VIDEO CARDS β threshold follows the master switch (plan f52492)
// ========================================
// Presentation only. The server gate does NOT consult the threshold when
// the switch is off, so hiding the field never changes behavior β it just
// stops the page offering a number that is currently inert. Mirrors the
// YouTube import form's own show/hide idiom on this same page. Autosave is
// untouched: both fields keep their own change handler in mxchat-admin.js.
$('#mxchat_video_embed_enabled').on('change', function() {
$('#mxchat-video-embed-threshold-field').toggle($(this).is(':checked'));
});
});