PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 1.2
MxChat – AI Chatbot & Content Generation for WordPress v1.2
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | js/mxchat-admin.js +91 -513 2.0.61.2 View file →
@@ -1,362 +1,99 @@
1 -// Simple debounce function implementation
2 -function debounce(func, wait) {
3 - let timeout;
4 - return function executedFunction(...args) {
5 - const later = () => {
6 - clearTimeout(timeout);
7 - func(...args);
8 - };
9 - clearTimeout(timeout);
10 - timeout = setTimeout(later, wait);
11 - };
12 -}
1 +jQuery(document).ready(function($) {
2 + $('#mxchat-activation-form').on('submit', function(event) {
3 + event.preventDefault();
13 4
14 -// Helper function to open edit modal
15 -function mxchatOpenEditModal(intentId, phrases) {
16 - const modal = document.getElementById('mxchat-edit-modal');
17 - if (!modal) return;
18 -
19 - // Get form fields
20 - const intentIdField = document.getElementById('edit_intent_id');
21 - const phrasesField = document.getElementById('edit_phrases');
22 -
23 - // Set values
24 - intentIdField.value = intentId;
25 - phrasesField.value = phrases;
26 -
27 - // Show modal with animation
28 - modal.style.display = 'flex';
29 - requestAnimationFrame(() => {
30 - modal.classList.add('active');
31 - });
32 -
33 - // Set up close handlers
34 - const closeModal = () => {
35 - modal.classList.remove('active');
36 - setTimeout(() => {
37 - modal.style.display = 'none';
38 - }, 300); // Match the CSS transition time
39 - };
40 -
41 - // Close button handler
42 - const closeBtn = modal.querySelector('.mxchat-modal-close');
43 - if (closeBtn) {
44 - closeBtn.onclick = closeModal;
45 - }
46 -
47 - // Cancel button handler
48 - const cancelBtn = modal.querySelector('.mxchat-modal-cancel');
49 - if (cancelBtn) {
50 - cancelBtn.onclick = closeModal;
51 - }
52 -
53 - // Click outside modal to close
54 - modal.onclick = (e) => {
55 - if (e.target === modal) {
56 - closeModal();
57 - }
58 - };
59 -
60 - // Focus the textarea
61 - phrasesField.focus();
62 -}
63 -
64 -// Initialize event listeners
65 -document.addEventListener('DOMContentLoaded', () => {
66 - // Set up edit button handlers
67 - document.querySelectorAll('.mxchat-edit-button').forEach(button => {
68 - button.onclick = () => {
69 - const intentId = button.dataset.intentId;
70 - const phrases = button.dataset.phrases;
71 - mxchatOpenEditModal(intentId, phrases);
5 + var formData = {
6 + action: 'mxchat_activate_license',
7 + email: $('#mxchat_pro_email').val(),
8 + key: $('#mxchat_activation_key').val(),
9 + security: mxchatAdmin.nonce
72 10 };
73 - });
74 -});
75 11
76 -jQuery(document).ready(function($) {
77 - // Ensure we have a debounce function (use lodash if available, otherwise use our implementation)
78 - const useDebounce = (window._ && window._.debounce) ? window._.debounce : debounce;
79 -
80 - // --- AJAX Auto-Save ---
81 - const $autosaveSections = $('.mxchat-autosave-section');
82 -
83 - if ($autosaveSections.length) {
84 - // Handle real-time range slider value updates
85 - $autosaveSections.find('input[type="range"]').on('input', function() {
86 - const value = $(this).val();
87 - $('#threshold_value').text(value);
88 - });
89 -
90 - // Handle all input changes (including range slider)
91 - $autosaveSections.find('input, textarea, select').on('change', function() {
92 - const $field = $(this);
93 - const name = $field.attr('name');
94 - let value;
95 -
96 - // Handle different input types
97 - if ($field.attr('type') === 'checkbox') {
98 - value = $field.is(':checked') ? 'on' : 'off';
12 + $.post(mxchatAdmin.ajax_url, formData, function(response) {
13 + if (response.success) {
14 + $('#mxchat-license-status').text('Active');
99 15 } else {
100 - value = $field.val();
16 + $('#mxchat-license-status').text('Inactive');
17 + alert(response.data);
101 18 }
102 -
103 - // Create feedback container
104 - const feedbackContainer = $('<div class="feedback-container"></div>');
105 - const spinner = $('<div class="saving-spinner"></div>');
106 - const successIcon = $('<div class="success-icon">✔</div>');
107 -
108 - // Position feedback container based on input type
109 - if ($field.closest('.toggle-switch').length) {
110 - $field.closest('td').append(feedbackContainer);
111 - } else if ($field.closest('.mxchat-toggle-switch').length) {
112 - $field.closest('.mxchat-toggle-container').append(feedbackContainer);
113 - } else if ($field.closest('.slider-container').length) {
114 - $field.closest('.slider-container').after(feedbackContainer);
115 - } else {
116 - $field.after(feedbackContainer);
117 - }
118 - feedbackContainer.append(spinner);
119 -
120 - // Determine which AJAX action and nonce to use:
121 - var ajaxAction, nonce;
122 - // Use the new AJAX action for submenu fields:
123 - if ( name.indexOf('mxchat_prompts_options') !== -1 ||
124 - name === 'mxchat_auto_sync_posts' ||
125 - name === 'mxchat_auto_sync_pages' ) {
126 - ajaxAction = 'mxchat_save_prompts_setting';
127 - nonce = mxchatPromptsAdmin.prompts_setting_nonce;
128 - } else {
129 - // Otherwise, use the existing AJAX action.
130 - ajaxAction = 'mxchat_save_setting';
131 - nonce = mxchatAdmin.setting_nonce;
132 - }
133 -
134 - // AJAX save request
135 - $.ajax({
136 - url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url,
137 - type: 'POST',
138 - data: {
139 - action: ajaxAction,
140 - name: name,
141 - value: value,
142 - _ajax_nonce: nonce
143 - },
144 - success: function(response) {
145 - if (response.success) {
146 - spinner.fadeOut(200, function() {
147 - feedbackContainer.append(successIcon);
148 - successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
149 - feedbackContainer.remove();
150 - });
151 - });
152 - } else {
153 - alert('Error saving: ' + (response.data?.message || 'Unknown error'));
154 - if ($field.attr('type') === 'checkbox') {
155 - $field.prop('checked', !$field.is(':checked'));
156 - }
157 - feedbackContainer.remove();
158 - }
159 - },
160 - error: function() {
161 - alert('An error occurred while saving.');
162 - if ($field.attr('type') === 'checkbox') {
163 - $field.prop('checked', !$field.is(':checked'));
164 - }
165 - feedbackContainer.remove();
166 - }
167 - });
168 19 });
20 + });
169 21
170 - // Initialize color pickers with debouncing
171 - $autosaveSections.find('.my-color-field').each(function() {
172 - const $colorField = $(this);
22 + $('.mxchat-nav-tab').on('click', function(e) {
23 + e.preventDefault();
24 + $('.mxchat-nav-tab').removeClass('mxchat-nav-tab-active');
25 + $(this).addClass('mxchat-nav-tab-active');
26 + $('.mxchat-tab-content').removeClass('active').hide();
27 + var activeTab = $(this).attr('href');
28 + $(activeTab).addClass('active').show();
29 + });
173 30
174 - $(this).wpColorPicker({
175 - change: useDebounce(function(event, ui) {
176 - // Safety check - ensure we have a valid field and value
177 - if (!$colorField || !$colorField.val()) {
178 - console.warn('Color picker not ready');
179 - return;
180 - }
31 + // Activate the first tab by default
32 + $('.mxchat-nav-tab-active').trigger('click');
181 33
182 - const name = $colorField.attr('name');
183 - const value = $colorField.val();
184 -
185 - if (!name || !value) {
186 - console.warn('Missing required color picker values');
187 - return;
188 - }
189 -
190 - // Create feedback container
191 - const feedbackContainer = $('<div class="feedback-container"></div>');
192 - const spinner = $('<div class="saving-spinner"></div>');
193 - const successIcon = $('<div class="success-icon">✔</div>');
194 -
195 - // Position feedback container
196 - $colorField.closest('.wp-picker-container').after(feedbackContainer);
197 - feedbackContainer.append(spinner);
198 -
199 - // Determine AJAX action and nonce for color fields:
200 - var ajaxAction, nonce;
201 - if ( name.indexOf('mxchat_prompts_options') !== -1 ||
202 - name === 'mxchat_auto_sync_posts' ||
203 - name === 'mxchat_auto_sync_pages' ) {
204 - ajaxAction = 'mxchat_save_prompts_setting';
205 - nonce = mxchatPromptsAdmin.prompts_setting_nonce;
206 - } else {
207 - ajaxAction = 'mxchat_save_setting';
208 - nonce = mxchatAdmin.setting_nonce;
209 - }
210 -
211 - // AJAX save request
212 - $.ajax({
213 - url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url,
214 - type: 'POST',
215 - data: {
216 - action: ajaxAction,
217 - name: name,
218 - value: value,
219 - _ajax_nonce: nonce
220 - },
221 - success: function(response) {
222 - if (response.success) {
223 - spinner.fadeOut(200, function() {
224 - feedbackContainer.append(successIcon);
225 - successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
226 - feedbackContainer.remove();
227 - });
228 - });
229 - } else {
230 - alert('Error saving: ' + (response.data?.message || 'Unknown error'));
231 - feedbackContainer.remove();
232 - }
233 - },
234 - error: function() {
235 - alert('An error occurred while saving.');
236 - feedbackContainer.remove();
237 - }
238 - });
239 - }, 500)
240 - });
241 - });
242 -
243 - // Reinitialize color pickers when switching tabs
244 - $('.mxchat-nav-tab').on('click.mxchat', function() {
245 - setTimeout(function() {
246 - $('.my-color-field:visible').wpColorPicker('close');
247 - }, 100);
248 - });
249 - }
250 -
251 - // Initialize tabs system
252 - function initTabs() {
253 - // Remove any existing handlers first
254 - $('.mxchat-nav-tab').off('click.mxchat');
255 -
256 - // Add new click handlers
257 - $('.mxchat-nav-tab').on('click.mxchat', function(e) {
258 - e.preventDefault();
259 - e.stopPropagation();
260 -
261 - var $this = $(this);
262 -
263 - // Get tab ID - try href first, fallback to data-tab, then to default
264 - var tabId = $this.attr('href');
265 - if (tabId) {
266 - tabId = tabId.replace('#', '');
267 - } else {
268 - tabId = $this.data('tab') || 'chatbot';
269 - }
270 -
271 - // Safety check for empty tabId
272 - if (!tabId) {
273 - console.warn('No tab identifier found');
274 - return;
275 - }
276 -
277 - // Update tabs
278 - $('.mxchat-nav-tab').removeClass('mxchat-nav-tab-active');
279 - $this.addClass('mxchat-nav-tab-active');
280 -
281 - // Update content areas - with safety check
282 - $('.mxchat-tab-content').removeClass('active').hide();
283 - var $targetTab = $('#' + tabId);
284 - if ($targetTab.length) {
285 - $targetTab.addClass('active').show();
286 -
287 - // Store active tab
288 - try {
289 - localStorage.setItem('mxchat_active_tab', tabId);
290 - } catch (e) {
291 - console.warn('LocalStorage not available:', e);
292 - }
293 - } else {
294 - console.warn('Tab content #' + tabId + ' not found');
295 - }
296 - });
297 - }
298 -
299 - // Initialize tabs and handle events
300 - initTabs();
301 - $(document).on('widget-added widget-updated postbox-toggled', initTabs);
302 -
303 - // Activate initial tab
304 - try {
305 - var savedTab = localStorage.getItem('mxchat_active_tab');
306 - if (savedTab && $('#' + savedTab).length > 0) {
307 - $('.mxchat-nav-tab[href="#' + savedTab + '"]').trigger('click.mxchat');
34 + // Toggle visibility of API key
35 + $('#toggleApiKeyVisibility').on('click', function() {
36 + var apiKeyInput = $('#api_key');
37 + if (apiKeyInput.attr('type') === 'password') {
38 + apiKeyInput.attr('type', 'text');
39 + $(this).text('Hide');
308 40 } else {
309 - $('.mxchat-nav-tab').first().trigger('click.mxchat');
41 + apiKeyInput.attr('type', 'password');
42 + $(this).text('Show');
310 43 }
311 - } catch (e) {
312 - $('.mxchat-nav-tab').first().trigger('click.mxchat');
313 - }
314 -
315 - // Attach edit modal event handler
316 - $(document).on('click', '.mxchat-edit-button', function() {
317 - const intentId = $(this).data('intent-id');
318 - const phrases = $(this).data('phrases');
319 - mxchatOpenEditModal(intentId, phrases);
320 44 });
321 -
322 - // Toggle visibility handlers
323 - function toggleVisibility(selector) {
324 - $(selector).on('click', function() {
325 - var inputField = $(this).prev('input');
326 - if (inputField.attr('type') === 'password') {
327 - inputField.attr('type', 'text');
328 - $(this).text('Hide');
329 - } else {
330 - inputField.attr('type', 'password');
331 - $(this).text('Show');
332 - }
333 - });
334 - }
335 -
336 - // Initialize all toggle visibility buttons
337 - [
338 - '#toggleApiKeyVisibility',
339 - '#toggleWooCommerceSecretVisibility',
340 - '#toggleVoyageAPIKeyVisibility',
341 - '#toggleLoopsApiKeyVisibility',
342 - '#toggleXaiApiKeyVisibility',
343 - '#toggleClaudeApiKeyVisibility',
344 - '#toggleBraveApiKeyVisibility',
345 - '#toggleWebhookUrlVisibility',
346 - '#toggleSecretKeyVisibility',
347 - '#toggleBotTokenVisibility',
348 - '#toggleDeepSeekApiKeyVisibility'
349 - ].forEach(toggleVisibility);
350 -
351 - // Add Intent Form Submission
352 - $('#mxchat-add-intent-form').on('submit', function(event) {
353 - $('#mxchat-intent-loading').show();
354 - $('#mxchat-intent-loading-text').show();
355 - $(this).find('button[type="submit"]').hide();
45 +
46 + // Toggle visibility of WooCommerce Consumer Secret
47 + $('#toggleWooCommerceSecretVisibility').on('click', function() {
48 + var secretInput = $('#woocommerce_consumer_secret');
49 + if (secretInput.attr('type') === 'password') {
50 + secretInput.attr('type', 'text');
51 + $(this).text('Hide');
52 + } else {
53 + secretInput.attr('type', 'password');
54 + $(this).text('Show');
55 + }
356 56 });
357 -
358 - // Inline Edit Functionality
57 +
58 + // Toggle visibility of Loops API Key
59 + $('#toggleLoopsApiKeyVisibility').on('click', function() {
60 + var loopsApiKeyInput = $('#loops_api_key');
61 + if (loopsApiKeyInput.attr('type') === 'password') {
62 + loopsApiKeyInput.attr('type', 'text');
63 + $(this).text('Hide');
64 + } else {
65 + loopsApiKeyInput.attr('type', 'password');
66 + $(this).text('Show');
67 + }
68 + });
69 +
70 + // Toggle visibility of X.AI API Key
71 + $('#toggleXaiApiKeyVisibility').on('click', function() {
72 + var xaiApiKeyInput = $('#xai_api_key');
73 + if (xaiApiKeyInput.attr('type') === 'password') {
74 + xaiApiKeyInput.attr('type', 'text');
75 + $(this).text('Hide');
76 + } else {
77 + xaiApiKeyInput.attr('type', 'password');
78 + $(this).text('Show');
79 + }
80 + });
81 +
82 + // Toggle visibility of Claude API Key
83 + $('#toggleClaudeApiKeyVisibility').on('click', function() {
84 + var claudeApiKeyInput = $('#claude_api_key');
85 + if (claudeApiKeyInput.attr('type') === 'password') {
86 + claudeApiKeyInput.attr('type', 'text');
87 + $(this).text('Hide');
88 + } else {
89 + claudeApiKeyInput.attr('type', 'password');
90 + $(this).text('Show');
91 + }
92 + });
93 +
94 +
95 + // Click the Edit button
359 96 $('.edit-button').on('click', function() {
360 97 var row = $(this).closest('tr');
361 98 row.find('.content-view, .url-view').hide();
362 99 row.find('.content-edit, .url-edit').show();
@@ -363,9 +100,9 @@
363 100 row.find('.edit-button').hide();
364 101 row.find('.save-button').show();
365 102 });
366 103
367 - // Save button handler
104 + // Click the Save button
368 105 $('.save-button').on('click', function() {
369 106 var button = $(this);
370 107 var row = button.closest('tr');
371 108 var id = button.data('id');
@@ -371,13 +108,11 @@
371 108 var id = button.data('id');
372 109 var newContent = row.find('.content-edit').val();
373 110 var newUrl = row.find('.url-edit').val();
374 111
375 - button.prop('disabled', true);
376 - button.text('Saving...');
377 -
112 + // Send an AJAX request to save the changes
378 113 $.ajax({
379 - url: mxchatAdmin.ajax_url,
114 + url: ajaxurl, // ajaxurl is automatically available in WP admin
380 115 type: 'POST',
381 116 data: {
382 117 action: 'mxchat_save_inline_prompt',
383 118 id: id,
@@ -382,187 +117,30 @@
382 117 action: 'mxchat_save_inline_prompt',
383 118 id: id,
384 119 article_content: newContent,
385 120 article_url: newUrl,
386 - _ajax_nonce: mxchatAdmin.inline_edit_nonce
121 + _ajax_nonce: mxchatInlineEdit.nonce // Use localized nonce
387 122 },
388 123 success: function(response) {
389 - button.prop('disabled', false);
390 - button.text('Save');
391 -
392 124 if (response.success) {
393 125 row.find('.content-view').html(newContent.replace(/\n/g, "<br>"));
394 - if (newUrl) {
395 - row.find('.url-view').html('<a href="' + newUrl + '" target="_blank">' + newUrl + '</a>');
396 - } else {
397 - row.find('.url-view').html('N/A');
398 - }
399 -
126 + row.find('.url-view a').attr('href', newUrl).text(newUrl);
400 127 row.find('.content-edit, .url-edit').hide();
401 128 row.find('.content-view, .url-view').show();
402 129 row.find('.save-button').hide();
403 130 row.find('.edit-button').show();
404 131 } else {
405 - alert('Error saving content: ' + (response.data?.message || 'Unknown error'));
132 + alert('Error saving content.');
406 133 }
407 134 },
408 135 error: function() {
409 - button.prop('disabled', false);
410 - button.text('Save');
411 - alert('An error occurred while saving.');
136 + alert('An error occurred.');
412 137 }
413 138 });
414 139 });
140 +
415 141
416 - // Activation handling
417 - const form = $('#mxchat-activation-form');
418 - const spinner = $('#mxchat-activation-spinner');
419 - const submitButton = $('#activate_license_button');
420 - const licenseStatus = $('#mxchat-license-status');
142 +});
421 143
422 - if (form.length && licenseStatus.length && submitButton.length) {
423 - function handleActivationResponse(response) {
424 - spinner.hide();
425 - if (response.success) {
426 - licenseStatus.text('Active');
427 - licenseStatus.removeClass('inactive').addClass('active');
428 - form.hide();
429 - } else {
430 - licenseStatus.text('Inactive');
431 - alert(response.data || 'Activation failed. Please check your input.');
432 - submitButton.prop('disabled', false);
433 - }
434 - }
435 144
436 - form.on('submit', function(event) {
437 - event.preventDefault();
438 - spinner.show();
439 - submitButton.prop('disabled', true);
440 145
441 - var formData = {
442 - action: 'mxchat_activate_license',
443 - mxchat_pro_email: $('#mxchat_pro_email').val(),
444 - mxchat_activation_key: $('#mxchat_activation_key').val(),
445 - security: mxchatAdmin.license_nonce
446 - };
447 146
448 - $.post(mxchatAdmin.ajax_url, formData, function(response) {
449 - handleActivationResponse(response);
450 - }).fail(function() {
451 - alert('Server error. Please try again.');
452 - spinner.hide();
453 - submitButton.prop('disabled', false);
454 - });
455 - });
456 - }
457 -
458 - // Questions handling
459 - $('.mxchat-add-question').on('click', function () {
460 - const container = $('#mxchat-additional-questions-container');
461 - const questionCount = container.find('.mxchat-question-row').length + 4;
462 - const questionIndex = container.find('.mxchat-question-row').length;
463 -
464 - const newQuestion = `
465 - <div class="mxchat-question-row">
466 - <input type="text"
467 - name="additional_popular_questions[]"
468 - placeholder="Enter Additional Popular Question ${questionCount}"
469 - class="regular-text mxchat-question-input"
470 - data-question-index="${questionIndex}" />
471 - <button type="button" class="button mxchat-remove-question"
472 - aria-label="Remove question">Remove</button>
473 - </div>
474 - `;
475 - container.append(newQuestion);
476 - });
477 -
478 - $(document).on('click', '.mxchat-remove-question', function () {
479 - $(this).closest('.mxchat-question-row').remove();
480 - saveQuestions();
481 - });
482 -
483 - $(document).on('change', '.mxchat-question-input', function() {
484 - saveQuestions();
485 - });
486 -
487 - function saveQuestions() {
488 - const questions = [];
489 - $('.mxchat-question-input').each(function() {
490 - const value = $(this).val().trim();
491 - if (value) {
492 - questions.push(value);
493 - }
494 - });
495 -
496 - const feedbackContainer = $('<div class="feedback-container"></div>');
497 - const spinner = $('<div class="saving-spinner"></div>');
498 - const successIcon = $('<div class="success-icon">✔</div>');
499 -
500 - // Append feedback after the add button
501 - $('.mxchat-add-question').after(feedbackContainer);
502 - feedbackContainer.append(spinner);
503 -
504 - // Save via AJAX
505 - $.ajax({
506 - url: mxchatAdmin.ajax_url,
507 - type: 'POST',
508 - data: {
509 - action: 'mxchat_save_setting',
510 - name: 'additional_popular_questions',
511 - value: JSON.stringify(questions),
512 - _ajax_nonce: mxchatAdmin.setting_nonce
513 - },
514 - success: function(response) {
515 - if (response.success) {
516 - spinner.fadeOut(200, function() {
517 - feedbackContainer.append(successIcon);
518 - successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
519 - feedbackContainer.remove();
520 - });
521 - });
522 - } else {
523 - alert('Error saving questions: ' + (response.data?.message || 'Unknown error'));
524 - feedbackContainer.remove();
525 - }
526 - },
527 - error: function() {
528 - alert('An error occurred while saving questions.');
529 - feedbackContainer.remove();
530 - }
531 - });
532 - }
533 -
534 - // Live agent status handler
535 - const statusToggle = document.getElementById('live_agent_status');
536 - const statusText = statusToggle?.parentElement.nextElementSibling?.querySelector('.status-text');
537 - if (statusToggle && statusText) {
538 - statusToggle.addEventListener('change', function() {
539 - // Update display text
540 - statusText.textContent = this.checked ? 'Online' : 'Offline';
541 -
542 - // Send the correct on/off value to the server
543 - if (window.mxchatSaveSetting) {
544 - window.mxchatSaveSetting('live_agent_status', this.checked ? 'on' : 'off');
545 - }
546 - });
547 - }
548 -
549 - // Function to adjust the textarea height to content
550 - function adjustTextareaHeight() {
551 - this.style.height = 'auto'; // Reset to auto to calculate scrollHeight
552 - this.style.height = this.scrollHeight + 'px'; // Expand to content height
553 - }
554 -
555 - // Function to reset the textarea height to initial
556 - function resetTextareaHeight() {
557 - this.style.height = ''; // Remove inline height, reverting to CSS default
558 - }
559 -
560 - // Target the specific textarea by ID
561 - var $textarea = $('#system_prompt_instructions');
562 -
563 - // Bind events
564 - $textarea.on('focus input', adjustTextareaHeight) // Expand on focus and input
565 - .on('blur', resetTextareaHeight); // Reset on blur
566 -
567 -
568 -});