PluginProbe
Forumax – AI Powered Advanced Community Forum Plugin / 2.4.3
Forumax – AI Powered Advanced Community Forum Plugin v2.4.3
2.4.4 2.4.3 2.4.2 2.4.1 2.4.0 trunk 1.0.8 1.1.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 2.0.0 2.1.0 All 29 releases
bbp-core / assets / frontend / js / frontend.js

frontend.js in Forumax – AI Powered Advanced Community Forum Plugin 2.4.3, at assets/frontend/js/frontend.js

251 lines 7.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function ( $ ) {
2 $( document ).ready( function () {
3
4 /**
5 * Initialise one search widget instance.
6 *
7 * All DOM queries are scoped to the specific form element so that
8 * multiple widgets on the same page never interfere with each other.
9 *
10 * @param {jQuery} $form - The .bbpc_search_form_wrapper element for this instance.
11 */
12 function initSearchWidget( $form ) {
13 var inputId = $form.data( 'input-id' );
14 var resultId = $form.data( 'result-id' );
15
16 var $input = $( '#' + inputId );
17 var $result = $( '#' + resultId );
18 var $overlay = $( '.bbpc-search-overlay[data-widget-id="' + $form.data( 'widget-id' ) + '"]' );
19
20 if ( ! $input.length ) {
21 return;
22 }
23
24 /**
25 * Highlight the matching keyword inside result links.
26 *
27 * @param {string} keyword - Current search term.
28 */
29 function highlightKeyword( keyword ) {
30 var isHighlight = $input.data( 'highlight' );
31 if ( ! isHighlight || ! keyword ) {
32 return;
33 }
34
35 var escaped = keyword.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
36 var regex = new RegExp( '(' + escaped + ')', 'gi' );
37
38 $result.find( '.search-result-item .bbpc-result-title' ).each( function () {
39 var text = $( this ).text();
40 var highlighted = text.replace( regex, '<mark class="frmx-search-highlight">$1</mark>' );
41 $( this ).html( highlighted );
42 } );
43 }
44
45 /**
46 * Run an AJAX search request and populate the result container.
47 *
48 * @param {string} keyword - The search term.
49 * @param {string} postType - The post type to search.
50 */
51 function runAjaxSearch( keyword, postType ) {
52 var ajaxUrl = forumax_localize_script.ajaxurl; /* eslint-disable-line camelcase */
53 var nonce = forumax_localize_script.nonce; /* eslint-disable-line camelcase */
54
55 $.ajax( {
56 url : ajaxUrl,
57 method : 'POST',
58 data : {
59 action : 'forumax_search_data_fetch',
60 keyword : keyword,
61 post_type : postType,
62 nonce : nonce
63 },
64 beforeSend: function () {
65 $form.find( '.spinner' ).css( 'display', 'inline-flex' );
66 },
67 success: function ( data ) {
68 $result.html( data ).addClass( 'ajax-search' );
69 $form.find( '.spinner' ).hide();
70
71 // Check for "no results" marker returned by the server.
72 var noResult = $result.find( '.tab-item.active.all-active' ).attr( 'data-noresult' );
73 if ( noResult ) {
74 var msg = noResult.replace( /-/g, ' ' );
75 $result.html( '<h5 class="bbpc-not-found-text">' + msg + '</h5>' );
76 } else {
77 highlightKeyword( keyword );
78 }
79 }
80 } );
81 }
82
83 // --------------- Overlay ------------------
84
85 $input.add( $result ).add( $form.find( '.bbpc-search-keyword ul li a' ) ).on( 'click', function () {
86 $overlay.css( 'display', 'block' ).addClass( 'active' );
87 } );
88
89 $overlay.on( 'click', function () {
90 $overlay.css( 'display', 'none' ).removeClass( 'active' );
91 // Close the results box so the input returns to its pill shape.
92 $result.removeClass( 'ajax-search' ).empty();
93 } );
94
95 // --------------- Focus states ------------------
96
97 $form.on( 'focusin', function () {
98 if ( $( 'body' ).hasClass( 'body_dark' ) ) {
99 $input.addClass( 'input_focused' );
100 }
101 } );
102
103 $form.on( 'focusout', function () {
104 if ( $( 'body' ).hasClass( 'body_dark' ) ) {
105 if ( ! $result.hasClass( 'ajax-search' ) ) {
106 $input.removeClass( 'input_focused' );
107 }
108 }
109 } );
110
111 $input.on( 'focus', function () {
112 $( 'body' ).addClass( 'bbpc-search-open' );
113 $form.css( { position: 'relative', 'z-index': '999' } );
114 } );
115
116 $overlay.on( 'click', function () {
117 $( 'body' ).removeClass( 'bbpc-search-open' );
118 $form.css( { 'z-index': '', position: '' } );
119 } );
120
121 // --------------- Clear input ------------------
122
123 $input.on( 'input', function () {
124 if ( this.value === '' ) {
125 $result.removeClass( 'ajax-search' ).empty();
126 }
127 } );
128
129 // --------------- Result type tabs ------------------
130 // Delegated so it works on the markup injected by each AJAX response.
131
132 $result.on( 'click', '.bbpc-tab', function () {
133 var tab = $( this ).data( 'tab' );
134
135 $result.find( '.bbpc-tab' ).removeClass( 'active' );
136 $( this ).addClass( 'active' );
137
138 if ( tab === 'all' ) {
139 $result.find( '.bbpc-result-group' ).show();
140 } else {
141 $result.find( '.bbpc-result-group' ).hide();
142 $result.find( '.bbpc-result-group[data-type="' + tab + '"]' ).show();
143 }
144 } );
145
146 // --------------- AJAX live search ------------------
147
148 var isAjaxSearch = $input.data( 'ajax-search' );
149
150 if ( isAjaxSearch ) {
151 var debounceTimer;
152
153 $input.on( 'keyup', function () {
154 $form.find( '.bbpc-not-found-text' ).hide();
155 clearTimeout( debounceTimer );
156
157 var keyword = $( this ).val();
158 var postType = $( this ).data( 'post-type' ) || '';
159
160 debounceTimer = setTimeout( function () {
161 if ( keyword !== '' ) {
162 runAjaxSearch( keyword, postType );
163 }
164 }, 1000 );
165 } );
166 }
167
168 // --------------- Keyword pill click ------------------
169
170 $form.find( '.bbpc-search-keyword ul li a' ).on( 'click', function ( e ) {
171 e.preventDefault();
172
173 var keyword = $( this ).text().trim();
174 var postType = $input.data( 'post-type' ) || '';
175
176 $input.val( keyword ).focus();
177
178 if ( ! isAjaxSearch || keyword === '' ) {
179 return;
180 }
181
182 runAjaxSearch( keyword, postType );
183 } );
184 }
185
186 // --------------- Bootstrap: find every widget instance ------------------
187
188 $( '.bbpc_search_form_wrapper[data-widget-id]' ).each( function () {
189 initSearchWidget( $( this ) );
190 } );
191
192 // --------------- TinyMCE dark mode theming ------------------
193
194 /**
195 * Style a single TinyMCE editor's iframe body to match the current
196 * light/dark mode. Toolbar, statusbar and popup dialogs are themed
197 * via CSS (body.body_dark selectors) since they live in the parent
198 * document, but the iframe body is a separate document that CSS
199 * cascades can't reach.
200 *
201 * @param {Object} editor - A tinymce.Editor instance.
202 */
203 function frmxThemeTinyMCEEditor( editor ) {
204 if ( ! editor || ! editor.getBody ) {
205 return;
206 }
207
208 var isDark = document.body.classList.contains( 'body_dark' );
209 var body = editor.getBody();
210
211 if ( body ) {
212 body.style.backgroundColor = isDark ? '#1e1e2a' : '#fff';
213 body.style.fontFamily = "'Inter', 'Roboto', -apple-system, sans-serif";
214 body.style.fontSize = '14px';
215 body.style.lineHeight = '1.6';
216 body.style.color = isDark ? '#e4e6eb' : '#1a1a2e';
217 body.style.padding = '14px 18px';
218 }
219
220 var container = editor.getContainer();
221 if ( container ) {
222 container.style.border = 'none';
223 container.style.boxShadow = 'none';
224 }
225 }
226
227 function frmxThemeAllTinyMCEEditors() {
228 if ( typeof tinymce === 'undefined' ) {
229 return;
230 }
231
232 tinymce.editors.forEach( frmxThemeTinyMCEEditor );
233 }
234
235 if ( typeof tinymce !== 'undefined' ) {
236 tinymce.on( 'AddEditor', function ( e ) {
237 e.editor.on( 'init', function () {
238 frmxThemeTinyMCEEditor( this );
239 } );
240 } );
241 }
242
243 // Re-theme open editors whenever dark mode is toggled, regardless
244 // of which switcher/script flips the `body_dark` class.
245 if ( window.MutationObserver ) {
246 new MutationObserver( frmxThemeAllTinyMCEEditors )
247 .observe( document.body, { attributes: true, attributeFilter: [ 'class' ] } );
248 }
249 } );
250 } )( jQuery ); /* eslint-disable-line no-undef */
251