/** * MxChat Actions Page JavaScript - v2.0 * Split-panel layout with action list and editor */ jQuery(document).ready(function($) { // ========================================================================== // Sidebar Navigation // ========================================================================== // Desktop sidebar navigation $('.mxch-nav-link').on('click', function(e) { e.preventDefault(); const target = $(this).data('target'); // Update active states $('.mxch-nav-link').removeClass('active'); $(this).addClass('active'); // Show target section $('.mxch-section').removeClass('active'); $('#' + target).addClass('active'); // Also update mobile nav if open $('.mxch-mobile-nav-link').removeClass('active'); $('.mxch-mobile-nav-link[data-target="' + target + '"]').addClass('active'); // Load actions when switching to all-actions if (target === 'all-actions' && !actionsLoaded) { loadActionList(1, '', ''); } }); // Mobile menu toggle $('.mxch-mobile-menu-btn').on('click', function() { $('.mxch-mobile-menu').addClass('open'); $('.mxch-mobile-overlay').addClass('open'); }); // Close mobile menu $('.mxch-mobile-menu-close, .mxch-mobile-overlay').on('click', function() { $('.mxch-mobile-menu').removeClass('open'); $('.mxch-mobile-overlay').removeClass('open'); }); // Mobile navigation $('.mxch-mobile-nav-link').on('click', function(e) { e.preventDefault(); const target = $(this).data('target'); $('.mxch-mobile-nav-link').removeClass('active'); $(this).addClass('active'); $('.mxch-section').removeClass('active'); $('#' + target).addClass('active'); $('.mxch-nav-link').removeClass('active'); $('.mxch-nav-link[data-target="' + target + '"]').addClass('active'); $('.mxch-mobile-menu').removeClass('open'); $('.mxch-mobile-overlay').removeClass('open'); }); // ========================================================================== // AI Tools: autosave the function-calling toggle + per-tool checklist // (matches MxChat's autosave UX — no Save button). plan a41dee. // ========================================================================== var $fc = $('#mxch-fc-settings'); if ($fc.length) { var fcNonce = $fc.data('fc-nonce'); var $fcStatus = $('#mxch-fc-save-status'); var fcTimer = null; function fcCollectAndSave() { // Presence = active (plan d450a7): the global on/off toggle is gone, so // the gate is derived from whether any tool is enabled. The server // derives this too; we send it for an immediate, correct nav badge. var enabled = $fc.find('input[name="mxchat_fc_tools[]"]:checked').length > 0 ? '1' : '0'; var allTools = $fc.find('input[name="mxchat_fc_all_tools[]"]').map(function () { return $(this).val(); }).get(); var checked = $fc.find('input[name="mxchat_fc_tools[]"]:checked').map(function () { return $(this).val(); }).get(); // Per-tool "when to use" hints, keyed by callback. var hints = {}; $fc.find('.mxch-fc-hint-input').each(function () { var cb = $(this).data('fc-callback'); if (cb) { hints[cb] = $(this).val(); } }); if ($fcStatus.length) { $fcStatus.text('Saving…').removeClass('is-saved is-error'); } $.post(mxchActionsData.ajaxUrl, { action: 'mxchat_fc_autosave', nonce: fcNonce, enabled: enabled, all_tools: allTools, tools: checked, hints: hints }).done(function () { if ($fcStatus.length) { $fcStatus.text('Saved').addClass('is-saved'); setTimeout(function () { $fcStatus.text('').removeClass('is-saved'); }, 2000); } }).fail(function () { if ($fcStatus.length) { $fcStatus.text('Save failed — try again').addClass('is-error'); } }); } // The hidden store's checkboxes are toggled programmatically by the // list/detail/modal flow (which calls fcCollectAndSave() directly). Keep // a change-bound autosave as a safety net for any direct mutation. $fc.on('change', 'input[name="mxchat_fc_tools[]"]', function () { clearTimeout(fcTimer); fcTimer = setTimeout(fcCollectAndSave, 200); }); // ====================================================================== // AI Tools MASTER-DETAIL list + detail panel (plan d450a7). // Clones the Trigger Phrases list/detail UX: the left list shows the // ACTIVE tools (a tool in the list = active — no global toggle, no // per-tool enable checkbox); selecting one shows its detail on the right // with Edit (usage note) + Remove. "Add" opens the same two-step // capability picker. Pure view layer over the hidden #mxch-fc-tool-store // inputs — the unchanged mxchat_fc_autosave contract (mxchat_fc_tools[] / // mxchat_fc_all_tools[] + hints) stays the source of truth. FC-specific // IDs/classes keep the Trigger Phrases JS off these elements. // ====================================================================== var $fcStore = $('#mxch-fc-tool-store'); if ($fcStore.length) { var $fcPanel = $('#mxch-fc-panel'); var $fcList = $('#mxch-fc-list'); var $fcCount = $('#mxch-fc-count'); var $fcEmpty = $('#mxch-fc-empty'); var $fcView = $('#mxch-fc-view'); var $fcDetail = $('#mxch-fc-detail-panel'); var $fcSearch = $('#mxch-fc-search'); var $fcModal = $('#mxch-fc-tool-modal'); var fcCurrentCb = null; // tool being added/edited in the modal var fcViewCb = null; // tool currently shown in the detail panel var fcSelected = new Set(); // callbacks ticked for bulk remove (plan 5f7409) var fcI18n = { tools: $fcPanel.data('i18n-tools') || 'tools', tool: $fcPanel.data('i18n-tool') || 'tool', active: $fcPanel.data('i18n-active') || 'Active', addon: $fcPanel.data('i18n-addon') || 'Add-on', sensitive: $fcPanel.data('i18n-sensitive') || 'Sensitive', noHint: $fcPanel.data('i18n-nohint') || 'No usage note — the AI decides on its own when to use this.' }; // Localized Add/Edit titles + button labels read from the modal DOM. var $fcStep2Title = $('#mxch-fc-modal-step2-title'); var $fcSaveBtn = $('#mxch-fc-modal-save'); var FC_TITLE_ADD = $fcStep2Title.data('add-title') || 'Add Tool'; var FC_TITLE_EDIT = $fcStep2Title.data('edit-title') || 'Edit Tool'; var FC_BTN_ADD = $fcSaveBtn.data('add-label') || 'Add Tool'; var FC_BTN_SAVE = $fcSaveBtn.data('save-label') || 'Save Changes'; function fcEsc(s) { return $('
').text(s == null ? '' : String(s)).html(); } function fcEntry(cb) { return $fcStore.find('.mxch-fc-tool').filter(function () { return String($(this).data('fc-callback')) === String(cb); }); } function fcIsOn($t) { return $t.find('input[name="mxchat_fc_tools[]"]').is(':checked'); } function fcIsMobile() { return window.innerWidth <= 782; } // Show the empty "Select a tool" state in the detail panel. function fcShowEmpty() { fcViewCb = null; if ($fcView.length) $fcView.hide(); if ($fcEmpty.length) $fcEmpty.show(); $fcList.find('.mxch-fc-list-item').removeClass('active'); if (fcIsMobile()) { $fcDetail.removeClass('mobile-active'); $('body').removeClass('mxch-mobile-panel-open'); } } // Build the left list from the store (active tools only), applying the // current search filter. Updates the count + resets the view if the // shown tool was just removed. function fcRenderList() { if (!$fcList.length) return; var q = $.trim(($fcSearch.val() || '')).toLowerCase(); $fcList.empty(); var total = 0; $fcStore.find('.mxch-fc-tool').each(function () { var $t = $(this); if (!fcIsOn($t)) return; total++; var cb = $t.data('fc-callback'); var label = $t.data('fc-label') || cb; var desc = $t.data('fc-desc') || ''; var icon = $t.data('fc-icon') || 'admin-generic'; var isAddon = String($t.data('fc-addon')) === '1'; var isCautious = String($t.data('fc-cautious')) === '1'; if (q && String(label).toLowerCase().indexOf(q) === -1 && String(desc).toLowerCase().indexOf(q) === -1) return; var typeBits = []; if (isAddon) typeBits.push(fcI18n.addon); if (isCautious) typeBits.push(fcI18n.sensitive); var typeLabel = typeBits.length ? typeBits.join(' · ') : fcI18n.active; var activeCls = (String(cb) === String(fcViewCb)) ? ' active' : ''; var isSel = fcSelected.has(String(cb)); var selCls = isSel ? ' selected' : ''; $fcList.append( '
' + '' + '
' + '
' + '
' + fcEsc(label) + '
' + '
' + fcEsc(typeLabel) + '
' + '
' + '
' + '' + fcEsc(fcI18n.active) + '' + '
' + '
' ); }); if ($fcCount.length) { $fcCount.text(total + ' ' + (total === 1 ? fcI18n.tool : fcI18n.tools)); } // Drop any selected callbacks that are no longer in the list (e.g. // filtered out or removed) so the bulk count stays truthful. var fcPresent = {}; $fcList.find('.mxch-fc-list-item').each(function () { fcPresent[String($(this).data('fc-callback'))] = true; }); fcSelected.forEach(function (selCb) { if (!fcPresent[String(selCb)]) { fcSelected.delete(selCb); } }); fcUpdateSelectionUI(); // If the tool currently in the detail panel is no longer active, reset. if (fcViewCb && !fcIsOn(fcEntry(fcViewCb))) { fcShowEmpty(); } } // Populate + show the detail view for one tool. function fcSelectTool(cb) { var $t = fcEntry(cb); if (!$t.length || !fcIsOn($t)) { fcShowEmpty(); return; } fcViewCb = String(cb); var label = $t.data('fc-label') || cb; var desc = $t.data('fc-desc') || ''; var icon = $t.data('fc-icon') || 'admin-generic'; var isAddon = String($t.data('fc-addon')) === '1'; var isCautious = String($t.data('fc-cautious')) === '1'; var hint = $.trim($t.find('.mxch-fc-hint-input').val() || ''); var typeBits = []; if (isAddon) typeBits.push(fcI18n.addon); if (isCautious) typeBits.push(fcI18n.sensitive); $('#mxch-fc-view-icon').html(''); $('#mxch-fc-view-label').text(label); $('#mxch-fc-view-type').text(typeBits.join(' · ')); $('#mxch-fc-view-desc').text(desc); var $hint = $('#mxch-fc-view-hint'); if (hint) { $hint.text(hint).removeClass('is-muted'); } else { $hint.text(fcI18n.noHint).addClass('is-muted'); } // Setup requirement (plan 183856): show the tool's setup note (e.g. the // Brave-key requirement on Web/Image Search) when present, else hide it. var setup = $.trim(String($t.data('fc-setup') || '')); var $setupWrap = $('#mxch-fc-view-setup-wrap'); if (setup) { $('#mxch-fc-view-setup').text(setup); $setupWrap.show(); } else { $setupWrap.hide(); } $fcEmpty.hide(); $fcView.show(); $fcList.find('.mxch-fc-list-item').removeClass('active') .filter(function () { return String($(this).data('fc-callback')) === String(cb); }).addClass('active'); if (fcIsMobile()) { $fcDetail.addClass('mobile-active'); $('body').addClass('mxch-mobile-panel-open'); } } // ---- Add/Edit modal (two-step capability picker) ---- function fcShowStep(n) { $fcModal.find('.mxch-fc-modal-step').hide(); $fcModal.find('.mxch-fc-modal-step[data-step="' + n + '"]').show(); } function fcCloseModal() { $fcModal.hide(); fcCurrentCb = null; } // Step 1: grid of tools not yet added. function fcOpenAddModal() { var $grid = $('#mxch-fc-modal-grid').empty(); var addable = 0; $fcStore.find('.mxch-fc-tool').each(function () { var $t = $(this); if (fcIsOn($t)) return; // already active addable++; var cb = $t.data('fc-callback'); var label = $t.data('fc-label') || cb; var desc = $t.data('fc-desc') || ''; var icon = $t.data('fc-icon') || 'admin-generic'; var isAddon = String($t.data('fc-addon')) === '1'; var isCautious = String($t.data('fc-cautious')) === '1'; var badges = ''; if (isAddon) { badges += ' ' + fcEsc(fcI18n.addon) + ''; } if (isCautious) { badges += ' ' + fcEsc(fcI18n.sensitive) + ''; } $grid.append( '
' + '
' + '
' + '

' + fcEsc(label) + badges + '

' + '

' + fcEsc(desc) + '

' + '
' + '
' ); }); $('#mxch-fc-modal-allset').toggle(addable === 0); $grid.toggle(addable > 0); fcShowStep(1); $fcModal.css('display', 'flex'); } // Step 2: confirm + when-to-use (shared by Add and Edit). function fcOpenConfigStep(cb) { var $t = fcEntry(cb); if (!$t.length) return; fcCurrentCb = cb; var editing = fcIsOn($t); $('#mxch-fc-modal-selected-icon').html(''); $('#mxch-fc-modal-selected-label').text($t.data('fc-label') || cb); $('#mxch-fc-modal-selected-desc').text($t.data('fc-desc') || ''); $('#mxch-fc-modal-hint').val($t.find('.mxch-fc-hint-input').val() || ''); $('#mxch-fc-modal-sensitive').toggle(String($t.data('fc-cautious')) === '1'); $fcStep2Title.text(editing ? FC_TITLE_EDIT : FC_TITLE_ADD); $fcSaveBtn.text(editing ? FC_BTN_SAVE : FC_BTN_ADD); fcShowStep(2); $fcModal.css('display', 'flex'); } // ---- wiring ---- // "Add" buttons (panel header + empty state) — delegated on $fc. $fc.on('click', '.js-mxch-fc-add', fcOpenAddModal); $fcModal.on('click', '.mxch-fc-type-card', function () { fcOpenConfigStep($(this).data('fc-callback')); }); $('#mxch-fc-modal-back').on('click', fcOpenAddModal); $fcModal.on('click', '[data-fc-modal-close]', fcCloseModal); $fcModal.on('click', function (e) { if (e.target === this) fcCloseModal(); }); // backdrop $(document).on('keydown', function (e) { if (e.key === 'Escape' && $fcModal.is(':visible')) fcCloseModal(); }); // Modal Save (Add or Edit): write the store inputs, autosave, refresh // the list, and show the tool's detail on the right. $fcSaveBtn.on('click', function () { if (!fcCurrentCb) return; var $t = fcEntry(fcCurrentCb); if (!$t.length) return; var cb = fcCurrentCb; $t.find('.mxch-fc-hint-input').val($('#mxch-fc-modal-hint').val() || ''); $t.find('input[name="mxchat_fc_tools[]"]').prop('checked', true); fcCloseModal(); fcRenderList(); fcSelectTool(cb); fcCollectAndSave(); }); // List item click → show that tool's detail. $fcList.on('click', '.mxch-fc-list-item', function () { fcSelectTool($(this).data('fc-callback')); }); // Detail Edit → reopen the config step for the viewed tool. $('#mxch-fc-edit-btn').on('click', function () { if (fcViewCb) fcOpenConfigStep(fcViewCb); }); // Detail Remove → disable the tool (keep its hint for a later re-add), // refresh the list, and return to the empty state. $('#mxch-fc-remove-btn').on('click', function () { if (!fcViewCb) return; var $t = fcEntry(fcViewCb); if (!$t.length) return; $t.find('input[name="mxchat_fc_tools[]"]').prop('checked', false); fcShowEmpty(); fcRenderList(); fcCollectAndSave(); }); // ---- Bulk selection + bulk remove ---- // Clones the Trigger Phrases #all-actions bulk toolbar behavior, but // wires removal to the SAME path the single Remove button uses (uncheck // each tool's store checkbox, then one autosave). FC-specific IDs keep // this isolated from the Trigger Phrases bulk JS (plan 5f7409). var $fcSelectAll = $('#mxch-fc-select-all'); var $fcBulkDelete = $('#mxch-fc-delete-selected'); var $fcSelCount = $('#mxch-fc-selected-count'); function fcUpdateSelectionUI() { var count = fcSelected.size; if (count > 0) { $fcSelCount.text(count + ' selected').addClass('has-selection'); $fcBulkDelete.prop('disabled', false); $fcList.addClass('selection-mode'); } else { $fcSelCount.removeClass('has-selection'); $fcBulkDelete.prop('disabled', true); $fcList.removeClass('selection-mode'); } var totalItems = $fcList.find('.mxch-fc-checkbox').length; var checkedItems = $fcList.find('.mxch-fc-checkbox:checked').length; $fcSelectAll.prop('checked', totalItems > 0 && checkedItems === totalItems); $fcSelectAll.prop('indeterminate', checkedItems > 0 && checkedItems < totalItems); } // Select-all toggles every visible tool row. $fcSelectAll.on('change', function () { var on = $(this).is(':checked'); fcSelected.clear(); $fcList.find('.mxch-fc-list-item').each(function () { var $row = $(this); var cb = String($row.data('fc-callback')); $row.find('.mxch-fc-checkbox').prop('checked', on); if (on) { fcSelected.add(cb); $row.addClass('selected'); } else { $row.removeClass('selected'); } }); fcUpdateSelectionUI(); }); // Per-row checkbox: toggle selection without opening the detail view. $fcList.on('click', '.mxch-fc-checkbox', function (e) { e.stopPropagation(); var $row = $(this).closest('.mxch-fc-list-item'); var cb = String($row.data('fc-callback')); if ($(this).is(':checked')) { fcSelected.add(cb); $row.addClass('selected'); } else { fcSelected.delete(cb); $row.removeClass('selected'); } fcUpdateSelectionUI(); }); // Bulk remove: same removal path as single Remove — uncheck each tool's // store checkbox (presence = active), then refresh + autosave once. $fcBulkDelete.on('click', function () { if (!fcSelected.size) return; var msg = (window.mxchActionsData && mxchActionsData.i18n && mxchActionsData.i18n.confirmBulkDelete) || 'Remove the selected tools? They stay available to add back later.'; if (!window.confirm(msg)) return; var removingViewed = false; fcSelected.forEach(function (cb) { var $t = fcEntry(cb); if ($t.length) { $t.find('input[name="mxchat_fc_tools[]"]').prop('checked', false); } if (String(cb) === String(fcViewCb)) { removingViewed = true; } }); fcSelected.clear(); if (removingViewed) { fcShowEmpty(); } fcRenderList(); fcCollectAndSave(); }); // Search filter. $fcSearch.on('input', function () { fcRenderList(); }); // Refresh — re-render the list from the store (parity with the Trigger // Phrases refresh affordance; the list is client-built so this re-syncs // the view rather than re-fetching). $('#mxch-fc-refresh').on('click', function () { var $btn = $(this); $btn.addClass('spinning'); fcRenderList(); setTimeout(function () { $btn.removeClass('spinning'); }, 500); }); fcRenderList(); } } // Quick action buttons $('.mxch-quick-action-btn[data-action="view-actions"]').on('click', function() { $('.mxch-nav-link[data-target="all-actions"]').trigger('click'); }); // Dashboard explainer cards — generic "jump to this section" (e.g. Set up AI Tools). $('.mxch-approach-btn[data-approach-target]').on('click', function() { var target = $(this).data('approach-target'); $('.mxch-nav-link[data-target="' + target + '"]').trigger('click'); }); // "Add Trigger Phrase" on the dashboard → jump to Trigger Phrases + open the editor. $('#mxch-add-action-dashboard-btn').on('click', function() { $('.mxch-nav-link[data-target="all-actions"]').trigger('click'); setTimeout(function() { openActionEditor(); }, 100); }); // ========================================================================== // Mobile Detection & Panel Management // ========================================================================== function isMobile() { return window.innerWidth <= 782; } function showMobileDetailPanel() { if (isMobile()) { $('.mxch-action-detail-panel').addClass('mobile-active'); $('body').addClass('mxch-mobile-panel-open'); } } function hideMobileDetailPanel() { $('.mxch-action-detail-panel').removeClass('mobile-active'); $('body').removeClass('mxch-mobile-panel-open'); } // Mobile back button handler $(document).on('click', '.mxch-mobile-back-btn', function(e) { e.preventDefault(); hideMobileDetailPanel(); resetEditorPanel(); $('.mxch-action-item').removeClass('active'); currentActionId = null; }); // ========================================================================== // Action List - Split Panel // ========================================================================== let currentPage = 1; const perPage = 50; let totalPages = 1; let currentActionId = null; let actionsLoaded = false; let selectedActions = new Set(); let currentSortOrder = 'desc'; let currentFilter = ''; // ========================================================================== // Phrase Tag Management // ========================================================================== let currentPhrases = []; // Array of {id, text, isLegacy} function renderPhraseTags() { const $container = $('#mxch-phrase-tags'); let html = ''; currentPhrases.forEach(function(phrase, index) { const legacyClass = phrase.isLegacy ? ' mxch-phrase-legacy' : ''; const badge = phrase.isLegacy ? ' legacy' : ''; html += '' + escapeHtml(phrase.text) + badge + ' ' + ''; }); $container.html(html); } function addPhraseFromInput() { const $input = $('#mxch-phrase-input'); const text = $input.val().trim(); if (!text) return; const formMode = $('#mxch-form-mode').val(); const intentId = $('#mxch-action-id').val(); if (formMode === 'edit' && intentId) { // Edit mode: AJAX add immediately const $btn = $('#mxch-add-phrase-btn'); $btn.prop('disabled', true).text('Adding...'); $.ajax({ url: mxchActionsData.ajaxUrl, type: 'POST', data: { action: 'mxchat_add_phrase', intent_id: intentId, phrase: text, security: mxchActionsData.addPhraseNonce }, success: function(response) { $btn.prop('disabled', false).text('Add'); if (response.success) { currentPhrases.push({id: response.data.id, text: text, isLegacy: false}); renderPhraseTags(); $input.val('').focus(); } else { alert(response.data || 'Failed to add phrase.'); } }, error: function() { $btn.prop('disabled', false).text('Add'); alert('Failed to add phrase. Please try again.'); } }); } else { // Add mode: just push locally currentPhrases.push({id: null, text: text, isLegacy: false}); renderPhraseTags(); $input.val('').focus(); } } // Add phrase on Enter key $(document).on('keydown', '#mxch-phrase-input', function(e) { if (e.key === 'Enter') { e.preventDefault(); addPhraseFromInput(); } }); // Add phrase on button click $(document).on('click', '#mxch-add-phrase-btn', function() { addPhraseFromInput(); }); // Remove phrase on X click $(document).on('click', '.mxch-phrase-remove', function(e) { e.stopPropagation(); const $pill = $(this).closest('.mxch-phrase-pill'); const index = parseInt($pill.data('index')); const phrase = currentPhrases[index]; if (!phrase) return; if (phrase.isLegacy) { // Delete legacy phrases from main table const intentId = $('#mxch-action-id').val(); if (!intentId) { currentPhrases.splice(index, 1); renderPhraseTags(); return; } if (!confirm('Remove all legacy grouped phrases? You can re-add them individually for better matching.')) return; $.ajax({ url: mxchActionsData.ajaxUrl, type: 'POST', data: { action: 'mxchat_delete_legacy_phrases', intent_id: intentId, security: mxchActionsData.deleteLegacyNonce }, success: function(response) { if (response.success) { currentPhrases.splice(index, 1); renderPhraseTags(); } else { alert(response.data || 'Failed to remove legacy phrases.'); } } }); } else if (phrase.id) { // Delete individual phrase via AJAX $.ajax({ url: mxchActionsData.ajaxUrl, type: 'POST', data: { action: 'mxchat_delete_phrase', phrase_id: phrase.id, security: mxchActionsData.deletePhraseNonce }, success: function(response) { if (response.success) { currentPhrases.splice(index, 1); renderPhraseTags(); } else { alert(response.data || 'Failed to delete phrase.'); } } }); } else { // Local-only phrase (add mode, not yet saved) currentPhrases.splice(index, 1); renderPhraseTags(); } }); function fetchIndividualPhrases(intentId, callback) { $.ajax({ url: mxchActionsData.ajaxUrl, type: 'POST', data: { action: 'mxchat_get_phrases', intent_id: intentId, security: mxchActionsData.getPhrasesNonce }, success: function(response) { if (response.success) { callback(response.data.phrases || []); } else { callback([]); } }, error: function() { callback([]); } }); } // Load action list on page load loadActionList(1, '', ''); // Search functionality with debounce let searchTimeout; $('#mxch-search-actions').on('input', function() { clearTimeout(searchTimeout); const searchTerm = $(this).val().toLowerCase(); searchTimeout = setTimeout(function() { currentPage = 1; loadActionList(currentPage, searchTerm, currentFilter); }, 300); }); // Filter by type $('#mxch-filter-actions').on('change', function() { currentFilter = $(this).val(); currentPage = 1; loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter); }); // Refresh button $('#mxch-refresh-actions').on('click', function() { const $btn = $(this); $btn.addClass('spinning'); loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter); setTimeout(() => $btn.removeClass('spinning'), 500); }); // ========================================================================== // Bulk Selection & Actions // ========================================================================== // Select all checkbox $('#mxch-select-all-actions').on('change', function() { const isChecked = $(this).is(':checked'); $('.mxch-action-checkbox').prop('checked', isChecked); if (isChecked) { $('.mxch-action-item').each(function() { selectedActions.add($(this).data('action-id')); $(this).addClass('selected'); }); $('#mxch-action-list').addClass('selection-mode'); } else { selectedActions.clear(); $('.mxch-action-item').removeClass('selected'); $('#mxch-action-list').removeClass('selection-mode'); } updateSelectionUI(); }); // Update selection UI function updateSelectionUI() { const count = selectedActions.size; const $countEl = $('#mxch-selected-action-count'); const $deleteBtn = $('#mxch-delete-selected-actions'); if (count > 0) { $countEl.text(count + ' selected').addClass('has-selection'); $deleteBtn.prop('disabled', false); $('#mxch-action-list').addClass('selection-mode'); } else { $countEl.removeClass('has-selection'); $deleteBtn.prop('disabled', true); $('#mxch-action-list').removeClass('selection-mode'); } // Update select all checkbox state const totalItems = $('.mxch-action-checkbox').length; const checkedItems = $('.mxch-action-checkbox:checked').length; $('#mxch-select-all-actions').prop('checked', totalItems > 0 && checkedItems === totalItems); $('#mxch-select-all-actions').prop('indeterminate', checkedItems > 0 && checkedItems < totalItems); } // Delete selected button $('#mxch-delete-selected-actions').on('click', function() { const count = selectedActions.size; if (count === 0) return; if (!confirm(mxchActionsData.i18n.confirmBulkDelete)) { return; } deleteMultipleActions(Array.from(selectedActions)); }); // Delete multiple actions function deleteMultipleActions(actionIds) { showLoading(); $.ajax({ url: mxchActionsData.ajaxUrl, type: 'POST', data: { action: 'mxchat_bulk_delete_actions', action_ids: actionIds, security: mxchActionsData.deleteNonce }, success: function(response) { hideLoading(); if (response.success) { // Clear selection selectedActions.clear(); $('#mxch-select-all-actions').prop('checked', false); updateSelectionUI(); // If current action was deleted, reset panel if (actionIds.includes(currentActionId)) { currentActionId = null; resetEditorPanel(); } // Reload list loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter); } else { alert(response.data || mxchActionsData.i18n.error); } }, error: function() { hideLoading(); alert(mxchActionsData.i18n.error); } }); } // Load action list function function loadActionList(page, searchTerm, filter) { const $container = $('#mxch-action-list'); $container.html('
'); $.ajax({ url: mxchActionsData.ajaxUrl, type: 'POST', data: { action: 'mxchat_fetch_actions_list', page: page, per_page: perPage, search: searchTerm, callback_filter: filter, sort_order: currentSortOrder, security: mxchActionsData.nonce }, success: function(response) { actionsLoaded = true; if (response.success && response.data.actions && response.data.actions.length > 0) { renderActionList(response.data.actions); currentPage = response.data.page; totalPages = response.data.total_pages; updateActionCount(response.data.showing_start, response.data.showing_end, response.data.total_actions); renderPagination(response.data.page, response.data.total_pages, searchTerm, filter); } else { $container.html('

No trigger phrases found

'); updateActionCount(0, 0, 0); $('#mxch-actions-pagination').html(''); } }, error: function() { $container.html('

Error loading trigger phrases

'); } }); } // Render action list items function renderActionList(actions) { const $container = $('#mxch-action-list'); let html = ''; actions.forEach(function(action) { const isActive = action.id == currentActionId ? ' active' : ''; const isSelected = selectedActions.has(action.id) ? ' selected' : ''; const isChecked = selectedActions.has(action.id) ? ' checked' : ''; const isDisabled = !action.enabled ? ' disabled' : ''; const statusClass = action.enabled ? 'enabled' : 'disabled'; const statusText = action.enabled ? 'Enabled' : 'Disabled'; html += `
${escapeHtml(action.label)}
${escapeHtml(action.callback_label)}
${statusText}
`; }); $container.html(html); // Attach checkbox handlers $('.mxch-action-checkbox').on('click', function(e) { e.stopPropagation(); const $item = $(this).closest('.mxch-action-item'); const actionId = $item.data('action-id'); if ($(this).is(':checked')) { selectedActions.add(actionId); $item.addClass('selected'); } else { selectedActions.delete(actionId); $item.removeClass('selected'); } updateSelectionUI(); }); // Attach click handlers for selecting action $('.mxch-action-item').on('click', function(e) { if ($(e.target).is('.mxch-action-checkbox')) return; const actionId = $(this).data('action-id'); selectAction(actionId, $(this)); // Update active state $('.mxch-action-item').removeClass('active'); $(this).addClass('active'); }); // Update selection UI after render updateSelectionUI(); } // Update action count display function updateActionCount(start, end, total) { if (total === 0) { $('#mxch-action-count').text('0 trigger phrases'); } else { $('#mxch-action-count').text(`${start}-${end} / ${total} trigger phrases`); } } // Render pagination function renderPagination(currentPage, totalPages, searchTerm, filter) { const $container = $('#mxch-actions-pagination'); if (totalPages <= 1) { $container.html(''); return; } let html = '
'; if (currentPage > 1) { html += ``; } html += `${currentPage} / ${totalPages}`; if (currentPage < totalPages) { html += ``; } html += '
'; $container.html(html); // Pagination click handlers $('.mxch-page-btn').on('click', function() { const pageNum = $(this).data('page'); loadActionList(pageNum, searchTerm, filter); }); } // ========================================================================== // Action Editor Panel // ========================================================================== // Select and view an action function selectAction(actionId, $item) { currentActionId = actionId; // Get data from item attributes const data = { id: actionId, label: $item.data('label'), phrases: $item.data('phrases'), threshold: $item.data('threshold'), callback_function: $item.data('callback'), enabled: $item.data('enabled') == '1', enabled_bots: $item.data('enabled-bots') || ['default'], has_legacy: $item.data('has-legacy') == '1', individualPhrases: [] }; // Fetch individual phrases then show view fetchIndividualPhrases(actionId, function(phrases) { data.individualPhrases = phrases; showActionView(data); }); } // Show action view (details) function showActionView(action) { $('#mxch-action-empty').hide(); $('#mxch-action-editor').show(); // Hide editor steps, show view $('.mxch-editor-step').removeClass('active'); $('#mxch-action-view').addClass('active'); // Show detail panel on mobile showMobileDetailPanel(); // Populate view $('#mxch-view-title').text(action.label); $('#mxch-view-label').text(action.label); // Get callback label from filter dropdown const callbackLabel = $('#mxch-filter-actions option[value="' + action.callback_function + '"]').text() || action.callback_function; $('#mxch-view-type').text(callbackLabel); // Get icon from type grid const $typeCard = $('.mxch-type-card[data-value="' + action.callback_function + '"]'); const iconClass = $typeCard.find('.dashicons').attr('class') || 'dashicons dashicons-admin-generic'; $('#mxch-view-icon').html(''); $('#mxch-detail-icon').html(''); // Phrases - show legacy grouped phrases and individual phrases let phrasesHtml = ''; const legacyPhrases = action.phrases ? String(action.phrases).trim() : ''; if (legacyPhrases) { phrasesHtml += '' + escapeHtml(legacyPhrases) + ' legacy'; } if (action.individualPhrases && action.individualPhrases.length) { action.individualPhrases.forEach(function(p) { phrasesHtml += '' + escapeHtml(p.phrase) + ''; }); } if (!phrasesHtml) { phrasesHtml = 'No trigger phrases configured'; } $('#mxch-view-phrases').html(phrasesHtml); // Settings $('#mxch-view-threshold').text(action.threshold + '%'); const statusText = action.enabled ? 'Enabled' : 'Disabled'; const statusClass = action.enabled ? 'mxch-status-enabled' : 'mxch-status-disabled'; $('#mxch-view-status').text(statusText).removeClass('mxch-status-enabled mxch-status-disabled').addClass(statusClass); // Toggle switch $('#mxch-action-enabled-toggle').prop('checked', action.enabled); // Bots let botsHtml = ''; const enabledBots = Array.isArray(action.enabled_bots) ? action.enabled_bots : ['default']; enabledBots.forEach(function(bot) { const botName = bot === 'default' ? 'Default Bot' : bot; botsHtml += '' + escapeHtml(botName) + ''; }); $('#mxch-view-bots').html(botsHtml); // Store current action data for editing $('#mxch-action-view').data('current-action', action); } // Open action editor (for creating new) function openActionEditor() { currentActionId = null; $('#mxch-action-empty').hide(); $('#mxch-action-editor').show(); // Show step 1 $('.mxch-editor-step').removeClass('active'); $('#mxch-editor-step-1').addClass('active'); // Reset form resetForm(); // Show detail panel on mobile showMobileDetailPanel(); } // Open action editor for editing function openActionEditorForEdit(action) { currentActionId = action.id; $('#mxch-action-empty').hide(); $('#mxch-action-editor').show(); // Go directly to step 2 $('.mxch-editor-step').removeClass('active'); $('#mxch-editor-step-2').addClass('active'); // Populate form $('#mxch-action-id').val(action.id); $('#mxch-callback-function').val(action.callback_function); $('#mxch-form-mode').val('edit'); $('#mxch-action-label').val(action.label); $('#mxch-action-threshold').val(action.threshold); $('#mxch-threshold-value').text(action.threshold + '%'); // Populate phrase tags currentPhrases = []; const legacyPhrases = action.phrases ? String(action.phrases).trim() : ''; if (legacyPhrases) { currentPhrases.push({id: 'legacy', text: legacyPhrases, isLegacy: true}); } if (action.individualPhrases && action.individualPhrases.length) { action.individualPhrases.forEach(function(p) { currentPhrases.push({id: p.id, text: p.phrase, isLegacy: false}); }); } renderPhraseTags(); // Set selected type display const $typeCard = $('.mxch-type-card[data-value="' + action.callback_function + '"]'); const iconHtml = $typeCard.find('.mxch-type-icon').html(); const typeLabel = $typeCard.data('label'); const typeDesc = $typeCard.find('p').text(); $('#mxch-selected-type-icon').html(iconHtml); $('#mxch-selected-type-label').text(typeLabel); $('#mxch-selected-type-desc').text(typeDesc); $('#mxch-config-title').text('Edit Trigger Phrase'); // Set enabled bots const enabledBots = Array.isArray(action.enabled_bots) ? action.enabled_bots : ['default']; $('#mxch-bot-selector input[type="checkbox"]').each(function() { $(this).prop('checked', enabledBots.includes($(this).val())); }); // Update save button text $('#mxch-save-action').text('Update Trigger Phrase'); } // Reset form function resetForm() { $('#mxch-action-id').val(''); $('#mxch-callback-function').val(''); $('#mxch-form-mode').val('add'); $('#mxch-action-label').val(''); $('#mxch-action-threshold').val(85); // Clear phrase tags currentPhrases = []; renderPhraseTags(); $('#mxch-threshold-value').text('85%'); $('#mxch-config-title').text('Configure Trigger Phrase'); $('#mxch-save-action').text('Save Trigger Phrase'); // Reset bot selection $('#mxch-bot-selector input[type="checkbox"]').prop('checked', false); $('#mxch-bot-selector input[value="default"]').prop('checked', true); // Reset type selection $('.mxch-type-card').removeClass('selected'); } // Reset editor panel to empty state function resetEditorPanel() { $('#mxch-action-editor').hide(); $('#mxch-action-empty').show(); $('.mxch-editor-step').removeClass('active'); // Hide mobile panel hideMobileDetailPanel(); } // ========================================================================== // Editor Event Handlers // ========================================================================== // Add new action buttons $('#mxch-add-action-btn, #mxch-create-first-action-btn').on('click', function() { openActionEditor(); }); // Close editor buttons $('#mxch-close-editor, #mxch-close-editor-2').on('click', function() { resetEditorPanel(); $('.mxch-action-item').removeClass('active'); currentActionId = null; }); // Cancel button $('#mxch-cancel-action').on('click', function() { if (currentActionId && $('#mxch-form-mode').val() === 'edit') { // Go back to view mode const action = $('#mxch-action-view').data('current-action'); if (action) { showActionView(action); } } else { resetEditorPanel(); } }); // Back to step 1 $('#mxch-back-to-step-1').on('click', function() { if ($('#mxch-form-mode').val() === 'edit') { // If editing, go back to view const action = $('#mxch-action-view').data('current-action'); if (action) { showActionView(action); } } else { // If creating, go back to step 1 $('.mxch-editor-step').removeClass('active'); $('#mxch-editor-step-1').addClass('active'); } }); // Edit action button $('#mxch-edit-action-btn').on('click', function() { const action = $('#mxch-action-view').data('current-action'); if (action) { openActionEditorForEdit(action); } }); // Toggle enabled status $('#mxch-action-enabled-toggle').on('change', function() { if (!currentActionId) return; const isEnabled = $(this).is(':checked'); toggleActionStatus(currentActionId, isEnabled); }); // Delete action button $('#mxch-delete-action-btn').on('click', function() { if (!currentActionId) return; showDeleteModal(currentActionId); }); // ========================================================================== // Action Type Selection (Step 1) // ========================================================================== // Type search $('#mxch-type-search-input').on('input', function() { const searchTerm = $(this).val().toLowerCase(); filterTypeCards(searchTerm, 'all'); }); // Category buttons $('.mxch-category-btn').on('click', function() { $('.mxch-category-btn').removeClass('active'); $(this).addClass('active'); const category = $(this).data('category'); const searchTerm = $('#mxch-type-search-input').val().toLowerCase(); filterTypeCards(searchTerm, category); }); // Filter type cards function filterTypeCards(searchTerm, category) { $('.mxch-type-card').each(function() { const $card = $(this); const cardCategory = $card.data('category'); const cardLabel = $card.data('label').toLowerCase(); const cardDesc = $card.find('p').text().toLowerCase(); const matchesCategory = category === 'all' || cardCategory === category; const matchesSearch = !searchTerm || cardLabel.includes(searchTerm) || cardDesc.includes(searchTerm); if (matchesCategory && matchesSearch) { $card.show(); } else { $card.hide(); } }); } // Type card click $('.mxch-type-card').on('click', function() { const $card = $(this); // Check if pro required if ($card.hasClass('mxch-type-pro') && !mxchActionsData.isActivated) { alert(mxchActionsData.i18n.proRequired); return; } // Check if addon required if ($card.hasClass('mxch-type-addon') && $card.data('installed') !== true) { alert(mxchActionsData.i18n.addonRequired); return; } // Select this card $('.mxch-type-card').removeClass('selected'); $card.addClass('selected'); // Get card data const callback = $card.data('value'); const label = $card.data('label'); const iconHtml = $card.find('.mxch-type-icon').html(); const desc = $card.find('p').text(); // Populate step 2 $('#mxch-callback-function').val(callback); $('#mxch-selected-type-icon').html(iconHtml); $('#mxch-selected-type-label').text(label); $('#mxch-selected-type-desc').text(desc); // Go to step 2 $('.mxch-editor-step').removeClass('active'); $('#mxch-editor-step-2').addClass('active'); }); // ========================================================================== // Form Handling // ========================================================================== // Threshold slider $('#mxch-action-threshold').on('input', function() { $('#mxch-threshold-value').text($(this).val() + '%'); }); // Form submission $('#mxch-action-form').on('submit', function(e) { e.preventDefault(); const formMode = $('#mxch-form-mode').val(); const actionId = $('#mxch-action-id').val(); const callbackFunction = $('#mxch-callback-function').val(); const label = $('#mxch-action-label').val().trim(); const threshold = $('#mxch-action-threshold').val(); // Validation if (!callbackFunction) { alert('Please select an action type.'); return; } if (!label) { alert('Please enter an action label.'); $('#mxch-action-label').focus(); return; } // Check phrases exist (from tag input) if (currentPhrases.length === 0) { alert('Please add at least one trigger phrase.'); $('#mxch-phrase-input').focus(); return; } // Get enabled bots const enabledBots = []; $('#mxch-bot-selector input[type="checkbox"]:checked').each(function() { enabledBots.push($(this).val()); }); if (enabledBots.length === 0) { enabledBots.push('default'); } showLoading(); const data = { action: formMode === 'edit' ? 'mxchat_edit_intent_ajax' : 'mxchat_add_intent_ajax', intent_label: label, similarity_threshold: threshold, callback_function: callbackFunction, enabled_bots: enabledBots, security: formMode === 'edit' ? mxchActionsData.editNonce : mxchActionsData.addNonce }; if (formMode === 'edit') { data.intent_id = actionId; // In edit mode, phrases are managed via individual AJAX calls already // Send empty phrases to skip legacy re-embedding data.phrases = ''; } else { // Add mode: send individual phrases for per-phrase embedding const nonLegacyPhrases = currentPhrases.filter(p => !p.isLegacy).map(p => p.text); if (nonLegacyPhrases.length > 0) { data.individual_phrases = nonLegacyPhrases; data.phrases = ''; } else { data.phrases = ''; } } $.ajax({ url: mxchActionsData.ajaxUrl, type: 'POST', data: data, success: function(response) { hideLoading(); if (response.success) { // Reload list loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter); // Reset and close editor resetEditorPanel(); currentActionId = null; // Show success message (could use a toast notification) // For now, just reload } else { alert(response.data || mxchActionsData.i18n.error); } }, error: function() { hideLoading(); alert(mxchActionsData.i18n.error); } }); }); // ========================================================================== // Toggle Action Status // ========================================================================== function toggleActionStatus(actionId, isEnabled) { // Optimistic UI update - update immediately before AJAX completes const $item = $('.mxch-action-item[data-action-id="' + actionId + '"]'); const $status = $item.find('.mxch-action-status'); const $toggle = $('#mxch-action-enabled-toggle'); // Immediately update UI $item.data('enabled', isEnabled ? '1' : '0'); if (isEnabled) { $status.removeClass('disabled').addClass('enabled').text('Enabled'); $item.removeClass('disabled'); } else { $status.removeClass('enabled').addClass('disabled').text('Disabled'); $item.addClass('disabled'); } // Update view status immediately const statusText = isEnabled ? 'Enabled' : 'Disabled'; const statusClass = isEnabled ? 'mxch-status-enabled' : 'mxch-status-disabled'; $('#mxch-view-status').text(statusText).removeClass('mxch-status-enabled mxch-status-disabled').addClass(statusClass); // Update stored data immediately const currentData = $('#mxch-action-view').data('current-action'); if (currentData) { currentData.enabled = isEnabled; $('#mxch-action-view').data('current-action', currentData); } $.ajax({ url: mxchActionsData.ajaxUrl, type: 'POST', data: { action: 'mxchat_toggle_action_status', action_id: actionId, enabled: isEnabled ? 1 : 0, security: mxchActionsData.toggleNonce }, success: function(response) { if (!response.success) { // Revert optimistic update on failure revertToggleUI(!isEnabled, $item, $status, $toggle); alert(response.data || mxchActionsData.i18n.error); } }, error: function() { // Revert optimistic update on error revertToggleUI(!isEnabled, $item, $status, $toggle); alert(mxchActionsData.i18n.error); } }); } // Helper to revert toggle UI on error function revertToggleUI(revertEnabled, $item, $status, $toggle) { $toggle.prop('checked', revertEnabled); $item.data('enabled', revertEnabled ? '1' : '0'); if (revertEnabled) { $status.removeClass('disabled').addClass('enabled').text('Enabled'); $item.removeClass('disabled'); } else { $status.removeClass('enabled').addClass('disabled').text('Disabled'); $item.addClass('disabled'); } const statusText = revertEnabled ? 'Enabled' : 'Disabled'; const statusClass = revertEnabled ? 'mxch-status-enabled' : 'mxch-status-disabled'; $('#mxch-view-status').text(statusText).removeClass('mxch-status-enabled mxch-status-disabled').addClass(statusClass); } // ========================================================================== // Delete Modal // ========================================================================== function showDeleteModal(actionId) { $('#mxch-delete-modal').show(); $('#mxch-confirm-delete').data('action-id', actionId); } $('#mxch-delete-modal .mxch-modal-close, #mxch-cancel-delete').on('click', function() { $('#mxch-delete-modal').hide(); }); $('#mxch-confirm-delete').on('click', function() { const actionId = $(this).data('action-id'); $('#mxch-delete-modal').hide(); deleteAction(actionId); }); function deleteAction(actionId) { showLoading(); $.ajax({ url: mxchActionsData.ajaxUrl, type: 'POST', data: { action: 'mxchat_delete_intent_ajax', intent_id: actionId, security: mxchActionsData.deleteNonce }, success: function(response) { hideLoading(); if (response.success) { currentActionId = null; resetEditorPanel(); loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter); } else { alert(response.data || mxchActionsData.i18n.error); } }, error: function() { hideLoading(); alert(mxchActionsData.i18n.error); } }); } // ========================================================================== // Utility Functions // ========================================================================== function showLoading() { $('#mxch-action-loading').show(); } function hideLoading() { $('#mxch-action-loading').hide(); } function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.appendChild(document.createTextNode(text)); return div.innerHTML; } });