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

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

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