PluginProbe
Forumax – AI Powered Advanced Community Forum Plugin / 2.5.1
Forumax – AI Powered Advanced Community Forum Plugin v2.5.1
2.5.1 2.5.0 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 All 31 releases
bbp-core / assets / frontend / js / frontend.js

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

282 lines 8.3 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 var searchRequest = null;
52
53 function runAjaxSearch( keyword, postType ) {
54 var ajaxUrl = forumax_localize_script.ajaxurl; /* eslint-disable-line camelcase */
55 var nonce = forumax_localize_script.nonce; /* eslint-disable-line camelcase */
56
57 // Drop the in-flight request so a slower, older response can't
58 // overwrite the results for the latest keyword.
59 if ( searchRequest ) {
60 searchRequest.abort();
61 }
62
63 searchRequest = $.ajax( {
64 url : ajaxUrl,
65 method : 'POST',
66 data : {
67 action : 'forumax_search_data_fetch',
68 keyword : keyword,
69 post_type : postType,
70 nonce : nonce
71 },
72 beforeSend: function () {
73 $form.find( '.spinner' ).css( 'display', 'inline-flex' );
74 },
75 success: function ( data ) {
76 $result.html( data ).addClass( 'ajax-search' );
77 $form.find( '.spinner' ).hide();
78
79 // Check for "no results" marker returned by the server.
80 var noResult = $result.find( '.tab-item.active.all-active' ).attr( 'data-noresult' );
81 if ( noResult ) {
82 var msg = noResult.replace( /-/g, ' ' );
83 var hint = $result.attr( 'data-noresult-hint' ) || '';
84 var hintTag = hint
85 ? '<p class="bbpc-search-empty-hint">' + $( '<div>' ).text( hint ).html() + '</p>'
86 : '';
87
88 $result.html(
89 '<div class="bbpc-search-empty" role="status">' +
90 '<h4 class="bbpc-not-found-text">' + $( '<div>' ).text( msg ).html() + '</h4>' +
91 hintTag +
92 '</div>'
93 );
94 } else {
95 highlightKeyword( keyword );
96 }
97 },
98 complete: function ( xhr, status ) {
99 if ( 'abort' !== status ) {
100 searchRequest = null;
101 $form.find( '.spinner' ).hide();
102 }
103 }
104 } );
105 }
106
107 // --------------- Overlay ------------------
108
109 $input.add( $result ).add( $form.find( '.bbpc-search-keyword ul li a' ) ).on( 'click', function () {
110 $overlay.css( 'display', 'block' ).addClass( 'active' );
111 } );
112
113 $overlay.on( 'click', function () {
114 $overlay.css( 'display', 'none' ).removeClass( 'active' );
115 // Close the results box so the input returns to its pill shape.
116 $result.removeClass( 'ajax-search' ).empty();
117 } );
118
119 // --------------- Focus states ------------------
120
121 $form.on( 'focusin', function () {
122 if ( $( 'body' ).hasClass( 'body_dark' ) ) {
123 $input.addClass( 'input_focused' );
124 }
125 } );
126
127 $form.on( 'focusout', function () {
128 if ( $( 'body' ).hasClass( 'body_dark' ) ) {
129 if ( ! $result.hasClass( 'ajax-search' ) ) {
130 $input.removeClass( 'input_focused' );
131 }
132 }
133 } );
134
135 $input.on( 'focus', function () {
136 $( 'body' ).addClass( 'bbpc-search-open' );
137 $form.css( { position: 'relative', 'z-index': '999' } );
138 } );
139
140 $overlay.on( 'click', function () {
141 $( 'body' ).removeClass( 'bbpc-search-open' );
142 $form.css( { 'z-index': '', position: '' } );
143 } );
144
145 // --------------- Clear input ------------------
146
147 $input.on( 'input', function () {
148 if ( this.value === '' ) {
149 $result.removeClass( 'ajax-search' ).empty();
150 }
151 } );
152
153 // --------------- Result type tabs ------------------
154 // Delegated so it works on the markup injected by each AJAX response.
155
156 $result.on( 'click', '.bbpc-tab', function () {
157 var tab = $( this ).data( 'tab' );
158
159 $result.find( '.bbpc-tab' ).removeClass( 'active' );
160 $( this ).addClass( 'active' );
161
162 if ( tab === 'all' ) {
163 $result.find( '.bbpc-result-group' ).show();
164 } else {
165 $result.find( '.bbpc-result-group' ).hide();
166 $result.find( '.bbpc-result-group[data-type="' + tab + '"]' ).show();
167 }
168 } );
169
170 // --------------- AJAX live search ------------------
171
172 var isAjaxSearch = $input.data( 'ajax-search' );
173
174 if ( isAjaxSearch ) {
175 var debounceTimer;
176 var lastKeyword = '';
177
178 $input.on( 'keyup', function () {
179 var keyword = $.trim( $( this ).val() );
180 var postType = $( this ).data( 'post-type' ) || '';
181
182 // Arrow keys, Shift, etc. don't change the query.
183 if ( keyword === lastKeyword ) {
184 return;
185 }
186
187 lastKeyword = keyword;
188 $form.find( '.bbpc-not-found-text' ).hide();
189 clearTimeout( debounceTimer );
190
191 debounceTimer = setTimeout( function () {
192 if ( keyword !== '' ) {
193 runAjaxSearch( keyword, postType );
194 }
195 }, 400 );
196 } );
197 }
198
199 // --------------- Keyword pill click ------------------
200
201 $form.find( '.bbpc-search-keyword ul li a' ).on( 'click', function ( e ) {
202 e.preventDefault();
203
204 var keyword = $( this ).text().trim();
205 var postType = $input.data( 'post-type' ) || '';
206
207 $input.val( keyword ).focus();
208
209 if ( ! isAjaxSearch || keyword === '' ) {
210 return;
211 }
212
213 runAjaxSearch( keyword, postType );
214 } );
215 }
216
217 // --------------- Bootstrap: find every widget instance ------------------
218
219 $( '.bbpc_search_form_wrapper[data-widget-id]' ).each( function () {
220 initSearchWidget( $( this ) );
221 } );
222
223 // --------------- TinyMCE dark mode theming ------------------
224
225 /**
226 * Style a single TinyMCE editor's iframe body to match the current
227 * light/dark mode. Toolbar, statusbar and popup dialogs are themed
228 * via CSS (body.body_dark selectors) since they live in the parent
229 * document, but the iframe body is a separate document that CSS
230 * cascades can't reach.
231 *
232 * @param {Object} editor - A tinymce.Editor instance.
233 */
234 function frmxThemeTinyMCEEditor( editor ) {
235 if ( ! editor || ! editor.getBody ) {
236 return;
237 }
238
239 var isDark = document.body.classList.contains( 'body_dark' );
240 var body = editor.getBody();
241
242 if ( body ) {
243 body.style.backgroundColor = isDark ? '#1e1e2a' : '#fff';
244 body.style.fontFamily = "'Inter', 'Roboto', -apple-system, sans-serif";
245 body.style.fontSize = '14px';
246 body.style.lineHeight = '1.6';
247 body.style.color = isDark ? '#e4e6eb' : '#1a1a2e';
248 body.style.padding = '14px 18px';
249 }
250
251 var container = editor.getContainer();
252 if ( container ) {
253 container.style.border = 'none';
254 container.style.boxShadow = 'none';
255 }
256 }
257
258 function frmxThemeAllTinyMCEEditors() {
259 if ( typeof tinymce === 'undefined' ) {
260 return;
261 }
262
263 tinymce.editors.forEach( frmxThemeTinyMCEEditor );
264 }
265
266 if ( typeof tinymce !== 'undefined' ) {
267 tinymce.on( 'AddEditor', function ( e ) {
268 e.editor.on( 'init', function () {
269 frmxThemeTinyMCEEditor( this );
270 } );
271 } );
272 }
273
274 // Re-theme open editors whenever dark mode is toggled, regardless
275 // of which switcher/script flips the `body_dark` class.
276 if ( window.MutationObserver ) {
277 new MutationObserver( frmxThemeAllTinyMCEEditors )
278 .observe( document.body, { attributes: true, attributeFilter: [ 'class' ] } );
279 }
280 } );
281 } )( jQuery ); /* eslint-disable-line no-undef */
282