File: /storage/v4513/tepnot/public_html/wp-content/plugins/echo-knowledge-base/js/frontend-editor.js
jQuery( document ).ready( function( $ ) {
let frontendEditor = $( '#epkb-fe__editor' );
/*
* Move #epkb-fe__editor directly under <body> as the last element.
* This ensures the editor always displays on top of the page.
* If it stays inside other containers, those containers can block it
* and cause parts of the editor to be hidden behind other elements.
*/
if ( frontendEditor.length && document.body.contains( frontendEditor[0] ) ) {
document.body.appendChild( frontendEditor[0]); // now last child of <body>
}
// show the frontend editor if one of KB pages is found in the page
if ( frontendEditor.length > 0 ) {
if ( frontendEditor.data( 'display-frontend-editor-closed' ) ) {
$('#epkb-fe__toggle').show();
} else {
open_frontend_editor(null );
}
}
// Clean up wpautop-injected tags (Elementor compatibility)
cleanupWpautopTags();
// Check for setup wizard parameter - auto-open editor and highlight template section (shortcode pages only)
const urlParams = new URLSearchParams( window.location.search );
if ( urlParams.has( 'epkb_from_setup_wizard' ) && frontendEditor.length > 0 ) {
// Open the Frontend Editor
open_frontend_editor( null );
// For shortcode pages, highlight the template section; for block pages, just open the FE
let isBlockPage = frontendEditor.find( '.epkb-fe__feature-select-button[data-feature="block-main-page-settings"]' ).length > 0;
if ( ! isBlockPage ) {
setTimeout( function() {
// Open the main page settings feature
frontendEditor.find( '.epkb-fe__feature-select-button[data-feature="main-page-settings"]' ).trigger( 'click' );
// Highlight the theme-compatibility-mode section
setTimeout( function() {
let $targetSection = frontendEditor.find( '.epkb-fe__settings-section--theme-compatibility-mode' );
if ( $targetSection.length > 0 ) {
$targetSection.addClass( 'epkb-highlighted_setting' );
// Add explanatory message at the top of the section body
let $sectionBody = $targetSection.find( '.epkb-fe__settings-section-body' );
if ( $sectionBody.length > 0 && ! $sectionBody.find( '.epkb-fe__highlight-message' ).length ) {
$sectionBody.prepend(
'<div class="epkb-fe__highlight-message">' +
'<span class="epkbfa epkbfa-lightbulb-o"></span> ' +
'Try both templates to see which one looks better for your website.' +
'</div>'
);
}
let offset = $targetSection.offset().top - frontendEditor.offset().top - 100;
frontendEditor.animate( { scrollTop: Math.max( 0, offset ) }, 300 );
}
}, 300 );
}, 500 );
}
}
// Handle FE Open Button and Admin bar FE edit link
$( document ).on( 'click', '.epkb-fe__toggle, #wp-admin-bar-epkb-edit-mode-button', function ( e ) {
// Don't open editor if close button was clicked
if ( $(e.target).closest('.epkb-fe__toggle-close').length > 0 ) {
return;
}
const params = new URLSearchParams( window.location.search );
const bodyClasses = document.body.classList;
const builders = {
visualComposer : (
params.get( 'vcv-editable' ) === '1' ||
bodyClasses.contains( 'vcwb-editor-body' )
),
divi : bodyClasses.contains( 'et-fb-root-ancestor' ),
siteOrigin : bodyClasses.contains( 'siteorigin-panels-live-editor' ),
pageLayer : bodyClasses.contains( 'pagelayer-normalize' ),
beaver : bodyClasses.contains( 'fl-builder-edit' ),
elementor : bodyClasses.contains( 'elementor-editor-active' ),
brizy : bodyClasses.contains( 'brz' ),
kubio : bodyClasses.contains( 'kubio-iframe-holder--show' ),
colibri : (
bodyClasses.contains( 'colibri-in-customizer' ) ||
bodyClasses.contains( 'customize-partial-edit-shortcuts-shown' ) ||
bodyClasses.contains( 'colibri-in-customizer--loaded' )
)
};
const activeKey = Object.keys( builders ).find( key => builders[ key ] );
if ( activeKey ) {
e.preventDefault();
const builderNames = {
visualComposer : 'Visual Composer',
divi : 'Divi Builder',
siteOrigin : 'SiteOrigin Builder',
pageLayer : 'Pagelayer Builder',
beaver : 'Beaver Builder',
elementor : 'Elementor Builder',
brizy : 'Brizy Builder',
kubio : 'Kubio Builder',
colibri : 'Colibri Page Builder'
};
const builderName = builderNames[ activeKey ] || 'Page Builder';
const message = wp.i18n.sprintf(
/* translators: %s: Name of the active page builder */
wp.i18n.__( 'The Knowledge Base Editor is disabled while the %s is active. Do you want to go to the Knowledge Base settings instead?', 'echo-knowledge-base' ),
builderName
);
// Show confirmation dialog
const goToSettings = window.confirm( message );
if ( goToSettings ) {
// Get admin URL from localized script variables
const adminBase = epkb_fe_vars?.admin_url;
if ( adminBase ) {
const adminUrl = adminBase + 'edit.php?post_type=epkb_post_type_1&page=epkb-kb-configuration#settings';
window.open( adminUrl, '_blank' );
}
}
return;
}
// If no builders active, continue to open frontend editor
open_frontend_editor( e );
} );
function open_frontend_editor( e ) {
if ( e ) {
e.preventDefault(); // Prevent the default link behavior
}
frontendEditor.css( 'right', '0' );
frontendEditor.show();
$( '.epkb-fe__toggle' ).hide();
$( '#epkb-fe__editor .epkb-fe__feature-settings' ).each(function () {
const $featureSettings = $( this );
const rowNumber = $featureSettings.attr( 'data-row-number' );
const isNonRowFeature = $featureSettings.data( 'non-row-feature' );
if ( rowNumber === 'none' && ! isNonRowFeature ) {
$featureSettings.find( '.epkb-fe__settings-section:not(.epkb-fe__settings-section--module-position)' ).addClass( 'epkb-fe__settings-section--hide' );
} else {
$featureSettings.find( '.epkb-fe__settings-section:not(.epkb-fe__settings-section--module-position)' ).removeClass( 'epkb-fe__settings-section--hide' );
}
} );
}
// Close FE
$( document ).on( 'click', '.epkb-fe__header-close-button', function() {
frontendEditor.hide();
$( '.epkb-fe__toggle' ).show();
$( '#epkb-fe__action-back' ).trigger( 'click' );
// first time user closed the FE, do not save the state
if ( frontendEditor.data( 'display-frontend-editor-closed' ) ) {
return;
}
$.ajax( {
url: epkb_vars.ajaxurl,
method: 'POST',
data: {
action: 'eckb_closed_fe_editor',
_wpnonce_epkb_ajax_action: epkb_vars.nonce,
}
} );
} );
// Close FE Toggle Button - Hide permanently
$( document ).on( 'click', '.epkb-fe__toggle-close', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
// Hide the toggle button
$( '.epkb-fe__toggle' ).hide();
// Save the setting to hide the button permanently
$.ajax( {
url: epkb_vars.ajaxurl,
method: 'POST',
data: {
action: 'eckb_hide_fe_toggle_button',
kb_id: frontendEditor.data( 'kbid' ),
_wpnonce_epkb_ajax_action: epkb_vars.nonce,
}
} );
return false;
} );
// Show settings for a feature
$( document ).on( 'click', '.epkb-fe__feature-select-button', function() {
let $button = $( this );
let feature_name = $button.data( 'feature' );
let $tab = $( '.epkb-fe__feature-settings[data-feature="' + feature_name + '"]' );
$( '.epkb-fe__header-title' ).hide();
$( '.epkb-fe__header-title[data-title="' + feature_name + '"]' ).addClass( 'epkb-fe__header-title--active' );
// set epkb-fe__editor--settings and remove rest of classes
/*frontendEditor.removeClass(function(index, className) {
return (className.match(/\bepkb-fe__editor--\S+/g) || []).join(' ');
});*/
frontendEditor.addClass( 'epkb-fe__editor--settings' ).removeClass( 'epkb-fe__editor--home epkb-fe__editor--help' );
$( '.epkb-fe__feature-select-button' ).hide();
$( '.epkb-fe__feature-settings' ).removeClass( 'epkb-fe__feature-settings--active' );
$tab.addClass( 'epkb-fe__feature-settings--active' );
// Restore all sections to their default open state
$tab.find( '.epkb-fe__settings-section' ).each( function() {
let $section = $( this );
if ( ! $section.hasClass( 'epkb-fe__is_opened' ) ) {
$section.addClass( 'epkb-fe__is_opened' );
$section.find( '.epkb-fe__settings-section-body' ).show();
}
} );
// Refresh conditional settings
$tab.find( '.eckb-conditional-setting-input' ).trigger( 'click' );
// Ensure custom dropdowns in the newly shown tab reflect the current select values
$tab.find('.epkb-input-custom-dropdown select').each(function() {
update_custom_dropdown_display($(this));
});
$( '#epkb-fe__editor .epkb-fe__actions' ).show();
$( '.epkb-fe__top-actions' ).hide();
} );
// Back button to hide settings for the feature and show features list
$( document ).on( 'click', '#epkb-fe__action-back', function() {
// set epkb-fe__editor--home and remove rest of classes
frontendEditor.removeClass(function(index, className) {
return (className.match(/\bepkb-fe__editor--\S+/g) || []).join(' ');
});
$( '.epkb-fe__header-title' ).removeClass( 'epkb-fe__header-title--active' );
frontendEditor.addClass( 'epkb-fe__editor--home' );
// Show module icons
$( '.epkb-fe__feature-select-button' ).show();
// Hide all module settings
$( '.epkb-fe__feature-settings' ).removeClass( 'epkb-fe__feature-settings--active' );
// Hide action buttons
$( '#epkb-fe__editor .epkb-fe__actions' ).hide();
// Show 'close' button
$( '.epkb-fe__top-actions' ).show();
} );
// Expand / Collapse settings section
$( document ).on( 'click', '.epkb-fe__settings-section-header', function() {
let $section = $( this ).parent();
let $sectionBody = $section.find( '.epkb-fe__settings-section-body' );
if ( $section.hasClass( 'epkb-fe__is_opened' ) ) {
$sectionBody.stop().slideUp();
$section.removeClass( 'epkb-fe__is_opened' );
} else {
$sectionBody.stop().slideDown();
$section.addClass( 'epkb-fe__is_opened' );
}
} );
// Switch Settings boxes which belong to certain feature
// Add the following CSS classes in PHP config to necessary Settings boxes:
// - epkb-fe__settings-section--module-box
// - epkb-fe__settings-section--{module name}-box
// - epkb-fe__settings-section--hide
// Add 'data' => [ 'insert-box-after' => {selector} ] in PHP config to insert the box after certain Settings box
// Adapted from admin-plugin-pages.js - has similar functionality
function switch_module_boxes( module_selector ) {
let current_module_name = $( module_selector ).val();
// Hide other modules Settings boxes in the current tab
let other_modules_boxes = $( module_selector ).closest( '.epkb-fe__feature-settings' ).find( '.epkb-fe__settings-section--module-box:not(.epkb-fe__settings-section--' + current_module_name + '-box)' );
other_modules_boxes.addClass( 'epkb-fe__settings-section--hide' );
// Find all Settings boxes which belong to the currently selected module
let module_boxes = $( module_selector ).closest( '.epkb-fe__features-list' ).find( '.epkb-fe__settings-section--' + current_module_name + '-box' );
if ( ! module_boxes.length ) {
return;
}
$( module_boxes.get().reverse() ).each( function () {
$( this ).removeClass( 'epkb-fe__settings-section--hide' );
// Show Settings boxes which belong to the currently selected module
let insert_box_after = $( this ).data( 'insert-box-after' );
$( module_selector ).closest( '.epkb-fe__feature-settings .epkb-fe__settings-list' ).find( insert_box_after ).after( this );
} );
// Insure the selected Layout is shown as active - fix for Grid or Sidebar Layout selection with Elegant Layouts disabled
if ( current_module_name === 'categories_articles' ) {
$( '[name="kb_main_page_layout"]:checked' ).trigger( 'click' );
}
}
// Initialize Layout box settings
// Adapted from admin-plugin-pages.js - has similar functionality
$( '[data-settings-group="ml-row"].epkb-row-module-setting select' ).each( function() {
switch_module_boxes( this );
} );
// Helper function to update the custom dropdown's display
function update_custom_dropdown_display($select) {
const $inputGroup = $select.closest('.epkb-input-custom-dropdown');
if (!$inputGroup.length) return;
const $optionsList = $inputGroup.find('.epkb-input-custom-dropdown__options-list');
const newValue = $select.val();
const newText = $select.find('option:selected').text();
$inputGroup.find('.epkb-input-custom-dropdown__input span').text(newText);
$optionsList.find('.epkb-input-custom-dropdown__option').removeClass('epkb-input-custom-dropdown__option--selected');
$optionsList.find('.epkb-input-custom-dropdown__option[data-value="' + newValue + '"]').addClass('epkb-input-custom-dropdown__option--selected');
}
/*************************************************************************************************
*
* FE Settings - Preview Changes / Save Changes
*
************************************************************************************************/
let ignore_setting_update_flag = false;
let current_layout_name = frontendEditor.find( '[name="kb_main_page_layout"]:checked' ).val();
// Design preset may change settings which are not present in the FE UI - apply full design settings + FE UI settings until user saved settings
let selected_search_preset = 'current';
let selected_categories_preset = 'current';
// Update page with a new preview - call backend
function updatePreview( event, ui ) {
if ( ignore_setting_update_flag ) {
return;
}
const $feature_settings_container = $( event.target ).closest( '.epkb-fe__feature-settings' );
const feature_name = $feature_settings_container.data( 'feature' );
const kb_page_type = $feature_settings_container.data( 'kb-page-type' );
const post_id = frontendEditor.data( 'post-id' );
const setting_name = $(event.target).attr('name');
// Get the actual current position from the DOM for this feature
const $activeRow = $('.epkb-ml__row[data-feature="' + feature_name + '"]');
const actualPosition = $activeRow.length > 0 ? $activeRow.attr('data-position') : 'none';
if ( $( event.target ).hasClass( 'wp-color-picker' ) && ui && ui.color ) {
$( event.target ).closest( '.wp-picker-container' ).find( '.wp-color-result' ).css( 'background-color', ui.color.toString() );
$( event.target ).val( ui.color.toString() );
}
let kb_config = collectConfig();
kb_config[feature_name + '_module_position'] = actualPosition;
if ( feature_name === 'categories_articles' && setting_name === 'categories_articles_preset' ) {
selected_categories_preset = kb_config.categories_articles_preset && kb_config.categories_articles_preset !== 'current'
? kb_config.categories_articles_preset
: 'current';
}
// Get taxonomy and term information for archive pages
let taxonomy = '';
let term_id = 0;
if ( kb_page_type === 'archive-page' ) {
// Try to get taxonomy from body class or data attributes
const $body = $('body');
const bodyClasses = $body.attr('class');
// Look for taxonomy in body classes (e.g., 'tax-epkb_post_type_1_category')
const taxMatch = bodyClasses ? bodyClasses.match(/tax-(epkb_post_type_\d+_[\w]+)/) : null;
if (taxMatch) {
taxonomy = taxMatch[1];
}
// Try to get term ID from body classes (e.g., 'term-123')
const termMatch = bodyClasses ? bodyClasses.match(/term-(\d+)/) : null;
if (termMatch) {
term_id = parseInt(termMatch[1]);
}
}
// Show loading dialog based on page type
if ( kb_page_type === 'main-page' ) {
epkb_loading_Dialog( 'show', '', $( '#epkb-modular-main-page-container, #eckb-kb-template' ) );
let $search_preset_input = $feature_settings_container.find( '[name="advanced_search_mp_presets"]:checked' );
if ( $search_preset_input.length > 0 && $search_preset_input.val() !== 'current' ) {
selected_search_preset = $search_preset_input.val();
}
} else if ( kb_page_type === 'article-page' ) {
// For article page, always show loading on the main article container
epkb_loading_Dialog( 'show', '', $( '#eckb-article-page-container-v2' ) );
let $search_preset_input = $feature_settings_container.find( '[name="advanced_search_ap_presets"]:checked' );
if ( $search_preset_input.length > 0 && $search_preset_input.val() !== 'current' ) {
selected_search_preset = $search_preset_input.val();
}
} else if ( kb_page_type === 'archive-page' ) {
epkb_loading_Dialog( 'show', '', $( '#eckb-archive-page-container' ) );
}
// Apply changes without saving (for preview purpose)
$.ajax( {
url: epkb_vars.ajaxurl,
method: 'POST',
data: {
action: 'eckb_apply_fe_settings',
_wpnonce_epkb_ajax_action: epkb_vars.nonce,
kb_id: frontendEditor.data( 'kbid' ),
new_kb_config: kb_config,
kb_page_type: kb_page_type,
is_article_page: kb_page_type === 'article-page' ? 1 : 0,
feature_name: feature_name,
setting_name: setting_name,
prev_link_css_id: $( '[id^="epkb-mp-frontend-modular-"][id$="-layout-css"]' ).attr( 'id' ),
settings_row_number: actualPosition ? actualPosition : 'none',
taxonomy: taxonomy,
term_id: term_id,
kb_post_id: post_id,
layout_name: current_layout_name,
selected_search_preset: selected_search_preset,
selected_categories_preset: selected_categories_preset
},
success: function( response ) {
// Handle generic KB error (caught by KB)
if ( ! response.success ) {
try {
let responseJson = JSON.parse( response );
if ( responseJson.message ) {
$( 'body' ).append( $( responseJson.message ) );
}
} catch ( e ) {}
return;
}
// Main Page: Handle successful AJAX response
// Ensure we do not trigger extra updates during control re-initialization
ignore_setting_update_flag = true;
if ( kb_page_type === 'main-page' ) {
update_main_page_css( response );
// Update layout module settings if required (on layout change)
if ( response.data.layout_settings_html && response.data.layout_settings_html.length > 0 ) {
// Update current layout value fter layout switch (is needed to properly adjust settings on server side for layout change)
current_layout_name = frontendEditor.find( '[name="kb_main_page_layout"]:checked' ).val();
// Update articles list container ID to match the new layout (so inline styles apply correctly)
let $articles_list_container = $( '.epkb-ml-article-list-container' );
if ( $articles_list_container.length > 0 ) {
$articles_list_container.attr( 'id', 'epkb-ml-article-list-' + current_layout_name.toLowerCase() + '-layout' );
}
// Update module settings HTML
let $settings_list = $( '.epkb-fe__feature-settings[data-feature="' + feature_name + '"] .epkb-fe__settings-list' );
$settings_list.html( response.data.layout_settings_html );
// If the feature is located in the first row, then it already contains all settings boxes (the first row is used as a storage for all optional settings boxes - Settings UI inherited functionality)
if ( parseInt( actualPosition ) === 1 ) {
// Find all Settings boxes which belong to the currently selected module
let module_boxes = $settings_list.find( '.epkb-fe__settings-section--' + feature_name + '-box' );
if ( module_boxes.length > 0 ) {
$( module_boxes.get() ).each( function () {
$( this ).removeClass( 'epkb-fe__settings-section--hide' );
} );
}
}
// If the feature is located in non-first row, then retrieve its optional settings boxes from temporary storage
else {
// Create temporary container for optional settings to initialize (required by inherited logic from Settings UI)
let $temporary_layout_change_settings = $( '<div id="epkb-fe__layout-change-settings" style="display: none !important;">' + response.data.layout_settings_html_temp + '</div>' );
// Find all Settings boxes which belong to the currently selected module
let module_boxes = $temporary_layout_change_settings.find( '.epkb-fe__settings-section--' + feature_name + '-box' );
if ( module_boxes.length > 0 ) {
$( module_boxes.get().reverse() ).each( function () {
$( this ).removeClass( 'epkb-fe__settings-section--hide' );
// Show Settings boxes which belong to the currently selected module
let insert_box_after = $( this ).data( 'insert-box-after' );
$settings_list.find( insert_box_after ).after( this );
} );
}
}
// Re-apply current settings for the dropdown controls of the module
$( '#epkb-fe__editor .epkb-fe__feature-settings[data-feature="' + feature_name + '"] .epkb-fe__settings-list .epkb-input-custom-dropdown select' ).trigger( 'change' );
// Re-init radio-buttons
$( '#epkb-fe__editor .epkb-fe__feature-settings .epkb-fe__settings-list input[type="radio"][checked]' ).prop( 'checked', true );
prepare_color_picker( feature_name );
// Re-apply current settings for the buttons, radio buttons, and other controls of the module
$( '#epkb-fe__editor .epkb-fe__feature-settings[data-feature="' + feature_name + '"] .epkb-fe__settings-list .eckb-conditional-setting-input' ).trigger( 'click' );
// Enable the feature in the refreshed settings
$( '#epkb-fe__editor .epkb-settings-control__input__toggle[name="' + feature_name + '"]' ).trigger( 'click', true );
}
// Update FAQs module settings (after applying design preset)
if ( response.data.faqs_design_settings && response.data.faqs_design_settings.length > 0 ) {
// Unselect preset name to prevent continuing preset applying on further settings changes
$feature_settings_container.find( '[name="faq_preset_name"]' ).prop( 'checked', false );
// Apply preset settings for UI controls
for ( const [ key, value ] of Object.entries( response.data.faqs_design_settings ) ) {
const $target_field = $feature_settings_container.find( '[name="' + key + '"]' );
apply_preset_setting( $feature_settings_container, $target_field, key, value );
}
// Re-apply current settings for the buttons, radio buttons, and other controls of the module
$( '#epkb-fe__editor .epkb-fe__feature-settings[data-feature="' + feature_name + '"] .epkb-fe__settings-list .eckb-conditional-setting-input' ).trigger( 'click' );
}
// Update Categories & Articles module settings (after applying design preset)
if ( response.data.categories_articles_design_settings && Object.keys(response.data.categories_articles_design_settings).length > 0 ) {
if ( kb_config.categories_articles_preset && kb_config.categories_articles_preset !== 'current' ) {
selected_categories_preset = kb_config.categories_articles_preset;
}
// Unselect preset name to prevent continuing preset applying on further settings changes
$feature_settings_container.find( '[name="categories_articles_preset"]' ).val( 'current' ).trigger( 'change' );
// Apply preset settings for UI controls
for ( const [ key, value ] of Object.entries( response.data.categories_articles_design_settings ) ) {
const $target_field = $feature_settings_container.find( '[name="' + key + '"]' );
apply_preset_setting( $feature_settings_container, $target_field, key, value );
}
// Re-apply current settings for the buttons, radio buttons, and other controls of the module
$( '#epkb-fe__editor .epkb-fe__settings-list .eckb-conditional-setting-input' ).trigger( 'click' );
}
// Update search settings (after applying design preset)
if ( response.data.search_design_settings && $feature_settings_container.find( '[name="advanced_search_mp_presets"]' ).length > 0 ) {
// Apply preset settings for UI controls
for ( const [ key, value ] of Object.entries( response.data.search_design_settings ) ) {
const $target_field = $feature_settings_container.find( '[name="' + key + '"]' );
apply_preset_setting( $feature_settings_container, $target_field, key, value );
}
// Unselect preset name to prevent continuing preset applying on further settings changes
$feature_settings_container.find( '[name="advanced_search_mp_presets"][value="current"]' ).prop( 'checked', true );
// Re-apply current settings for the buttons, radio buttons, and other controls of the module
$( '#epkb-fe__editor .epkb-fe__settings-list .eckb-conditional-setting-input' ).trigger( 'click' );
}
// Inline styles - changed every time a module setting was changed
if ( response.data.inline_styles && response.data.inline_styles.length > 0 ) {
$( '[id^="epkb-mp-frontend-modular-"][id$="-layout-inline-css"]' ).html( response.data.inline_styles );
}
// Update HTML of the target module - changed every time a module setting was changed
if ( response.data.preview_html && response.data.preview_html.length > 0 ) {
// Find the row by feature name, not by row number
let $destination_row = $( '.epkb-ml__row[data-feature="' + feature_name + '"]' );
// If the row exists (module is enabled), update its content
if ( $destination_row.length > 0 ) {
$destination_row.html( response.data.preview_html );
}
// Note: If the row doesn't exist, we don't create it here because
// the module is disabled and shouldn't have content displayed
}
// Custom inline styles - always rendered separately to avoid possible impact of internal KB inline styles on user's mistake
if ( response.data.custom_inline_styles && response.data.custom_inline_styles.length > 0 ) {
// epkb-mp-frontend-modular-category-layout-custom-inline-css
let $custom_inline_styles_tag = $( '[id^="epkb-"][id$="-custom-inline-css"]' );
const $new_custom_inline_styles_tag = $( '<style id="epkb--custom-inline-css">' + response.data.custom_inline_styles + '</style>');
if ( $custom_inline_styles_tag.length > 0 ) {
$custom_inline_styles_tag.replaceWith( $new_custom_inline_styles_tag );
} else {
$new_custom_inline_styles_tag.insertAfter( '[id^="epkb-"][id$="-inline-css"]' );
}
}
// Ensure public JS which is dependent on HTML initialization is re-initialized
setTimeout( function() {
$( window ).trigger( 'resize' );
}, 100 );
}
// Article Page
if ( kb_page_type === 'article-page' ) {
// Update search settings (after applying design preset or sync with Main Page search)
if ( response.data.search_design_settings && $feature_settings_container.find( '[name="advanced_search_ap_presets"]' ).length > 0 ) {
// Apply preset settings for UI controls
for ( const [ key, value ] of Object.entries( response.data.search_design_settings ) ) {
const $target_field = $feature_settings_container.find( '[name="' + key + '"]' );
apply_preset_setting( $feature_settings_container, $target_field, key, value );
}
// Unselect preset name to prevent continuing preset applying on further settings changes
$feature_settings_container.find( '[name="advanced_search_ap_presets"][value="current"]' ).prop( 'checked', true );
// Re-apply current settings for the buttons, radio buttons, and other controls of the module
$( '#epkb-fe__editor .epkb-fe__settings-list .eckb-conditional-setting-input' ).trigger( 'click' );
}
// Update HTML of the entire Article content or specific sections
if ( response.data.preview_html && response.data.preview_html.length > 0 ) {
const $newContent = $( response.data.preview_html );
const $templateData = $newContent.filter('#eckb-template-update-data');
// Update template wrapper if data is present
if ( $templateData.length ) {
const data = JSON.parse( $templateData.text() );
const $kbTemplate = $( '.eckb-kb-template' );
if ( data.classes ) {
$kbTemplate.removeClass( 'eckb-article-resets eckb-article-defaults' );
$kbTemplate.addClass( data.classes );
}
if ( data.style ) {
$kbTemplate.attr( 'style', data.style );
}
}
// Update article content
$( '[id^="eckb-article-page-container"]' ).parent().html( response.data.preview_html );
}
// Inline styles - changed every time a module setting was changed
if ( response.data.inline_styles && response.data.inline_styles.length > 0 ) {
let $inlineStyles = $( '#epkb-ap-frontend-layout-inline-css' );
if ( $inlineStyles.length ) {
$inlineStyles.html( response.data.inline_styles );
} else {
// Create the inline styles element if it doesn't exist
$( '<style id="epkb-ap-frontend-layout-inline-css">' + response.data.inline_styles + '</style>' ).appendTo( 'head' );
}
}
// Ensure public JS which is dependent on HTML initialization is re-initialized
setTimeout( function() {
$( window ).trigger( 'resize' );
// Re-initialize any article-specific JavaScript functionality
if ( typeof epkb_init_article_toc === 'function' ) {
epkb_init_article_toc();
}
// Re-initialize advanced search if it was updated
if ( feature_name === 'article-page-search-box' && typeof epkb_init_advanced_search === 'function' ) {
epkb_init_advanced_search();
}
}, 100 );
}
// Archive Page
if ( kb_page_type === 'archive-page' ) {
// Update design settings (after applying design preset)
if ( response.data.archive_design_settings && response.data.archive_design_settings.length > 0 ) {
// Unselect preset name to prevent continuing preset applying on further settings changes
$feature_settings_container.find( '[name="archive_content_sub_categories_display_mode"]' ).prop( 'checked', false );
// Apply preset settings for UI controls
for ( const [ key, value ] of Object.entries( response.data.archive_design_settings ) ) {
const $target_field = $feature_settings_container.find( '[name="' + key + '"]' );
apply_preset_setting( $feature_settings_container, $target_field, key, value );
}
// Re-apply current settings for the buttons, radio buttons, and other controls of the module
$( '#epkb-fe__editor .epkb-fe__feature-settings[data-feature="' + feature_name + '"] .epkb-fe__settings-list .eckb-conditional-setting-input' ).trigger( 'click' );
}
// Update HTML of the entire Archive content
if ( response.data.preview_html && response.data.preview_html.length > 0 ) {
$( '#eckb-archive-page-container' ).replaceWith( response.data.preview_html );
}
// Inline styles - changed every time a module setting was changed
if ( response.data.inline_styles && response.data.inline_styles.length > 0 ) {
let $inlineStyles = $( '#epkb-cp-frontend-layout-inline-css' );
if ( $inlineStyles.length ) {
$inlineStyles.html( response.data.inline_styles );
} else {
// Create the inline styles element if it doesn't exist
$( '<style id="epkb-cp-frontend-layout-inline-css">' + response.data.inline_styles + '</style>' ).appendTo( 'head' );
}
}
// Ensure public JS which is dependent on HTML initialization is re-initialized
setTimeout( function() {
$( window ).trigger( 'resize' );
}, 100 );
}
// Allow to handle user changes for settings
ignore_setting_update_flag = false;
},
complete: function() {
// Remove loading dialog based on page type
if ( kb_page_type === 'main-page' ) {
epkb_loading_Dialog( 'remove', '', $( '#epkb-modular-main-page-container, #eckb-kb-template' ) );
} else if ( kb_page_type === 'article-page' ) {
// Remove loading from the main article container
epkb_loading_Dialog( 'remove', '', $( '#eckb-article-page-container-v2' ) );
} else if ( kb_page_type === 'archive-page' ) {
epkb_loading_Dialog( 'remove', '', $( '#eckb-archive-page-container' ) );
}
},
error: function( jqXHR, textStatus, errorThrown ) {
// Log error to console
console.error( 'Frontend Editor Preview Error:', {
status: jqXHR.status,
statusText: jqXHR.statusText,
textStatus: textStatus,
errorThrown: errorThrown,
response: jqXHR.responseJSON || jqXHR.responseText
} );
// Remove loading dialog based on page type
if ( kb_page_type === 'main-page' ) {
epkb_loading_Dialog( 'remove', '', $( '#epkb-modular-main-page-container, #eckb-kb-template' ) );
} else if ( kb_page_type === 'article-page' ) {
// Remove loading from the main article container
epkb_loading_Dialog( 'remove', '', $( '#eckb-article-page-container-v2' ) );
} else if ( kb_page_type === 'archive-page' ) {
epkb_loading_Dialog( 'remove', '', $( '#eckb-archive-page-container' ) );
}
},
} );
}
function apply_preset_setting( $feature_settings_container, $target_setting_field, key, value ) {
// Fallback to the same page type container if the field is outside the current feature (e.g. theme presets updating Search)
if ( $target_setting_field.length === 0 ) {
const kb_page_type = $feature_settings_container.data( 'kb-page-type' );
const $page_scope = kb_page_type ? frontendEditor.find( '.epkb-fe__feature-settings[data-kb-page-type="' + kb_page_type + '"]' ) : frontendEditor;
$target_setting_field = $page_scope.find( '[name="' + key + '"]' );
if ( $target_setting_field.length === 0 ) {
return;
}
}
const $target_container = $target_setting_field.closest( '.epkb-fe__feature-settings' ).length ? $target_setting_field.closest( '.epkb-fe__feature-settings' ) : $feature_settings_container;
// Radio buttons
if ( $target_setting_field.attr( 'type' ) === 'radio' ) {
$target_container.find( '[name="' + key + '"][value="' + value + '"]' ).prop( 'checked', true );
// Checkbox
} else if ( $target_setting_field.attr( 'type' ) === 'checkbox' ) {
$target_container.find( '[name="' + key + '"]' ).prop( 'checked', value === 'on' );
// Color-picker
} else if ( $target_setting_field.hasClass( 'wp-color-picker' ) ) {
$target_setting_field.closest( '.wp-picker-container' ).find( '.wp-color-result' ).css( 'background-color', value );
$target_setting_field.val( value );
// Other field types
} else {
$target_setting_field.val( value );
}
}
// Update page with reload while keep unsaved changes in FE settings
function update_preview_via_page_reload( event, ui ) {
if ( ignore_setting_update_flag ) {
return;
}
const $feature_settings_container = $( event.target ).closest( '.epkb-fe__feature-settings' );
const kb_page_type = $feature_settings_container.data( 'kb-page-type' );
if ( kb_page_type === 'main-page' ) {
epkb_loading_Dialog( 'show', '', $( '#epkb-modular-main-page-container, #eckb-kb-template' ) );
} else if ( kb_page_type === 'article-page' ) {
epkb_loading_Dialog( 'show', '', $( '#eckb-article-page-container-v2' ) );
} else if ( kb_page_type === 'archive-page' ) {
epkb_loading_Dialog( 'show', '', $( '#eckb-archive-page-container' ) );
}
if ( $( event.target ).hasClass( 'wp-color-picker' ) && ui && ui.color ) {
$( event.target ).closest( '.wp-picker-container' ).find( '.wp-color-result' ).css( 'background-color', ui.color.toString() );
$( event.target ).val( ui.color.toString() );
}
const kb_config = collectConfig();
const config_json = JSON.stringify( kb_config );
const feature_name = $( '#epkb-fe__editor .epkb-fe__feature-settings--active' ).attr( 'data-feature' );
// Set parameter to re-open currently active feature in the editor
const action_url = new URL( window.location.href );
action_url.searchParams.set( 'epkb_fe_reopen_feature', feature_name );
let $preview_form = $( '<form method="post" action="' + action_url + '" style="display: none !important;">' +
'<input type="hidden" name="epkb_fe_reload_mode" value="on">' +
'<input type="hidden" name="kb_id" value="' + frontendEditor.data( 'kbid' ) + '">' +
'<input type="hidden" name="is_article_page" value="' + (kb_page_type === 'article-page' ? 1 : 0) + '">' +
'<input type="text" name="new_kb_config" value="">' +
'</form>' );
$preview_form.find( '[name="new_kb_config"]' ).val( config_json );
$( 'body' ).append( $preview_form );
$preview_form.trigger( 'submit' );
}
// Save settings and reload the page - used when switching to Current Theme Template
function save_settings_and_reload_page( kb_page_type ) {
let loadingContainer;
if ( kb_page_type === 'article-page' ) {
loadingContainer = $( '#eckb-article-page-container-v2' );
} else if ( kb_page_type === 'archive-page' ) {
loadingContainer = $( '#eckb-archive-page-container' );
} else {
loadingContainer = $( '#epkb-modular-main-page-container, #eckb-kb-template' );
}
epkb_loading_Dialog( 'show', '', loadingContainer );
let kb_config = collectConfig();
let save_action = 'eckb_save_fe_settings';
if ( kb_page_type === 'article-page' ) {
save_action = 'eckb_save_fe_article_settings';
} else if ( kb_page_type === 'archive-page' ) {
save_action = 'eckb_save_fe_archive_settings';
}
const feature_name = $( '#epkb-fe__editor .epkb-fe__feature-settings--active' ).attr( 'data-feature' );
$.ajax( {
url: epkb_vars.ajaxurl,
method: 'POST',
data: {
action: save_action,
_wpnonce_epkb_ajax_action: epkb_vars.nonce,
kb_id: frontendEditor.data( 'kbid' ),
new_kb_config: kb_config,
is_article_page: kb_page_type === 'article-page' ? 1 : 0,
selected_search_preset: selected_search_preset,
selected_categories_preset: selected_categories_preset
},
success: function( response ) {
// Add URL parameter to reopen the Page feature in the editor after reload
const reload_url = new URL( window.location.href );
reload_url.searchParams.set( 'epkb_fe_reopen_feature', feature_name );
window.location.href = reload_url.toString();
},
error: function( jqXHR, textStatus, errorThrown ) {
console.error( 'Frontend Editor Save Settings Error:', errorThrown );
epkb_loading_Dialog( 'remove', '', loadingContainer );
}
} );
}
function update_main_page_css( response ) {
// Main CSS file - changed only on switching layout
if ( response.data.link_css && response.data.link_css.length > 0 ) {
let new_link_css_id = $( response.data.link_css ).attr( 'id' );
let $current_link_css = $( '[id^="epkb-mp-frontend-modular-"][id$="-layout-css"]' );
// Load the file only once
if ( new_link_css_id !== $current_link_css.attr( 'id' ) ) {
$current_link_css.replaceWith( response.data.link_css );
}
}
// RTL Main CSS file - changed only on switching layout
if ( response.data.link_css_rtl && response.data.link_css_rtl.length > 0 ) {
let new_link_css_rtl_id = $( response.data.link_css_rtl ).attr( 'id' );
let $current_link_css_rtl = $( '[id^="epkb-mp-frontend-modular-"][id$="-layout-rtl-css"]' );
// Load the file only once
if ( new_link_css_rtl_id !== $current_link_css_rtl.attr( 'id' ) ) {
$current_link_css_rtl.replaceWith( response.data.link_css_rtl );
}
}
// EL.AY Main CSS file - changed only on switching layout
if ( response.data.elay_link_css && response.data.elay_link_css.length > 0 ) {
let new_elay_link_css_id = $( response.data.elay_link_css ).attr( 'id' );
let $current_elay_link_css = $( '[id^="elay-mp-frontend-modular-"][id$="-layout-css"]' );
// EL.AY layout-specific CSS file is not present if the layout was not active during the page load
if ( $current_elay_link_css.length > 0 ) {
// Load the file only once
if ( new_elay_link_css_id !== $current_elay_link_css.attr( 'id' ) ) {
$current_elay_link_css.replaceWith( response.data.elay_link_css );
}
} else {
$current_elay_link_css.insertAfter( '#elay-public-modular-styles-css' );
}
if ( $current_elay_link_css.length ) {
$current_elay_link_css.replaceWith( response.data.elay_link_css );
} else {
let $current_link_css = $( '[id^="epkb-mp-frontend-modular-"][id$="-layout-css"]' );
$( response.data.elay_link_css ).insertAfter( $current_link_css );
}
}
// RTL Main CSS file
if ( response.data.elay_link_css_rtl && response.data.elay_link_css_rtl.length > 0 ) {
let new_elay_link_css_rtl_id = $( response.data.elay_link_css ).attr( 'id' );
let $current_elay_link_css_rtl = $( '[id^="elay-mp-frontend-modular-"][id$="-layout-rtl-css"]' );
// EL.AY layout-specific CSS file is not present if the layout was not active during the page load
if ( $current_elay_link_css_rtl.length > 0 ) {
// Load the file only once
if ( new_elay_link_css_rtl_id !== $current_elay_link_css_rtl.attr( 'id' ) ) {
$current_elay_link_css_rtl.replaceWith( response.data.elay_link_css_rtl );
}
} else {
$current_elay_link_css_rtl.insertAfter( '#elay-public-modular-styles-rtl-css' );
}
if ( $current_elay_link_css_rtl.length ) {
$current_elay_link_css_rtl.replaceWith( response.data.elay_link_css_rtl );
} else {
let $current_link_css = $( '[id^="epkb-mp-frontend-modular-"][id$="-layout-rtl-css"]' );
$( response.data.elay_link_css_rtl ).insertAfter( $current_link_css );
}
}
}
// Preview Update: on single setting change except colors
$( document ).on( 'change', '#epkb-fe__editor input, #epkb-fe__editor select, #epkb-fe__editor textarea', function( event, is_triggered_programmatically ) {
// Programmatically triggered 'change' event from Settings UI inherited functionality passes the additional argument to let other handlers know it was triggered by script
if ( is_triggered_programmatically ) {
return;
}
// do not update preview if we are updating settings based on previous user selection
if ( ignore_setting_update_flag ) {
return;
}
let $field = $( this );
const field_name = $field.attr( 'name' );
// some settings do not need to trigger AJAX update for preview
const noPreviewUpdateSettings = [ 'search_result_mode', 'search_box_results_style', 'article_search_box_results_style', 'article_search_result_mode', 'advanced_search_mp_show_top_category',
'advanced_search_ap_show_top_category', 'advanced_search_mp_results_list_size', 'advanced_search_ap_results_list_size', 'advanced_search_text_highlight_enabled',
'advanced_search_mp_results_page_size', 'advanced_search_ap_results_page_size', 'advanced_search_mp_box_results_style', 'advanced_search_ap_box_results_style', 'article_views_counter_method', 'back_navigation_mode', 'advanced_search_faqs_toggle'];
if ( noPreviewUpdateSettings.includes( field_name ) ) {
return;
}
// Map of toggle settings to their corresponding selectors for instant show/hide (sidebars cannot use this feature since the page layout needs to be adjusted for enabled/disabled sidebar)
const instantToggleSettings = {
// TODO FUTURE: does not give any advantage since it can have different selectors while all the selectors should be present to skip the AJAX update correctly
// 'article_search_toggle': ['#eckb-article-header .epkb-doc-search-container', '#eckb-article-header #asea-doc-search-container'],
// TODO FUTURE: does not give any advantage since is designed for handle new and old article at the same time, while all the selectors should be present to skip the AJAX update correctly
// 'print_button_enable': ['.eckb-article-content-toolbar-button-container', '.eckb-print-button-meta-container'],
// TODO FUTURE: it looks like the 'eckb-ach__article-meta__views_counter' is never printed on the page
// 'article_views_counter_enable': ['.eckb-article-content-article-views-counter-container', '.eckb-ach__article-meta__views_counter'],
// 'article_content_enable_views_counter': ['.eckb-article-content-article-views-counter-container', '.eckb-ach__article-meta__views_counter'],
// TODO FUTURE: has effect only when both top and bottom features enabled, otherwise reloads the preview via AJAX when toggle turned 'ON'
'article_content_enable_author': ['.eckb-article-content-author-container', '.eckb-ach__article-meta__author'],
'article_content_enable_created_date': ['.eckb-article-content-created-date-container', '.eckb-ach__article-meta__date-created'],
'article_content_enable_last_updated_date': ['.eckb-article-content-last-updated-date-container', '.eckb-ach__article-meta__date-updated'],
'article_content_enable_article_title': ['#eckb-article-content-title-container'],
'article_content_enable_back_navigation': ['#eckb-article-back-navigation-container'],
'breadcrumb_enable': ['#eckb-article-content-breadcrumb-container'],
'prev_next_navigation_enable': ['.epkb-article-navigation-container'],
'meta-data-footer-toggle': ['.eckb-article-content-footer__article-meta'],
};
// Special handling for instant toggle settings
if ( instantToggleSettings[field_name] ) {
const isToggleOn = $field.attr('type') === 'checkbox' ? $field.prop('checked') : $field.val() === 'on';
const selectors = instantToggleSettings[field_name];
let foundExisting = false;
// If toggling OFF, hide all matching elements and skip backend call
if ( ! isToggleOn ) {
selectors.forEach(selector => {
$(selector).hide();
} );
return;
}
// Check if any of the elements exist
for ( const one_selector_index in selectors ) {
const $element = $( selectors[one_selector_index] );
if ( $element.length > 0 ) {
foundExisting = true;
}
// If any of the selectors is missing, then consider all are missing
else {
foundExisting = false;
break;
}
}
// If toggling ON and elements exist, show them and skip backend call
if ( isToggleOn && foundExisting ) {
selectors.forEach( selector => {
$( selector ).show();
} );
return;
}
// If toggling OFF and elements do not exist, skip backend call
else if ( ! isToggleOn && ! foundExisting ) {
return;
}
// If toggling ON but elements don't exist, continue to backend call
// Don't return here - let it fall through to updatePreview
}
// Special handling for TOC fields synchronization
if ( field_name === 'toc_toggler' ||
field_name === 'toc_left' ||
field_name === 'toc_right' ) {
ignore_setting_update_flag = true;
// Handle TOC toggler change
if ( field_name === 'toc_toggler' ) {
const isChecked = $field.prop( 'checked' );
if ( isChecked ) {
// Case 1: Try to set to first available position on Left Sidebar
if ( set_toc_to_article_sidebar_position( 'left' ) ) {
$( '#epkb-fe__editor input[name="toc_locations"][value="toc_left"]' ).prop( 'checked', true );
$( '#epkb-fe__editor input[name="toc_locations"][value="toc_content"], #epkb-fe__editor input[name="toc_locations"][value="toc_right"]' ).prop( 'checked', false );
}
// Case 2: If all positions of Left Sidebar have components, try Right Sidebar
else if ( set_toc_to_article_sidebar_position( 'right' ) ) {
$( '#epkb-fe__editor input[name="toc_locations"][value="toc_right"]' ).prop( 'checked', true );
$( '#epkb-fe__editor input[name="toc_locations"][value="toc_left"], #epkb-fe__editor input[name="toc_locations"][value="toc_content"]' ).prop( 'checked', false );
}
// Case 3: If all positions of both Sidebars have components, set to Content
else {
$( '#epkb-fe__editor #toc_content' ).val( '1' ).trigger( 'change' );
update_custom_dropdown_display( $( '#epkb-fe__editor #toc_content' ) );
$( '#epkb-fe__editor input[name="toc_locations"][value="toc_content"]' ).prop( 'checked', true );
$( '#epkb-fe__editor input[name="toc_locations"][value="toc_left"], #epkb-fe__editor input[name="toc_locations"][value="toc_right"]' ).prop( 'checked', false );
}
} else {
// Unselect all locations
$( '#epkb-fe__editor #toc_content' ).val( '0' ).trigger( 'change' );
update_custom_dropdown_display( $( '#epkb-fe__editor #toc_content' ) );
$( '#epkb-fe__editor input[name="toc_locations"]' ).prop( 'checked', false );
unselect_toc_in_article_sidebar_positions();
}
}
// Handle TOC location change (radio buttons)
else if ( field_name === 'toc_locations' && $field.prop( 'checked' ) ) {
const location = $field.val();
let is_toc_set = false;
switch ( location ) {
case 'toc_left':
unselect_toc_in_article_sidebar_positions();
$( '#epkb-fe__editor #toc_content' ).val( '0' ).trigger( 'change' );
update_custom_dropdown_display( $( '#epkb-fe__editor #toc_content' ) );
is_toc_set = set_toc_to_article_sidebar_position( 'left' );
break;
case 'toc_content':
unselect_toc_in_article_sidebar_positions();
$( '#epkb-fe__editor #toc_content' ).val( '1' ).trigger( 'change' );
update_custom_dropdown_display( $( '#epkb-fe__editor #toc_content' ) );
is_toc_set = true;
break;
case 'toc_right':
unselect_toc_in_article_sidebar_positions();
$( '#epkb-fe__editor #toc_content' ).val( '0' ).trigger( 'change' );
update_custom_dropdown_display( $( '#epkb-fe__editor #toc_content' ) );
is_toc_set = set_toc_to_article_sidebar_position( 'right' );
break;
}
// If failed to set TOC position, uncheck the location
if ( !is_toc_set ) {
$field.prop( 'checked', false );
}
// Uncheck other TOC location checkboxes (except the current one if successfully set)
$( '#epkb-fe__editor #toc_locations input' ).each( function() {
// Skip current Location input if it was successfully set
if ( $( this ).val() === location && is_toc_set ) {
return true;
}
// Unselect Location input
$( this ).prop( 'checked', false );
});
check_toc_toggler_state();
}
// Handle Article Sidebar Position dropdowns
else if ( field_name === 'toc_left' || field_name === 'toc_right' ) {
const sidebarSuffix = field_name === 'toc_left' ? 'left' : 'right';
const currentValue = parseInt( $field.val() );
if ( currentValue > 0 ) {
// TOC position selected
const locationValue = sidebarSuffix === 'left' ? 'toc_left' : 'toc_right';
$( '#epkb-fe__editor input[name="toc_locations"][value="' + locationValue + '"]' ).prop( 'checked', true );
$( '#epkb-fe__editor input[name="toc_locations"]' ).not( '[value="' + locationValue + '"]' ).prop( 'checked', false );
$( '#epkb-fe__editor #toc_content' ).val( '0' ).trigger( 'change' );
update_custom_dropdown_display( $( '#epkb-fe__editor #toc_content' ) );
// Unselect the other sidebar
const otherSidebar = sidebarSuffix === 'left' ? 'right' : 'left';
const $otherToc = $( '#epkb-fe__editor #toc_' + otherSidebar );
if ( parseInt( $otherToc.val() ) > 0 ) {
$otherToc.val( '0' ).trigger( 'change' );
update_custom_dropdown_display( $otherToc );
}
}
check_toc_toggler_state();
}
ignore_setting_update_flag = false;
// Continue to updatePreview
}
// Settings UI inherited logic - need to unselect conflicting dropdowns here before the config values are collected and passed to AJAX update
let current_unselection_group = $field.closest( '[data-custom-unselection-group]' ).data( 'custom-unselection-group' );
let current_nonzero_unselection_group = $field.closest( '[data-custom-nonzero-unselection-group]' ).data( 'custom-nonzero-unselection-group' );
if ( current_unselection_group || current_nonzero_unselection_group ) {
ignore_setting_update_flag = true;
// Handle change of value
// Unset current value in other dropdowns of the current unselection group
$( '[data-custom-unselection-group="' + current_unselection_group + '"] select' ).each( function() {
if ( field_name !== $( this ).attr( 'name' ) && $( this ).val() === $field.val() ) {
$( this ).val( 'none' ).trigger( 'change', true ); // trigger 'change' to have the updated appearance of select element in browser
$( this ).closest( '.eckb-conditional-setting-input' ).trigger( 'click' ); // trigger dependent fields
}
} );
// Unset other dropdowns of the current unselection group if any of them has non-zero value
$( '[data-custom-nonzero-unselection-group="' + current_nonzero_unselection_group + '"] select' ).each( function() {
if ( parseInt( $field.val() ) > 0 && field_name !== $( this ).attr( 'name' ) && parseInt( $( this ).val() ) > 0 ) {
$( this ).val( '0' ).trigger( 'change', true ); // trigger 'change' to have the updated appearance of select element in browser
$( this ).closest( '.eckb-conditional-setting-input' ).trigger( 'click' ); // trigger dependent fields
}
} );
ignore_setting_update_flag = false;
}
// Article Left Sidebar Toggle - when enabled, set Categories and Articles Navigation to "All Categories and Articles"
if ( field_name === 'article-left-sidebar-toggle' ) {
const is_enabled = $field.prop( 'checked' );
if ( is_enabled ) {
// Set the position to 1 if it's currently disabled (0)
const $nav_sidebar_left = $( '#nav_sidebar_left' );
if ( $nav_sidebar_left.length && $nav_sidebar_left.val() === '0' ) {
$nav_sidebar_left.val( '1' ).trigger( 'change' );
update_custom_dropdown_display( $nav_sidebar_left );
// Trigger conditional field visibility to show dependent settings (Sidebar Navigation)
$nav_sidebar_left.closest( '.eckb-conditional-setting-input' ).trigger( 'click' );
}
// Set Categories and Articles Navigation type to "All Categories and Articles"
const current_left_sidebar_type = $( '[name="article_nav_sidebar_type_left"]:checked' ).val();
if ( current_left_sidebar_type === 'eckb-nav-sidebar-none' ) {
$( '[name="article_nav_sidebar_type_left"][value="eckb-nav-sidebar-v1"]' ).parent().find( '.epkb-label' ).click();
}
}
}
// Article Right Sidebar Toggle - when enabled, set TOC to position 1
if ( field_name === 'article-right-sidebar-toggle' ) {
const is_enabled = $field.prop( 'checked' );
if ( is_enabled ) {
// Set TOC to position 1 on the right side if it's currently disabled (0)
const $toc_right = $( '#toc_right' );
if ( $toc_right.length && $toc_right.val() === '0' ) {
$toc_right.val( '1' ).trigger( 'change' );
update_custom_dropdown_display( $toc_right );
}
}
}
// Article Right Sidebar (the UI has hidden fields which need to update on visual UI change - required to have the correct values in KB config when call AJAX update)
if ( field_name === 'nav_sidebar_right' ) {
// When unselected, then set current sidebar navigation type to none
const current_value = $( this ).val();
if ( current_value === '0' || current_value === 0 ) {
$( '[name="article_nav_sidebar_type_right"][value="eckb-nav-sidebar-none"]' ).parent().find( '.epkb-label' ).click();
return;
}
// When sidebar switched by 'Categories and Articles Navigation' dropdown, then keep its type in the selected sidebar (e.g. copy navigation type value from opposite sidebar to the current sidebar)
const current_left_sidebar_type = $( '[name="article_nav_sidebar_type_left"]:checked' ).val();
if ( current_left_sidebar_type !== 'eckb-nav-sidebar-none' ) {
$( '[name="article_nav_sidebar_type_left"][value="eckb-nav-sidebar-none"]' ).parent().find( '.epkb-label' ).click();
$( '[name="article_nav_sidebar_type_right"][value="' + current_left_sidebar_type + '"]' ).parent().find( '.epkb-label' ).click();
}
}
// Article Left Sidebar Position dropdown (the UI has hidden fields which need to update on visual UI change - required to have the correct values in KB config when call AJAX update)
if ( field_name === 'nav_sidebar_left' ) {
// When unselected, then set current sidebar navigation type to none
const current_value = $( this ).val();
if ( current_value === '0' || current_value === 0 ) {
$( '[name="article_nav_sidebar_type_left"][value="eckb-nav-sidebar-none"]' ).parent().find( '.epkb-label' ).click();
return;
}
// When position is set to 1, 2, or 3, ensure Sidebar Navigation is set to "All Categories and Articles"
const current_left_sidebar_type = $( '[name="article_nav_sidebar_type_left"]:checked' ).val();
if ( current_left_sidebar_type === 'eckb-nav-sidebar-none' ) {
$( '[name="article_nav_sidebar_type_left"][value="eckb-nav-sidebar-v1"]' ).parent().find( '.epkb-label' ).click();
}
// When sidebar switched by 'Categories and Articles Navigation' dropdown, then keep its type in the selected sidebar (e.g. copy navigation type value from opposite sidebar to the current sidebar)
const current_right_sidebar_type = $( '[name="article_nav_sidebar_type_right"]:checked' ).val();
if ( current_right_sidebar_type !== 'eckb-nav-sidebar-none' ) {
$( '[name="article_nav_sidebar_type_right"][value="eckb-nav-sidebar-none"]' ).parent().find( '.epkb-label' ).click();
$( '[name="article_nav_sidebar_type_left"][value="' + current_right_sidebar_type + '"]' ).parent().find( '.epkb-label' ).click();
}
}
// For radio buttons, only proceed if the changed element is the selected one.
if ( $field.attr( 'type' ) === 'radio' && ! $field.is( ':checked' ) ) {
return;
}
// Handle template_for_archive_page with current_theme_templates value
if ( field_name === 'template_for_archive_page' && $field.val() === 'current_theme_templates' ) {
// Create dialog HTML
const dialogHtml = `
<div id="epkb-theme-template-dialog" class="epkb-dialog-box-form epkb-dialog-box-form--active">
<div class="epkb-dbf__header">
<h4>${epkb_vars.theme_template_switch_title || 'Switch to Theme Template'}</h4>
</div>
<div class="epkb-dbf__body">
${epkb_vars.theme_template_switch_msg || 'The page will reload with your theme template. You can switch back in KB Settings.'}
</div>
<div class="epkb-dbf__footer">
<div class="epkb-dbf__footer__accept">
<span class="epkb-dbf__footer__accept__btn">OK</span>
</div>
<div class="epkb-dbf__footer__cancel">
<span class="epkb-dbf__footer__cancel__btn">Cancel</span>
</div>
</div>
<div class="epkb-dbf__close epkbfa epkbfa-times"></div>
</div>
<div class="epkb-dialog-box-form-black-background"></div>
`;
// Remove any existing dialog
$( '#epkb-theme-template-dialog, .epkb-dialog-box-form-black-background' ).remove();
// Add dialog to body
$( 'body' ).append( dialogHtml );
// Handle OK button click
$( '#epkb-theme-template-dialog .epkb-dbf__footer__accept__btn' ).on( 'click', function() {
// Remove dialog
$( '#epkb-theme-template-dialog, .epkb-dialog-box-form-black-background' ).remove();
// Show loading dialog on the archive page container
const loadingContainer = $( '#eckb-archive-page-container' );
epkb_loading_Dialog( 'show', epkb_vars.switching_template_msg || 'Switching to theme template...', loadingContainer );
// Call the switch_kb_template AJAX function to switch to current theme template
$.ajax({
url: epkb_vars.ajaxurl,
type: 'POST',
dataType: 'json',
data: {
action: 'epkb_switch_kb_template',
_wpnonce_epkb_ajax_action: epkb_vars.nonce,
epkb_kb_id: $('#epkb-fe__editor').data('kbid'),
template_type: 'current_theme_templates'
},
success: function( response ) {
if ( response.reload === true ) {
// Update the loading message to indicate page reload
loadingContainer.find( '.epkb-admin-text' ).text( epkb_vars.reloading_page_msg || 'Reloading page...' );
// Reload the page after a short delay
setTimeout( function() {
window.location.reload();
}, 500 );
} else {
// Remove loading dialog if no reload needed
epkb_loading_Dialog( 'remove', '', loadingContainer );
}
},
error: function( jqXHR, textStatus, errorThrown ) {
// Remove loading dialog on error
epkb_loading_Dialog( 'remove', '', loadingContainer );
// Show error notification
$('.eckb-bottom-notice-message').remove();
$('body').append( epkb_admin_notification( '', epkb_vars.switch_template_error || 'Failed to switch template. Please try again.', 'error' ) );
console.error( 'Failed to switch template:', errorThrown );
}
});
});
// Handle Cancel button and close X click
$( '#epkb-theme-template-dialog .epkb-dbf__footer__cancel__btn, #epkb-theme-template-dialog .epkb-dbf__close' ).on( 'click', function() {
// Remove dialog
$( '#epkb-theme-template-dialog, .epkb-dialog-box-form-black-background' ).remove();
// Revert radio button selection back to previous value (kb_templates)
$( 'input[name="template_for_archive_page"][value="kb_templates"]' ).prop( 'checked', true );
});
// Prevent default processing
return;
}
// Module position handles its change itself
if ( $field.closest( '.epkb-row-module-position' ).length ) {
return;
}
// Module selector is excluded from the Editor UI
if ( $field.closest( '.epkb-fe__settings-section--module-selection' ).length > 0 ) {
return;
}
// Unselected module does not need to trigger AJAX update for preview
if ( $field.closest( '.epkb-fe__feature-settings' ).attr( 'data-row-number' ) === 'none' && ! $field.closest( '.epkb-fe__feature-settings' ).attr( 'data-non-row-feature' ) ) {
return;
}
// AI Collection ID should never trigger preview update - just save the new value
if ( field_name === 'kb_ai_collection_id' ) {
return;
}
// For some settings need to reload the entire page
// Also reload if page builder is enabled on article page
const $feature_settings = $( event.target ).closest( '.epkb-fe__feature-settings' );
const kb_page_type = $feature_settings.data( 'kb-page-type' );
const has_page_builder = frontendEditor.data( 'has-page-builder' ) === true;
// When switching to Current Theme Template, save settings and do a full page reload
if ( field_name === 'templates_for_kb' && $field.val() === 'current_theme_templates' ) {
save_settings_and_reload_page( kb_page_type );
return;
}
if ( field_name === 'templates_for_kb' || field_name === 'template_main_page_display_title' || field_name === 'general_typography_font_family' ||
( kb_page_type === 'article-page' && has_page_builder ) ) {
update_preview_via_page_reload( event );
return;
}
// Color-picker handles its update through 'iris' library
if ( $field.hasClass( 'wp-color-picker' ) ) {
return;
}
updatePreview( event );
});
function prepare_color_picker( feature_name ) {
let isColorInputSync = false;
$( '#epkb-fe__editor .epkb-fe__feature-settings[data-feature="' + feature_name + '"] .epkb-fe__settings-list .epkb-admin__color-field input' ).wpColorPicker({
change: function( colorEvent, ui) {
// Do nothing for programmatically changed value (for sync purpose)
if ( isColorInputSync ) {
return;
}
isColorInputSync = true;
// Get current color value
let color_value = $( colorEvent.target ).wpColorPicker( 'color' );
let setting_name = $( colorEvent.target ).attr( 'name' );
// Sync other color pickers that have the same name
$( '.epkb-admin__color-field input[name="' + setting_name + '"]' ).not( colorEvent.target ).each( function () {
$( this ).wpColorPicker( 'color', color_value );
} );
isColorInputSync = false;
},
});
// Ensure the WordPress color-picker is ready before 'iris' library options are available
setTimeout( function() {
$( '#epkb-fe__editor .epkb-fe__feature-settings[data-feature="' + feature_name + '"] .epkb-fe__settings-list input.wp-color-picker' ).iris( 'option', 'change', colorpicker_update );
}, 100 );
}
// Preview Update: on color change
setTimeout( function() {
$( '#epkb-fe__editor input.wp-color-picker' ).iris( 'option', 'change', colorpicker_update );
}, 100 );
// Before send AJAX request for the preview update, the color-picker should wait until the user stopped to change the color
let colorpicker_update_timeout = false;
function colorpicker_update( event, ui ) {
// Remove previous timeout handler
if ( colorpicker_update_timeout ) {
clearTimeout( colorpicker_update_timeout );
}
// Set current timeout handler
colorpicker_update_timeout = setTimeout( function () {
// Check if we need to reload for page builder on article page
const $feature_settings = $( event.target ).closest( '.epkb-fe__feature-settings' );
const kb_page_type = $feature_settings.data( 'kb-page-type' );
const has_page_builder = frontendEditor.data( 'has-page-builder' ) === true;
if ( kb_page_type === 'article-page' && has_page_builder ) {
update_preview_via_page_reload( event, ui );
} else {
updatePreview( event, ui );
}
colorpicker_update_timeout
}, 200 );
}
// Save settings
$( document ).on( 'click', '#epkb-fe__action-save', function( event ) {
let kb_config = collectConfig();
// Determine which save action to use based on the active feature's page type
const $activeFeatureSettings = $( '#epkb-fe__editor .epkb-fe__feature-settings--active' );
const kb_page_type = $activeFeatureSettings.data( 'kb-page-type' );
let save_action = 'eckb_save_fe_settings'; // default for main page
if ( kb_page_type === 'article-page' ) {
save_action = 'eckb_save_fe_article_settings';
} else if ( kb_page_type === 'archive-page' ) {
save_action = 'eckb_save_fe_archive_settings'; // for future implementation
}
// Show loading dialog based on page type
let loadingContainer;
if ( kb_page_type === 'article-page' ) {
loadingContainer = $( '#eckb-article-page-container-v2' );
} else if ( kb_page_type === 'archive-page' ) {
loadingContainer = $( '#eckb-archive-page-container' );
} else {
loadingContainer = $( '#epkb-modular-main-page-container, #eckb-kb-template' );
}
epkb_loading_Dialog( 'show', '', loadingContainer );
$.ajax( {
url: epkb_vars.ajaxurl,
method: 'POST',
data: {
action: save_action,
_wpnonce_epkb_ajax_action: epkb_vars.nonce,
kb_id: frontendEditor.data( 'kbid' ),
new_kb_config: kb_config,
is_article_page: kb_page_type === 'article-page' ? 1 : 0,
selected_search_preset: selected_search_preset,
selected_categories_preset: selected_categories_preset
},
success: function( response ) {
if ( response.data && response.data.message ) {
epkb_show_success_notification( response.data.message );
}
// Handle successful AJAX response.
if ( response.success ) {
} else {
try {
let responseJson = JSON.parse( response );
if ( responseJson.message ) {
$( 'body' ).append( $( responseJson.message ) );
}
} catch ( e ) {}
}
// Remove loading dialog
epkb_loading_Dialog( 'remove', '', loadingContainer );
// Reset stored search design preset
selected_search_preset = 'current';
selected_categories_preset = 'current';
},
error: function( jqXHR, textStatus, errorThrown ) {
// Log error to console
console.error( 'Frontend Editor Save Settings Error:', {
status: jqXHR.status,
statusText: jqXHR.statusText,
textStatus: textStatus,
errorThrown: errorThrown,
response: jqXHR.responseJSON || jqXHR.responseText
} );
epkb_loading_Dialog( 'remove', '', loadingContainer );
}
} );
} );
function collectConfig() {
// collect settings
let kb_config = {};
frontendEditor.find( 'input, select, textarea' ).each( function(){
// ignore inputs with empty name and pro feature fields (an ad field)
if ( ! $( this ).attr( 'name' ) || ! $( this ).attr( 'name' ).length
|| $( this ).closest( '.epkb-input-group' ).find( '.epkb__option-pro-tag' ).length
|| $( this ).closest( '.epkb-input-group' ).find( '.epkb__option-pro-tag-container' ).length ) {
return true;
}
if ( $( this ).attr( 'type' ) === 'checkbox' ) {
// checkboxes multiselect
if ( $( this ).closest( '.epkb-admin__checkboxes-multiselect' ).length ) {
if ( $( this ).prop( 'checked' ) ) {
if ( ! kb_config[ $(this).attr( 'name' ) ] ) {
kb_config[ $( this ).attr( 'name' ) ] = [];
}
kb_config[ $( this ).attr( 'name' ) ].push( $( this ).val() );
}
// single checkbox
} else {
kb_config[ $( this ).attr( 'name' ) ] = $( this ).prop( 'checked' ) ? 'on' : 'off';
}
return true;
}
if ( $( this ).attr('type') === 'radio' ) {
if ( $( this ).prop( 'checked' ) ) {
kb_config[ $( this ).attr( 'name' ) ] = $( this ).val();
}
return true;
}
if ( typeof $( this ).attr( 'name' ) == 'undefined' ) {
return true;
}
kb_config[ $( this ).attr( 'name' ) ] = $( this ).val();
});
// Ensure 'faq_group_ids' is set even if no FAQ Groups are selected
if ( $( '[name="faq_group_ids"]' ).length && typeof kb_config.faq_group_ids == 'undefined' ) {
kb_config.faq_group_ids = 0;
}
// Add current module positions for all enabled modules
$('.epkb-ml__row').each(function() {
const $row = $(this);
const feature = $row.attr('data-feature');
const position = $row.attr('data-position');
if (feature && position) {
kb_config[feature + '_module_position'] = position;
}
});
// Set 'none' position for disabled modules
$('.epkb-fe__feature-settings').each(function() {
const $featureSettings = $(this);
const feature = $featureSettings.attr('data-feature');
const rowNumber = $featureSettings.attr('data-row-number');
if (feature && rowNumber === 'none') {
kb_config[feature + '_module_position'] = 'none';
}
});
// General typography
const $general_typography_input = frontendEditor.find( '[name="general_typography_font_family"]' );
const $general_typography_loader = frontendEditor.find( '.epkb-general_typography-loader' );
if ( $general_typography_input.length > 0 ) {
kb_config.general_typography_font_family = $general_typography_input.val();
} else if ( $general_typography_loader.length > 0 ) {
kb_config.general_typography_font_family = $general_typography_loader.data( 'selected' );
}
return kb_config;
}
/*************************************************************************************************
*
* TOC Position Synchronization
*
************************************************************************************************/
// Helper function to unselect TOC in article sidebar positions
function unselect_toc_in_article_sidebar_positions() {
$( '#epkb-fe__editor #toc_left, #epkb-fe__editor #toc_right' ).each( function() {
if ( parseInt( $( this ).val() ) > 0 ) {
$( this ).val( '0' ).trigger( 'change' );
update_custom_dropdown_display( $( this ) );
}
} );
}
// Helper function to set TOC to article sidebar position
function set_toc_to_article_sidebar_position( sidebar_suffix ) {
let is_toc_set = false;
const $sidebar_toc = $( '#epkb-fe__editor #toc_' + sidebar_suffix );
if ( $sidebar_toc.length && parseInt( $sidebar_toc.val() ) === 0 ) {
ignore_setting_update_flag = true;
$sidebar_toc.val( '3' ).trigger( 'change' );
update_custom_dropdown_display( $sidebar_toc );
ignore_setting_update_flag = false;
is_toc_set = true;
}
return is_toc_set;
}
// Check and update TOC toggler state based on locations
function check_toc_toggler_state() {
let state = false;
// Check if any location is selected
$( '#epkb-fe__editor input[name="toc_locations"]' ).each( function() {
if ( $( this ).prop('checked' ) ) {
state = true;
}
} );
// Check if any sidebar position is selected
if ( parseInt( $( '#epkb-fe__editor #toc_left' ).val() ) > 0 ||
parseInt( $( '#epkb-fe__editor #toc_right' ).val() ) > 0 ||
parseInt( $( '#epkb-fe__editor #toc_content' ).val() ) > 0 ) {
state = true;
}
ignore_setting_update_flag = true;
$( '#epkb-fe__editor #toc_toggler input' ).prop( 'checked', state );
ignore_setting_update_flag = false;
}
// TOC Location Click Handler - matches backend behavior
$( document ).on( 'click', '#epkb-fe__editor input[name="toc_locations"]', function() {
let current_location = $( this ).prop( 'value' );
let input_checked = $( this ).prop( 'checked' );
let is_toc_set = false;
ignore_setting_update_flag = true;
switch ( current_location ) {
case 'toc_left':
unselect_toc_in_article_sidebar_positions();
$( '#epkb-fe__editor #toc_content' ).val( '0' ).trigger( 'change' );
update_custom_dropdown_display( $( '#epkb-fe__editor #toc_content' ) );
if ( input_checked ) {
is_toc_set = set_toc_to_article_sidebar_position( 'left' );
}
break;
case 'toc_content':
unselect_toc_in_article_sidebar_positions();
$( '#epkb-fe__editor #toc_content' ).val( '0' ).trigger( 'change' );
update_custom_dropdown_display( $( '#epkb-fe__editor #toc_content' ) );
if ( input_checked ) {
$( '#epkb-fe__editor input[name="toc_locations"][value="toc_content"]' ).prop( 'checked', true );
$( '#epkb-fe__editor input[name="toc_locations"][value="toc_left"], #epkb-fe__editor input[name="toc_locations"][value="toc_right"]' ).prop( 'checked', false );
$( '#epkb-fe__editor #toc_content' ).val( '1' ).trigger( 'change' );
update_custom_dropdown_display( $( '#epkb-fe__editor #toc_content' ) );
is_toc_set = true;
}
break;
case 'toc_right':
unselect_toc_in_article_sidebar_positions();
$( '#epkb-fe__editor #toc_content' ).val( '0' ).trigger( 'change' );
update_custom_dropdown_display( $( '#epkb-fe__editor #toc_content' ) );
if ( input_checked ) {
is_toc_set = set_toc_to_article_sidebar_position( 'right' );
}
break;
default:
break;
}
$( '#epkb-fe__editor input[name="toc_locations"]' ).each( function() {
// Skip current Location input (unset current location if failed to set toc position to the current location)
if ( $( this ).prop( 'value' ) === current_location && is_toc_set ) {
return true;
}
// Unselect Location input
$( this ).prop( 'checked', false );
} );
// Refresh toggler
check_toc_toggler_state();
ignore_setting_update_flag = false;
// Trigger preview update
updatePreview( { target: this } );
} );
/*************************************************************************************************
*
* Module Position Change
*
************************************************************************************************/
/* ------------------------------------------------------------------
Modular-page position helper (v2 – toggle + radio buttons)
– Keeps every module's toggle / "Move Up" / "Move Down" controls
in sync with the real order of .epkb-ml__row elements.
– Runs once on DOM-ready and after every control interaction.
------------------------------------------------------------------ */
const MAX_POS = 5; // hard limit
const $container = $('#epkb-modular-main-page-container');
/* ─── utilities ──────────────────────────────────────────────── */
const rows = () => $('.epkb-ml__row'); // live collection
const rowByFeature = slug => $(`.epkb-ml__row[data-feature="${slug}"]`);
const enabledCount = () => rows().length;
/* (re)build the on-page rows list according to their data-position */
function sortRows() {
rows()
.sort((a, b) => +$(a).attr('data-position') - +$(b).attr('data-position'))
.appendTo($container);
}
/* renumber 1…N after any removal or swap (keeps gaps out) */
function renumberRows() {
rows().each((idx, el) => {
const pos = idx + 1;
const $row = $(el);
const feature = $row.attr('data-feature');
$row.attr('data-position', pos);
// Update the corresponding feature settings' data-row-number attribute
if (feature) {
$('.epkb-fe__feature-settings[data-feature="' + feature + '"]').attr('data-row-number', pos);
}
});
normalizeSettingsAfterRowsRenumbering();
}
function normalizeSettingsAfterRowsRenumbering() {
// Normalize row numbers
rows().each( ( idx, el ) => {
const $row = $( el );
const position = parseInt( $row.attr( 'data-position' ) );
let prev_position = $row.attr( 'id' );
prev_position = prev_position ? parseInt( prev_position.replaceAll( 'epkb-ml__row-', '' ) ) : position;
const $other_row = $( '#epkb-ml__row-' + position );
if ( position === prev_position ) {
return;
}
let $row_module_setting = $( '[name="ml_row_' + prev_position + '_module"]' );
let $row_width_setting = $( '[name="ml_row_' + prev_position + '_desktop_width"]' );
let $row_width_units_setting = $( '[name="ml_row_' + prev_position + '_desktop_width_units"]' );
const width_unit_value = $( '[name="ml_row_' + prev_position + '_desktop_width_units"]:checked' ).val();
let $other_row_module_setting = $( '[name="ml_row_' + position + '_module"]' );
let $other_row_width_setting = $( '[name="ml_row_' + position + '_desktop_width"]' );
let $other_row_width_units_setting = $( '[name="ml_row_' + position + '_desktop_width_units"]' );
const other_width_unit_value = $( '[name="ml_row_' + position + '_desktop_width_units"]:checked' ).val();
const feature = $row_width_setting.closest( '.epkb-fe__feature-settings' ).find( '.epkb-row-module-position.epkb-settings-control-type-toggle .epkb-settings-control__input__toggle' ).prop( 'checked' )
? $row_width_setting.closest( '.epkb-fe__feature-settings' ).attr( 'data-feature' )
: 'none';
const other_feature = $other_row_width_setting.closest( '.epkb-fe__feature-settings' ).find( '.epkb-row-module-position.epkb-settings-control-type-toggle .epkb-settings-control__input__toggle' ).prop( 'checked' )
? $other_row_width_setting.closest( '.epkb-fe__feature-settings' ).attr( 'data-feature' )
: 'none';
$row.attr( 'id', 'epkb-ml__row-' + position );
$other_row.attr( 'id', 'epkb-ml__row-' + prev_position );
$row_module_setting
.attr( 'id', 'ml_row_' + position + '_module' )
.attr( 'name', 'ml_row_' + position + '_module' );
//.val( feature );
$row_width_setting
.attr( 'id', 'ml_row_' + position + '_desktop_width' )
.attr( 'name', 'ml_row_' + position + '_desktop_width' )
.parent().find( 'label' ).attr( 'for', 'ml_row_' + position + '_desktop_width' )
.closest( '#ml_row_' + prev_position + '_desktop_width' ).attr( 'id', 'ml_row_' + position + '_desktop_width' );
$row_width_units_setting.closest( '#ml_row_' + prev_position + '_desktop_width_units' ).attr( 'id', 'ml_row_' + position + '_desktop_width_units' );
$row_width_units_setting.each( function () {
let current_id = $( this ).attr( 'id' );
current_id = current_id.replaceAll( 'ml_row_' + prev_position, 'ml_row_' + position );
$( this ).attr( 'id', current_id ).attr( 'name', 'ml_row_' + position + '_desktop_width_units' ).parent().find( 'label' ).attr( 'for', current_id );
} );
$other_row_module_setting
.attr( 'id', 'ml_row_' + prev_position + '_module' )
.attr( 'name', 'ml_row_' + prev_position + '_module' );
// .val( other_feature );
$other_row_width_setting
.attr( 'id', 'ml_row_' + prev_position + '_desktop_width' )
.attr( 'name', 'ml_row_' + prev_position + '_desktop_width' )
.parent().find( 'label' ).attr( 'for', 'ml_row_' + prev_position + '_desktop_width' )
.closest( '#ml_row_' + position + '_desktop_width' ).attr( 'id', 'ml_row_' + prev_position + '_desktop_width' );
$other_row_width_units_setting.closest( '#ml_row_' + position + '_desktop_width_units' ).attr( 'id', 'ml_row_' + prev_position + '_desktop_width_units' );
$other_row_width_units_setting.each( function () {
let current_id = $( this ).attr( 'id' );
current_id = current_id.replaceAll( 'ml_row_' + position, 'ml_row_' + prev_position );
$( this ).attr( 'id', current_id ).attr( 'name', 'ml_row_' + prev_position + '_desktop_width_units' ).parent().find( 'label' ).attr( 'for', current_id );
} );
$row_width_units_setting.each( function () {
if ( $( this ).val() === width_unit_value ) {
$( this ).prop( 'checked', true );
}
} );
$other_row_width_units_setting.each( function () {
if ( $( this ).val() === other_width_unit_value ) {
$( this ).prop( 'checked', true );
}
} );
let inline_styles_container = $( '[id^="epkb-mp-frontend-modular-"][id$="-layout-inline-css"]' )
let inline_styles = inline_styles_container.html();
inline_styles = inline_styles
.replaceAll( '#epkb-ml__row-' + position, '#epkb-ml__row-temp' )
.replaceAll( '#epkb-ml__row-' + prev_position, '#epkb-ml__row-' + position )
.replaceAll( '#epkb-ml__row-temp', '#epkb-ml__row-' + prev_position );
inline_styles_container.html( inline_styles );
});
}
/* create a minimal placeholder row when a module is (re)enabled */
function addRow(toggle, slug) {
if (enabledCount() >= MAX_POS || rowByFeature(slug).length) return;
const new_row_id = 'epkb-ml__row-' + $( toggle ).closest( '#epkb-fe__editor .epkb-fe__feature-settings--active' ).find( '[id^="ml_row_"][id$="_desktop_width"]' ).attr( 'id' ).replaceAll( 'ml_row_', '' ).replaceAll( '_desktop_width', '' );
// Create new row with temporary position 0 (will be fixed by renumberRows)
$('<div>', {
id: new_row_id,
class: 'epkb-ml__row',
'data-position': 0,
'data-feature': slug
}).prependTo($container);
// Renumber all rows starting from 1
renumberRows();
sortRows();
// Update the feature settings' data-row-number attribute to match the new position
const newPosition = rowByFeature(slug).attr('data-position');
$('.epkb-fe__feature-settings[data-feature="' + slug + '"]').attr('data-row-number', newPosition);
// Trigger updatePreview with the correct context
const $featureSettings = $('.epkb-fe__feature-settings[data-feature="' + slug + '"]');
const $dummyEvent = {
target: $featureSettings.find('input, select').first()[0],
preventDefault: function() {}
};
if ($dummyEvent.target) {
updatePreview($dummyEvent);
}
// Update all position controls to reflect the new state
refreshPositionControls();
}
/* remove a row when a module is disabled */
function removeRow(slug) {
rowByFeature(slug).remove();
renumberRows();
sortRows();
refreshPositionControls();
}
/* refresh Module Position settings to reflect the current state */
function refreshPositionControls() {
const total = enabledCount();
// Only target module position toggles within the frontend editor
// More specific selector to avoid affecting other controls
$('#epkb-fe__editor .epkb-row-module-position .epkb-settings-control__input__toggle').each(function () {
const $toggle = $(this);
const slug = $toggle.attr('name');
const $wrap = $toggle.closest('.epkb-fe__settings-section-body');
const $radios = $wrap.find('.epkb-radio-buttons-container');
const $up = $radios.find('input[value="move-up"]');
const $down = $radios.find('input[value="move-down"]');
const $row = rowByFeature(slug);
const enabled = $row.length > 0;
const $featureSettings = $('.epkb-fe__feature-settings[data-feature="' + slug + '"]');
/* toggle state & label */
$toggle.prop('checked', enabled);
/* Update the feature settings' data-row-number attribute */
if (enabled) {
const position = $row.attr('data-position');
$featureSettings.attr('data-row-number', position);
} else {
$featureSettings.attr('data-row-number', 'none');
}
/* show/hide radio buttons */
if (!enabled || total <= 1) {
$radios.hide();
} else {
$radios.show();
const pos = +$row.attr('data-position');
$up.prop('disabled', pos === 1);
$down.prop('disabled', pos === total);
}
/* show/hide other settings sections based on module state */
if (enabled) {
// Show all settings sections when module is enabled
$featureSettings.find('.epkb-fe__settings-section:not(.epkb-fe__settings-section--module-position)').removeClass('epkb-fe__settings-section--hide');
} else {
// Hide all settings sections except module position when module is disabled
$featureSettings.find('.epkb-fe__settings-section:not(.epkb-fe__settings-section--module-position)').addClass('epkb-fe__settings-section--hide');
}
/* clear the momentary radio selection */
$radios.find('input[type="radio"]').prop('checked', false);
});
}
/* ─── event handlers ────────────────────────────────────────────────── */
// 1. toggle enable / disable ------------------------------------------
$( document ).on( 'change', '#epkb-fe__editor .epkb-row-module-position .epkb-settings-control__input__toggle', function () {
const slug = this.name;
if ( this.checked ) {
addRow( this, slug );
} else {
removeRow( slug );
}
} );
// 2. move-up / move-down ----------------------------------------------
$( document ).on('change', '.epkb-input[value="move-up"], .epkb-input[value="move-down"]', function () {
const $btn = $(this);
const dir = $btn.val() === 'move-up' ? -1 : +1;
const slug = $btn.closest('.epkb-row-module-position').data('module');
const $row = rowByFeature(slug);
const oldPos = +$row.attr('data-position');
const newPos = oldPos + dir;
const $otherRow = rows().filter((_, el) => +$(el).attr('data-position') === newPos);
if (!$row.length || !$otherRow.length) return; // already at edge
// swap the two rows' data-position values
$row.attr('data-position', newPos);
$otherRow.attr('data-position', oldPos);
normalizeSettingsAfterRowsRenumbering();
sortRows();
refreshPositionControls(); // Triggered by move up/down as required
});
/* ─── initial run ───────────────────────────────────────────────────── */
renumberRows(); // make sure positions start at 1‥N, no gaps
sortRows();
refreshPositionControls(); // Keep for initial setup
/*************************************************************************************************
*
* Various
*
************************************************************************************************/
//Collect Page Parameters
const pageConfigs = [
{
checkSelector: '#epkb-modular-main-page-container',
prefix: 'mp',
containerSelector: '#epkb-ml__module-categories-articles',
searchSelector: '#epkb-ml__row-1',
},
{
checkSelector: '#eckb-article-page-container-v2',
prefix: 'ap',
containerSelector: '#eckb-article-body',
searchSelector: '#eckb-article-header',
},
{
checkSelector: '#eckb-archive-body',
prefix: 'cp',
containerSelector: '#eckb-archive-body',
searchSelector: '#eckb-archive-header',
},
];
function epkb_get_page_params() {
const windowWidth = $( 'body' ).width();
let prefix = '';
let containerWidth = 0;
let searchWidth = 0;
for ( const config of pageConfigs ) {
if ( $( config.checkSelector ).length ) {
prefix = config.prefix;
containerWidth = Math.round( $( config.containerSelector ).width() );
searchWidth = Math.round( $( config.searchSelector ).width() );
break;
}
}
if ( prefix ) {
if ( searchWidth ) {
$( `.js-epkb-${prefix}-search-width` ).text(`${searchWidth}px` );
}
if ( containerWidth ) {
$( `.js-epkb-${prefix}-width-container`).text(`${containerWidth}px` );
}
if ( windowWidth ) {
$( `.js-epkb-${prefix}-width` ).text(`${windowWidth}px` );
}
}
}
//Initialize Visual Helper Collect Page Parameters
epkb_get_page_params();
//Resize Visual Helper Collect Page Parameters
$( window ).on('resize', function() {
epkb_get_page_params();
} );
function epkb_loading_Dialog( displayType, message, parent_container ){
if ( displayType === 'show' ) {
let output =
'<div class="epkb-fe-dialog-box-loading">' +
//<-- Header -->
'<div class="epkb-admin-dbl__header">' +
'<div class="epkb-admin-dbl-icon epkbfa epkbfa-hourglass-half"></div>'+
(message ? '<div class="epkb-admin-text">' + message + '</div>' : '' ) +
'</div>'+
'</div>';
//Add message output at the end of Body Tag
parent_container.append( output );
} else if( displayType === 'remove' ) {
// Remove loading dialogs.
parent_container.find( '.epkb-fe-dialog-box-loading' ).remove();
}
}
// Close Button Message if Close Icon clicked
$( document ).on( 'click', '.epkb-close-notice', function() {
let bottom_message = $( this ).closest( '.eckb-bottom-notice-message' );
bottom_message.addClass( 'fadeOutDown' );
setTimeout( function() {
bottom_message.html( '' );
}, 10000 );
} );
// SHOW INFO MESSAGES
function epkb_admin_notification( $title, $message , $type ) {
return '<div class="eckb-bottom-notice-message">' +
'<div class="contents">' +
'<span class="' + $type + '">' +
($title ? '<h4>' + $title + '</h4>' : '' ) +
($message ? '<p>' + $message + '</p>': '') +
'</span>' +
'</div>' +
'<div class="epkb-close-notice epkbfa epkbfa-window-close"></div>' +
'</div>';
}
function epkb_show_success_notification( $message, $title = '' ) {
$('.eckb-bottom-notice-message').remove();
$('body').append( epkb_admin_notification( $title, $message, 'success' ) );
}
// Re-open editor if the page was reloaded programmatically on setting change
( function() {
const current_url = new URL( window.location.href );
const feature_name = current_url.searchParams.get( 'epkb_fe_reopen_feature' )
if ( feature_name && feature_name.length > 0 ) {
open_frontend_editor( null );
// Re-open editor feature
$( '.epkb-fe__toggle' ).trigger( 'click' );
$( '#epkb-fe__editor .epkb-fe__feature-select-button[data-feature="' + feature_name + '"]' ).trigger( 'click' );
// Remove temporary parameter to avoid re-opening editor on manual page reloading
current_url.searchParams.delete( 'epkb_fe_reopen_feature' );
// Clear history to avoid resending the form on manual page reloading
history.replaceState( null, '', current_url.pathname + current_url.search + current_url.hash );
}
} )();
// Open editor if it is opened by 'epkb_load_editor' action (when FE is hidden by settings it is still possible to open the FE by admin link or direct link in Settings UI)
( function() {
const current_url = new URL( window.location.href );
const action = current_url.searchParams.get( 'action' )
if ( action && action.length > 0 && action === 'epkb_load_editor' ) {
open_frontend_editor( null );
// Prevent re-opening FE on page reload - remove both action and KB ID parameters
current_url.searchParams.delete( 'action' );
current_url.searchParams.delete( 'epkb_kb_id' );
// Clear history to avoid resending reopening the FE on manual page reloading
history.replaceState( null, '', current_url.pathname + current_url.search + current_url.hash );
}
} )();
// Link to open certain setting of certain feature
$( document ).on( 'click', '#epkb-fe__editor .epkb-fe__open-feature-setting-link', function ( event ) {
// Disable the default <a> tag behavior
event.preventDefault();
const feature_name = $( this ).attr( 'data-feature' );
const setting_name = $( this ).attr( 'data-setting' );
const settings_section = $( this ).attr( 'data-section' );
// Remove any previous highlighting
frontendEditor.find( '.epkb-highlighted_setting' ).removeClass( 'epkb-highlighted_setting' );
// Open the target feature settings
frontendEditor.find( '.epkb-fe__feature-select-button[data-feature="' + feature_name + '"]' ).trigger( 'click' );
let setting_offset = 0;
let $target_container = false;
// CASE: Link to single setting
if ( setting_name ) {
$target_container = frontendEditor.find( '.epkb-fe__feature-settings[data-feature="' + feature_name + '"] [name="' + setting_name + '"]' ).closest( '.epkb-input-group' );
}
// CASE: Link to settings section
if ( settings_section ) {
$target_container = frontendEditor.find( '.epkb-fe__feature-settings[data-feature="' + feature_name + '"] .epkb-fe__settings-section--' + settings_section );
}
if ( $target_container.length > 0 ) {
setting_offset = $target_container.offset().top - frontendEditor.offset().top - 100;
setting_offset = setting_offset > 0 ? setting_offset : 0;
// Highlight the target setting
$target_container.addClass( 'epkb-highlighted_setting' );
}
// Scroll to the target setting
frontendEditor.animate( {
scrollTop: setting_offset
}, 300 );
// Disable the default <a> tag behavior
return false;
} );
// use to remove link to FE
function isDiviPresent() {
// Multiple checks for maximum reliability
const checks = [
// Check for Divi CSS classes
document.querySelector('.et_pb_section, .et_pb_row, .et_pb_column') !== null,
// Check for Divi JavaScript
typeof window.et_pb_custom !== 'undefined',
// Check for body classes
document.body.classList.contains('et_divi_theme') ||
document.body.classList.contains('et_pb_theme'),
// Check for Divi stylesheets
Array.from(document.querySelectorAll('link[rel="stylesheet"]'))
.some(link => link.href.includes('divi') || link.href.includes('et-core')),
// Check meta generator tag
document.querySelector('meta[name="generator"][content*="Divi"]') !== null
];
// Return true if any check passes
return checks.some(check => check === true);
}
// Update typography font family hidden input when font is selected
$( document ).on( 'epkb-font-selected', function( e, fontFamily ) {
$( '#general_typography_font_family' ).val( fontFamily );
$( '.epkb-general_typography-current' ).text( fontFamily );
});
// Load General Typography on demand
$( document ).on( 'click', '.epkb-general_typography-loader', function() {
let loader = $( this );
let postData = {
action: 'epkb_load_general_typography',
_wpnonce_epkb_ajax_action: epkb_vars.nonce,
active_font_family: loader.data( 'selected' )
};
epkb_send_ajax( postData, function( response ) {
loader.closest( '.epkb-general_typography-loader-wrap' ).replaceWith( response.data );
ignore_setting_update_flag = true;
$( '#general_typography_font_family' ).trigger( 'change' );
ignore_setting_update_flag = false;
} );
} );
/*************************************************************************************************
*
* AJAX calls
*
************************************************************************************************/
// generic AJAX call handler
function epkb_send_ajax( postData, refreshCallback, callbackParam, reload, alwaysCallback, $loader ) {
let errorMsg;
let theResponse;
refreshCallback = (typeof refreshCallback === 'undefined') ? 'epkb_callback_noop' : refreshCallback;
let loadingContainer = $( '#epkb-modular-main-page-container, #eckb-kb-template' );
$.ajax({
type: 'POST',
dataType: 'json',
data: postData,
url: epkb_vars.ajaxurl,
beforeSend: function (xhr)
{
if ( typeof $loader == 'undefined' || $loader === false ) {
epkb_loading_Dialog('show', '', loadingContainer);
}
if ( typeof $loader == 'object' ) {
epkb_loading_Dialog('show', '', $loader);
}
}
}).done(function (response) {
theResponse = ( response ? response : '' );
if ( theResponse.error || typeof theResponse.message === 'undefined' ) {
//noinspection JSUnresolvedVariable,JSUnusedAssignment
errorMsg = theResponse.message ? theResponse.message : epkb_admin_notification('', epkb_vars.reload_try_again, 'error');
}
}).fail( function ( response, textStatus, error ) {
//noinspection JSUnresolvedVariable
errorMsg = ( error ? ' [' + error + ']' : epkb_vars.unknown_error );
//noinspection JSUnresolvedVariable
errorMsg = epkb_admin_notification(epkb_vars.error_occurred + '. ' + epkb_vars.msg_try_again, errorMsg, 'error');
}).always(function() {
theResponse = (typeof theResponse === 'undefined') ? '' : theResponse;
if ( typeof alwaysCallback == 'function' ) {
alwaysCallback( theResponse );
}
if ( ! reload ) {
epkb_loading_Dialog('remove', '', loadingContainer);
}
if ( errorMsg ) {
$('.eckb-bottom-notice-message').remove();
$('body').append(errorMsg).removeClass('fadeOutDown');
setTimeout( function() {
$('.eckb-bottom-notice-message').addClass( 'fadeOutDown' );
}, 10000 );
return;
}
if ( typeof refreshCallback === "function" ) {
if ( typeof callbackParam === 'undefined' ) {
refreshCallback(theResponse);
} else {
refreshCallback(theResponse, callbackParam);
}
} else {
if ( reload ) {
location.reload();
}
}
});
}
/**
* Clean up HTML tags incorrectly injected by wpautop.
* This is needed for compatibility with page builders like Elementor that apply wpautop-like formatting.
* Add new cleanup rules here as issues are discovered.
*/
function cleanupWpautopTags() {
// Unwrap select elements that are incorrectly wrapped in <p> tags
document.querySelectorAll( '#epkb-fe__editor p > select' ).forEach( function( select ) {
let p = select.parentNode;
p.parentNode.insertBefore( select, p );
p.remove();
});
// Remove empty <p></p> tags
document.querySelectorAll( '#epkb-fe__editor p:empty' ).forEach( function( p ) {
p.remove();
});
// Fix input_container incorrectly moved inside label by wpautop (move it back outside)
document.querySelectorAll( '#epkb-fe__editor label > .input_container' ).forEach( function( inputContainer ) {
let label = inputContainer.parentNode;
label.parentNode.insertBefore( inputContainer, label.nextSibling );
});
// Fix radio-buttons-container incorrectly moved inside epkb-main_label span by wpautop (move it back outside)
document.querySelectorAll( '#epkb-fe__editor .epkb-main_label > .epkb-radio-buttons-container' ).forEach( function( radioContainer ) {
let span = radioContainer.parentNode;
span.parentNode.insertBefore( radioContainer, span.nextSibling );
});
}
});