PluginProbe ʕ •ᴥ•ʔ
Admin Help Docs / trunk
Admin Help Docs vtrunk
2.0.1.1 trunk 1.4.3.2 2.0.0 2.0.0.1 2.0.0.2 2.0.1
admin-help-docs / inc / tabs / js / settings.js
admin-help-docs / inc / tabs / js Last commit date
admin-menu.js 3 months ago documentation.js 3 months ago import.js 3 months ago settings.js 3 months ago support.js 3 months ago
settings.js
706 lines
1 jQuery( document ).ready( function( $ ) {
2
3
4 // Dirty state tracking
5 const saveReminder = $( '#helpdocs-save-reminder' );
6 let isDirty = false;
7
8 // Initialize Code Editor for Custom CSS
9 if ( $( '#main_docs_css' ).length && typeof helpdocs_settings !== 'undefined' && helpdocs_settings.editor_settings ) {
10 cssEditor = wp.codeEditor.initialize( 'main_docs_css', helpdocs_settings.editor_settings );
11
12 // CodeMirror has its own change event
13 cssEditor.codemirror.on( 'change', function() {
14 markDirty();
15 });
16 }
17
18 function markDirty() {
19 if ( ! isDirty ) {
20 isDirty = true;
21 saveReminder.fadeIn( 150 );
22 $( '#helpdocs-save-status' ).remove();
23 }
24 }
25
26 function clearDirty() {
27 isDirty = false;
28 saveReminder.hide();
29 }
30
31 $( window ).on( 'beforeunload', function( e ) {
32 if( isDirty ){
33 e.preventDefault();
34 e.returnValue = '';
35 return '';
36 }
37 });
38
39 $( document ).on( 'change input', '.helpdocs-settings-grid [name]', function() {
40 markDirty();
41 });
42
43 // Show/hide conditional fields based on their target field's value
44 function updateConditionalFields() {
45 $( '.helpdocs-field[data-condition-field]' ).each( function() {
46 var $field = $( this );
47 var targetId = $field.data( 'condition-field' );
48 var expectedValue = $field.data( 'condition-value' );
49 var $target = $( '#' + targetId );
50
51 if ( $target.length ) {
52 var isVisible = false;
53
54 if ( $target.is( ':checkbox' ) ) {
55 // If it's a checkbox, we check if it's checked
56 // and if that state matches our expected value (1 for checked)
57 var isChecked = $target.is( ':checked' ) ? '1' : '0';
58 isVisible = ( isChecked == expectedValue );
59 } else {
60 // For select/text fields
61 isVisible = ( $target.val() == expectedValue );
62 }
63
64 if ( isVisible ) {
65 $field.removeClass( 'condition-hide' );
66 } else {
67 $field.addClass( 'condition-hide' );
68 }
69 }
70 } );
71 }
72
73 $( document ).on( 'change', '.has-condition, .has-condition input, .has-condition select', function() {
74 updateConditionalFields();
75 } );
76
77 // Menu Title
78 $( document ).on( 'input', '#helpdocs_field_menu_title input', function() {
79 var title = $( this ).val();
80 const target = $( 'li#toplevel_page_admin-help-docs .wp-menu-name' );
81
82 if ( title.length > 0 ) {
83 target.text( title );
84 } else {
85 target.text( '...' );
86 }
87 } );
88
89 // Dashicon
90 $( document ).on( 'change', '#helpdocs_field_dashicon select', function() {
91 var icon = $( this ).val();
92 const target = $( 'li#toplevel_page_admin-help-docs .wp-menu-image' );
93 target.removeClass().addClass( 'wp-menu-image dashicons-before dashicons-' + icon );
94 } );
95
96 // Page Title
97 $( document ).on( 'input', '#helpdocs_field_page_title input', function() {
98 var title = $( this ).val();
99 const target = $( '#helpdocs-header h1' );
100 if ( title.length > 0 ) {
101 target.text( title );
102 } else {
103 target.text( '...' );
104 }
105 } );
106
107 // Logo
108 $( document ).on( 'focusout paste', '#helpdocs_field_logo input', function( e ) {
109 const $input = $( this );
110 const target = $( '#helpdocs-header .logo' );
111
112 // For paste, delay slightly to get the pasted value
113 const updateLogo = function() {
114 const url = $input.val();
115 if ( url.length > 0 ) {
116 target.attr( 'src', url ).show();
117 } else {
118 target.hide();
119 }
120 };
121
122 if ( e.type === 'paste' ) {
123 setTimeout( updateLogo, 50 );
124 } else {
125 updateLogo();
126 }
127 } );
128
129 // Utility: hex → RGB
130 function hexToRgb( hex ) {
131 hex = hex.replace( '#', '' );
132 if ( hex.length === 3 ) hex = hex.split( '' ).map( c => c + c ).join( '' );
133 const bigint = parseInt( hex, 16 );
134 return [ (bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255 ];
135 }
136
137 // Utility: relative luminance
138 function getLuminance( hex ) {
139 const [ r, g, b ] = hexToRgb( hex ).map( c => {
140 c /= 255;
141 return c <= 0.03928 ? c / 12.92 : Math.pow( (c + 0.055) / 1.055, 2.4 );
142 } );
143 return 0.2126 * r + 0.7152 * g + 0.0722 * b;
144 }
145
146 // Update logo based on header_bg
147 function updateLogoContrast( bgColor, inputSelector, previewSelector = null, themeKey = null ) {
148 const $input = $( inputSelector );
149 const current = $input.val() || '';
150 const defaultLogo = helpdocs_settings.default_logo;
151
152 // Get the plugin's default image directory
153 let defaultDir = defaultLogo.substring( 0, defaultLogo.lastIndexOf( '/' ) + 1 );
154
155 const decodedCurrent = decodeURIComponent( current );
156 const normalizedDefaultDir = decodeURIComponent( defaultDir );
157
158 // Only proceed if the current logo is one of our default assets
159 if ( decodedCurrent.startsWith( normalizedDefaultDir ) ) {
160
161 if ( inputSelector === '#doc_logo' && themeKey === 'classic' ) {
162 $input.val( defaultLogo );
163
164 } else {
165 let newFilename = 'logo.png'; // default blue
166 const luminance = getLuminance( bgColor );
167 if ( luminance < 0.4 ) {
168 newFilename = 'logo-white.png';
169 } else if ( luminance > 0.75 ) {
170 newFilename = 'logo-black.png';
171 }
172
173 const newLogo = normalizedDefaultDir + newFilename;
174
175 if ( newLogo !== decodedCurrent ) {
176 // Always update the input value
177 $input.val( newLogo );
178
179 // Only update an image preview if a selector was provided
180 if ( previewSelector ) {
181 $( previewSelector ).attr( 'src', newLogo );
182 }
183 }
184 }
185 }
186 }
187
188 // Theme change handler
189 $( document ).on( 'change', '#helpdocs_field_themes select', function() {
190 const themeKey = $( this ).val();
191 const theme = helpdocs_settings.themes[ themeKey ];
192 if ( theme && theme.colors ) {
193 for ( const [ key, color ] of Object.entries( theme.colors ) ) {
194 const variable = '--helpdocs-color-' + key.replaceAll( '_', '-' );
195 document.documentElement.style.setProperty( variable, color );
196 $( '#color_' + key ).val( color );
197 }
198
199 // Update logo based on header_bg (updates preview + input)
200 if ( theme.colors.header_bg ) {
201 updateLogoContrast( theme.colors.header_bg, '#logo', '#helpdocs-header .logo' );
202 }
203
204 // Update doc logo based on doc_bg (updates input ONLY)
205 if ( theme.colors.doc_bg ) {
206 updateLogoContrast( theme.colors.doc_bg, '#doc_logo', null, themeKey );
207 }
208 }
209 });
210
211 // Color picker live update
212 $( document ).on( 'input', 'input[type="color"]', function() {
213 const input = $( this );
214 const fieldId = input.attr( 'id' );
215 const variable = '--helpdocs-' + fieldId.replaceAll( '_', '-' );
216 const color = input.val();
217 document.documentElement.style.setProperty( variable, color );
218 $( '#helpdocs_field_themes select' ).val( 'custom' );
219
220 // If header_bg changes, update both preview and input
221 if ( fieldId === 'color_header_bg' ) {
222 updateLogoContrast( color, '#logo', '#helpdocs-header .logo' );
223 }
224
225 // If doc_bg changes, update input only
226 if ( fieldId === 'color_doc_bg' ) {
227 updateLogoContrast( color, '#doc_logo' );
228 }
229 });
230
231 // Footer Text
232 $( document ).on( 'input', '#helpdocs_field_footer_left textarea', function() {
233 let text = $( this ).val();
234 text = text.replace( /{version}/g, helpdocs_settings.wp_version );
235 const target = $( '#footer-left' );
236 if ( text.length > 0 ) {
237 target.html( text );
238 } else {
239 target.text( '...' );
240 }
241 } );
242
243 $( document ).on( 'input', '#helpdocs_field_footer_right textarea', function() {
244 let text = $( this ).val();
245 text = text.replace( /{version}/g, helpdocs_settings.wp_version );
246 const target = $( '#footer-upgrade' );
247 if ( text.length > 0 ) {
248 target.html( text );
249 } else {
250 target.text( '...' );
251 }
252 } );
253
254 // Saving
255 const $saveButton = $( '#helpdocs-subheader .tab-button' );
256 const originalText = $saveButton.text();
257 let savingInterval;
258
259 // Start animated "Saving..." in browser tab
260 function startSavingTitle() {
261 const originalTitle = document.title;
262 let dots = 0;
263
264 document.title = helpdocs_settings.saving_text; // immediate first update
265
266 savingInterval = setInterval( function() {
267 dots = (dots + 1) % 4; // cycles 0 → 3
268 document.title = helpdocs_settings.saving_text + '.'.repeat(dots);
269 }, 500 );
270
271 return originalTitle;
272 }
273
274 // Stop animation and restore tab title
275 function stopSavingTitle(originalTitle) {
276 clearInterval(savingInterval);
277 document.title = originalTitle;
278 }
279
280 function showSaving() {
281 $saveButton.prop( 'disabled', true ).html( '<span class="dashicons dashicons-update spin"></span> ' + helpdocs_settings.saving_text + '...' );
282 $( '#helpdocs-save-status' ).remove();
283 }
284
285 function showResult( message, success = true ) {
286 $saveButton.prop( 'disabled', false ).text( originalText );
287 const $status = $( '<span id="helpdocs-save-status"></span>' ).text( message );
288 $status.css({
289 marginLeft: '10px',
290 color: success ? 'green' : 'red',
291 fontWeight: 'bold'
292 });
293 $saveButton.after( $status );
294 }
295
296 function gatherSettings() {
297 if ( cssEditor ) {
298 cssEditor.codemirror.save();
299 }
300
301 const data = {};
302 $( '.helpdocs-settings-grid [name]' ).each( function() {
303 const $field = $( this );
304 let val;
305
306 if ( $field.attr( 'type' ) === 'checkbox' ) {
307 if ( $field.is( ':checkbox' ) && $field.attr( 'name' ).endsWith( '[]' ) ) {
308 val = $( '[name="' + $field.attr( 'name' ) + '"]:checked' ).map( function() { return this.value; } ).get();
309 } else {
310 val = $field.is( ':checked' ) ? 1 : 0;
311 }
312 } else {
313 val = $field.val();
314 }
315
316 let key = $field.attr( 'name' ).replace( /\[\]$/, '' );
317 data[ key ] = val;
318 });
319 return data;
320 }
321
322 function saveSettings() {
323 const settings = gatherSettings();
324 const originalTitle = startSavingTitle();
325 showSaving();
326
327 $.ajax({
328 url: ajaxurl,
329 method: 'POST',
330 dataType: 'json',
331 data: {
332 action: 'helpdocs_save_settings',
333 nonce: helpdocs_settings.nonce,
334 settings: settings
335 },
336 success: function( response ) {
337 stopSavingTitle( originalTitle );
338 if ( response.success ) {
339 showResult( helpdocs_settings.saved_text );
340 clearDirty();
341 } else {
342 showResult( response.data || helpdocs_settings.error_text, false );
343 }
344 },
345 error: function() {
346 stopSavingTitle( originalTitle );
347 showResult( helpdocs_settings.error_text, false );
348 }
349 });
350 }
351
352 $saveButton.on( 'click', saveSettings );
353
354 $( document ).on( 'keydown', function( e ) {
355 if ( ( e.ctrlKey || e.metaKey ) && e.key.toLowerCase() === 's' ) {
356 e.preventDefault();
357 saveSettings();
358 }
359 } );
360
361 // Apply live updates for a specific field
362 function applyLiveUpdates( $field ) {
363 const type = $field.attr( 'type' );
364 const id = $field.attr( 'id' );
365
366 if ( type === 'color' ) {
367 const variable = '--helpdocs-' + id.replaceAll( '_', '-' );
368 document.documentElement.style.setProperty( variable, $field.val() );
369 } else if ( id === 'menu_title' ) {
370 $( 'li#toplevel_page_admin-help-docs .wp-menu-name' ).text( $field.val() || '...' );
371 } else if ( id === 'page_title' ) {
372 $( '#helpdocs-header h1' ).text( $field.val() || '...' );
373 } else if ( id === 'dashicon' ) {
374 $( 'li#toplevel_page_admin-help-docs .wp-menu-image' )
375 .removeClass()
376 .addClass( 'wp-menu-image dashicons-before dashicons-' + $field.val() );
377 } else if ( id === 'logo' ) {
378 const target = $( '#helpdocs-header .logo' );
379 const url = $field.val();
380 url ? target.attr( 'src', url ).show() : target.hide();
381 } else if ( id === 'helpdocs_footer_left' || id === 'helpdocs_footer_right' ) {
382 const target = id === 'helpdocs_footer_left' ? $( '#footer-left' ) : $( '#footer-upgrade' );
383 let value = $field.val();
384 if ( id === 'helpdocs_footer_right' ) value = value.replace( /{version}/g, helpdocs_settings.wp_version );
385 target.html( value.length ? value : '...' );
386 }
387
388 updateConditionalFields();
389 }
390
391 // Call this after any bulk operation
392 function updateAllLive() {
393 $( '.helpdocs-settings-grid [name]' ).each( function() {
394 console.log( 'Updating live for', this );
395 applyLiveUpdates( $( this ) );
396 });
397 }
398
399 // Reset Settings
400 $( '#helpdocs-reset-colors' ).on( 'click', function( e ) {
401 e.preventDefault();
402
403 if ( ! confirm( 'Are you sure you want to reset all colors to their defaults?' ) ) {
404 return;
405 }
406
407 helpdocs_settings.settings.forEach( function( field ) {
408 if ( field.type === 'color' && field.default ) {
409 $( '#' + field.name )
410 .val( field.default );
411 }
412 } );
413
414 updateAllLive();
415 } );
416
417 $( '#helpdocs-reset-settings' ).on( 'click', function( e ) {
418 e.preventDefault();
419
420 if ( ! confirm( 'Are you sure you want to reset ALL settings to their defaults?' ) ) {
421 return;
422 }
423
424 helpdocs_settings.settings.forEach( function( field ) {
425 if ( field.type === 'html' ) {
426 return;
427 }
428
429 const $field = $( '#' + field.name );
430
431 switch ( field.type ) {
432
433 case 'checkbox':
434 $field
435 .prop( 'checked', !! field.default );
436 break;
437
438 case 'checkboxes':
439 // Uncheck all first
440 $( '[ name="helpdocs_' + field.name + '[]" ]' )
441 .prop( 'checked', false );
442
443 // Check defaults if array exists
444 if ( Array.isArray( field.default ) ) {
445
446 field.default.forEach( function( value ) {
447
448 $( '[ name="helpdocs_' + field.name + '[]" ][ value="' + value + '" ]' )
449 .prop( 'checked', true );
450
451 } );
452
453 }
454
455 break;
456
457 case 'select':
458 case 'text':
459 case 'textarea':
460 case 'number':
461 case 'color':
462 $field
463 .val( field.default ?? '' );
464
465 break;
466 }
467 } );
468
469 updateAllLive();
470
471 if ( field.name === 'main_docs_css' && cssEditor ) {
472 cssEditor.codemirror.setValue( field.default ?? '' );
473 }
474 } );
475
476 // --- DOWNLOAD COLORS ---
477 $( '#helpdocs-download-colors-btn' ).on( 'click', function( e ) {
478 e.preventDefault();
479
480 const data = {};
481 helpdocs_settings.settings.forEach( function( field ) {
482 if ( field.type !== 'color' ) return;
483 const $field = $( '#' + field.name );
484 data[ field.name ] = $field.val();
485 });
486
487 const blob = new Blob( [ JSON.stringify( data, null, 4 ) ], { type: 'application/json' } );
488 const url = URL.createObjectURL( blob );
489 const a = document.createElement( 'a' );
490 a.href = url;
491 a.download = 'helpdocs-colors.json';
492 a.click();
493 URL.revokeObjectURL( url );
494 });
495
496 // --- UPLOAD COLORS ---
497 $( '#helpdocs-upload-colors' ).on( 'change', function( e ) {
498 const file = e.target.files[0];
499 if ( ! file ) return;
500
501 const reader = new FileReader();
502 reader.onload = function( event ) {
503 try {
504 const uploadedColors = JSON.parse( event.target.result );
505
506 helpdocs_settings.settings.forEach( function( field ) {
507 if ( field.type !== 'color' ) return;
508 const $field = $( '#' + field.name );
509 if ( ! uploadedColors.hasOwnProperty( field.name ) ) return;
510 $field.val( uploadedColors[ field.name ] );
511 });
512
513 updateAllLive();
514
515 $( '#helpdocs-upload-colors-filename' ).text( file.name ).show();
516
517 } catch ( err ) {
518 alert( 'Invalid JSON file. Please check the file and try again.' );
519 $( '#helpdocs-upload-colors' ).val( '' );
520 }
521 };
522
523 reader.readAsText( file );
524 });
525
526 // --- DOWNLOAD SETTINGS ---
527 $( '#helpdocs-download-settings-btn' ).on( 'click', function( e ) {
528 e.preventDefault();
529
530 if ( cssEditor ) {
531 cssEditor.codemirror.save();
532 }
533
534 const data = {};
535 helpdocs_settings.settings.forEach( function( field ) {
536
537 if ( field.type === 'html' || field.name === 'default_doc' ) return;
538
539 let field_id = field.name;
540 if ( field.name.startsWith( 'footer_' ) ) {
541 field_id = 'helpdocs_' + field.name;
542 }
543
544 const $field = $( '#' + field_id );
545
546 let value;
547 switch ( field.type ) {
548 case 'checkbox':
549 value = $field.is( ':checked' ) ? 1 : 0;
550 break;
551
552 case 'checkboxes':
553 value = $( '[name="helpdocs_' + field.name + '[]"]:checked' ).map( function() {
554 return this.value;
555 }).get();
556 break;
557
558 default:
559 value = $field.val();
560 }
561
562 data[ field.name ] = value;
563 });
564
565 const blob = new Blob( [ JSON.stringify( data, null, 4 ) ], { type: 'application/json' } );
566 const url = URL.createObjectURL( blob );
567 const a = document.createElement( 'a' );
568 a.href = url;
569 a.download = 'helpdocs-settings.json';
570 a.click();
571 URL.revokeObjectURL( url );
572 });
573
574 // --- UPLOAD SETTINGS ---
575 $( '#helpdocs-upload-settings' ).on( 'change', function( e ) {
576 const file = e.target.files[ 0 ];
577 if ( ! file ) return;
578
579 const reader = new FileReader();
580 reader.onload = function( event ) {
581 try {
582 const uploadedSettings = JSON.parse( event.target.result );
583
584 helpdocs_settings.settings.forEach( function( field ) {
585
586 if ( field.type === 'html' || field.name === 'default_doc' ) return;
587
588 let field_id = field.name;
589 if ( field.name.startsWith( 'footer_' ) ) {
590 field_id = 'helpdocs_' + field.name;
591 }
592
593 const $field = $( '#' + field_id );
594
595 if ( ! uploadedSettings.hasOwnProperty( field.name ) ) return;
596
597 const value = uploadedSettings[ field.name ];
598
599 switch ( field.type ) {
600
601 case 'checkbox':
602 $field.prop( 'checked', !! value );
603 break;
604
605 case 'checkboxes':
606 $( '[name="helpdocs_' + field.name + '[]"]' ).prop( 'checked', false );
607 if ( Array.isArray( value ) ) {
608 value.forEach( function( val ) {
609 $( '[name="helpdocs_' + field.name + '[]"][value="' + val + '"]' )
610 .prop( 'checked', true );
611 });
612 }
613 break;
614
615 default:
616 if ( field.name === 'main_docs_css' && typeof cssEditor !== 'undefined' && cssEditor.codemirror ) {
617 cssEditor.codemirror.setValue( value || '' );
618 cssEditor.codemirror.save();
619 } else {
620 $field.val( value ).trigger( 'change' );
621 }
622 break;
623 }
624
625 });
626
627 updateAllLive();
628
629 // Enable upload button after reading
630 $( '#helpdocs-upload-settings-btn' ).prop( 'disabled', false );
631 $( '#helpdocs-upload-filename' ).text( file.name ).show();
632
633 } catch ( err ) {
634 alert( 'Invalid JSON file. Please check the file and try again.' );
635 $( '#helpdocs-upload-settings' ).val( '' );
636 $( '#helpdocs-upload-settings-btn' ).prop( 'disabled', true );
637 }
638 };
639
640 reader.readAsText( file );
641 });
642
643 // --- TRIGGER UPLOAD BUTTON ---
644 $( '#helpdocs-upload-settings-btn' ).on( 'click', function( e ) {
645 e.preventDefault();
646
647 if ( ! confirm( 'Are you sure you want to overwrite all settings with this file?' ) ) return;
648
649 // Optionally trigger save after populating fields
650 $( '#helpdocs-subheader .tab-button' ).click();
651 });
652
653 // --- GENERATE API KEY ---
654 $( '.helpdocs-generate-api-key' ).on( 'click', function() {
655 const array = new Uint8Array( 32 );
656 window.crypto.getRandomValues( array );
657 const key = Array.from( array, byte => byte.toString( 16 ).padStart( 2, '0' ) ).join( '' );
658
659 $( '#api_key' ).val( key );
660 $( '#helpdocs_api_key_display' ).removeClass( 'no-key' ).addClass( 'has-key' ).text( key );
661 $( '.helpdocs-copy-api-key, .helpdocs-clear-api-key' ).prop( 'disabled', false );
662 } );
663
664 $( '.helpdocs-copy-api-key' ).on( 'click', function() {
665 const key = $( '#api_key' ).val();
666 if ( key ) {
667 navigator.clipboard.writeText( key );
668 const $btn = $( this );
669 const originalText = $btn.text();
670 $btn.text( 'Copied!' );
671 setTimeout( () => $btn.text( originalText ), 2000 );
672 }
673 } );
674
675 $( '.helpdocs-clear-api-key' ).on( 'click', function() {
676 if ( confirm( 'Are you sure you want to clear the API key? This may break existing imports on other sites.' ) ) {
677 $( '#api_key' ).val( '' );
678 $( '#helpdocs_api_key_display' ).removeClass( 'has-key' ).addClass( 'no-key' ).html( '<em>No API Key Generated</em>' );
679 $( this ).prop( 'disabled', true );
680 $( '.helpdocs-copy-api-key' ).prop( 'disabled', true );
681 }
682 } );
683
684 // --- FLUSH CACHE ---
685 jQuery( document ).on( 'click', '#helpdocs-flush-cache', function( e ) {
686 e.preventDefault();
687
688 const $button = jQuery( this );
689 const originalText = $button.text();
690
691 $button.text( helpdocs_settings.flushing_text ).addClass( 'updating-message thinking' ).prop( 'disabled', true );
692
693 jQuery.post( ajaxurl, {
694 action: 'helpdocs_flush_cache',
695 nonce: helpdocs_settings.nonce
696 }, function( response ) {
697 if ( response.success ) {
698 alert( response.data );
699 } else {
700 alert( 'Error: ' + response.data );
701 }
702 $button.text( originalText ).removeClass( 'updating-message thinking' ).prop( 'disabled', false );
703 } );
704 } );
705
706 } );