# bbp-core/trunk/assets/frontend/js/frontend.js

Forumax – AI Powered Advanced Community Forum Plugin, version trunk. 251 lines.

- Page: https://pluginprobe.com/plugins/bbp-core/trunk/code/assets/frontend/js/frontend.js
- Raw: https://pluginprobe.com/plugins/bbp-core/trunk/raw/assets/frontend/js/frontend.js
- Modified: 2026-08-01T14:38:00+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/bbp-core/trunk/code/assets/frontend/js/frontend.js#L10-L20`.

```javascript
( function ( $ ) {
	$( document ).ready( function () {

		/**
		 * Initialise one search widget instance.
		 *
		 * All DOM queries are scoped to the specific form element so that
		 * multiple widgets on the same page never interfere with each other.
		 *
		 * @param {jQuery} $form - The .bbpc_search_form_wrapper element for this instance.
		 */
		function initSearchWidget( $form ) {
			var inputId  = $form.data( 'input-id' );
			var resultId = $form.data( 'result-id' );

			var $input   = $( '#' + inputId );
			var $result  = $( '#' + resultId );
			var $overlay = $( '.bbpc-search-overlay[data-widget-id="' + $form.data( 'widget-id' ) + '"]' );

			if ( ! $input.length ) {
				return;
			}

			/**
			 * Highlight the matching keyword inside result links.
			 *
			 * @param {string} keyword - Current search term.
			 */
			function highlightKeyword( keyword ) {
				var isHighlight = $input.data( 'highlight' );
				if ( ! isHighlight || ! keyword ) {
					return;
				}

				var escaped = keyword.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
				var regex   = new RegExp( '(' + escaped + ')', 'gi' );

				$result.find( '.search-result-item .bbpc-result-title' ).each( function () {
					var text        = $( this ).text();
					var highlighted = text.replace( regex, '<mark class="frmx-search-highlight">$1</mark>' );
					$( this ).html( highlighted );
				} );
			}

			/**
			 * Run an AJAX search request and populate the result container.
			 *
			 * @param {string} keyword  - The search term.
			 * @param {string} postType - The post type to search.
			 */
			function runAjaxSearch( keyword, postType ) {
				var ajaxUrl = forumax_localize_script.ajaxurl; /* eslint-disable-line camelcase */
				var nonce   = forumax_localize_script.nonce;   /* eslint-disable-line camelcase */

				$.ajax( {
					url    : ajaxUrl,
					method : 'POST',
					data   : {
						action    : 'forumax_search_data_fetch',
						keyword   : keyword,
						post_type : postType,
						nonce     : nonce
					},
					beforeSend: function () {
						$form.find( '.spinner' ).css( 'display', 'inline-flex' );
					},
					success: function ( data ) {
						$result.html( data ).addClass( 'ajax-search' );
						$form.find( '.spinner' ).hide();

						// Check for "no results" marker returned by the server.
						var noResult = $result.find( '.tab-item.active.all-active' ).attr( 'data-noresult' );
						if ( noResult ) {
							var msg = noResult.replace( /-/g, ' ' );
							$result.html( '<h5 class="bbpc-not-found-text">' + msg + '</h5>' );
						} else {
							highlightKeyword( keyword );
						}
					}
				} );
			}

			// --------------- Overlay ------------------

			$input.add( $result ).add( $form.find( '.bbpc-search-keyword ul li a' ) ).on( 'click', function () {
				$overlay.css( 'display', 'block' ).addClass( 'active' );
			} );

			$overlay.on( 'click', function () {
				$overlay.css( 'display', 'none' ).removeClass( 'active' );
				// Close the results box so the input returns to its pill shape.
				$result.removeClass( 'ajax-search' ).empty();
			} );

			// --------------- Focus states ------------------

			$form.on( 'focusin', function () {
				if ( $( 'body' ).hasClass( 'body_dark' ) ) {
					$input.addClass( 'input_focused' );
				}
			} );

			$form.on( 'focusout', function () {
				if ( $( 'body' ).hasClass( 'body_dark' ) ) {
					if ( ! $result.hasClass( 'ajax-search' ) ) {
						$input.removeClass( 'input_focused' );
					}
				}
			} );

			$input.on( 'focus', function () {
				$( 'body' ).addClass( 'bbpc-search-open' );
				$form.css( { position: 'relative', 'z-index': '999' } );
			} );

			$overlay.on( 'click', function () {
				$( 'body' ).removeClass( 'bbpc-search-open' );
				$form.css( { 'z-index': '', position: '' } );
			} );

			// --------------- Clear input ------------------

			$input.on( 'input', function () {
				if ( this.value === '' ) {
					$result.removeClass( 'ajax-search' ).empty();
				}
			} );

			// --------------- Result type tabs ------------------
			// Delegated so it works on the markup injected by each AJAX response.

			$result.on( 'click', '.bbpc-tab', function () {
				var tab = $( this ).data( 'tab' );

				$result.find( '.bbpc-tab' ).removeClass( 'active' );
				$( this ).addClass( 'active' );

				if ( tab === 'all' ) {
					$result.find( '.bbpc-result-group' ).show();
				} else {
					$result.find( '.bbpc-result-group' ).hide();
					$result.find( '.bbpc-result-group[data-type="' + tab + '"]' ).show();
				}
			} );

			// --------------- AJAX live search ------------------

			var isAjaxSearch = $input.data( 'ajax-search' );

			if ( isAjaxSearch ) {
				var debounceTimer;

				$input.on( 'keyup', function () {
					$form.find( '.bbpc-not-found-text' ).hide();
					clearTimeout( debounceTimer );

					var keyword  = $( this ).val();
					var postType = $( this ).data( 'post-type' ) || '';

					debounceTimer = setTimeout( function () {
						if ( keyword !== '' ) {
							runAjaxSearch( keyword, postType );
						}
					}, 1000 );
				} );
			}

			// --------------- Keyword pill click ------------------

			$form.find( '.bbpc-search-keyword ul li a' ).on( 'click', function ( e ) {
				e.preventDefault();

				var keyword  = $( this ).text().trim();
				var postType = $input.data( 'post-type' ) || '';

				$input.val( keyword ).focus();

				if ( ! isAjaxSearch || keyword === '' ) {
					return;
				}

				runAjaxSearch( keyword, postType );
			} );
		}

		// --------------- Bootstrap: find every widget instance ------------------

		$( '.bbpc_search_form_wrapper[data-widget-id]' ).each( function () {
			initSearchWidget( $( this ) );
		} );

		// --------------- TinyMCE dark mode theming ------------------

		/**
		 * Style a single TinyMCE editor's iframe body to match the current
		 * light/dark mode. Toolbar, statusbar and popup dialogs are themed
		 * via CSS (body.body_dark selectors) since they live in the parent
		 * document, but the iframe body is a separate document that CSS
		 * cascades can't reach.
		 *
		 * @param {Object} editor - A tinymce.Editor instance.
		 */
		function frmxThemeTinyMCEEditor( editor ) {
			if ( ! editor || ! editor.getBody ) {
				return;
			}

			var isDark = document.body.classList.contains( 'body_dark' );
			var body   = editor.getBody();

			if ( body ) {
				body.style.backgroundColor = isDark ? '#1e1e2a' : '#fff';
				body.style.fontFamily      = "'Inter', 'Roboto', -apple-system, sans-serif";
				body.style.fontSize        = '14px';
				body.style.lineHeight      = '1.6';
				body.style.color           = isDark ? '#e4e6eb' : '#1a1a2e';
				body.style.padding         = '14px 18px';
			}

			var container = editor.getContainer();
			if ( container ) {
				container.style.border     = 'none';
				container.style.boxShadow  = 'none';
			}
		}

		function frmxThemeAllTinyMCEEditors() {
			if ( typeof tinymce === 'undefined' ) {
				return;
			}

			tinymce.editors.forEach( frmxThemeTinyMCEEditor );
		}

		if ( typeof tinymce !== 'undefined' ) {
			tinymce.on( 'AddEditor', function ( e ) {
				e.editor.on( 'init', function () {
					frmxThemeTinyMCEEditor( this );
				} );
			} );
		}

		// Re-theme open editors whenever dark mode is toggled, regardless
		// of which switcher/script flips the `body_dark` class.
		if ( window.MutationObserver ) {
			new MutationObserver( frmxThemeAllTinyMCEEditors )
				.observe( document.body, { attributes: true, attributeFilter: [ 'class' ] } );
		}
	} );
} )( jQuery ); /* eslint-disable-line no-undef */

```
