1(function($){'use strict';$(document).ready(function(){const $form=$('#odoo-helpdesk-form');if(!$form.length){return} 2const filesCollection=[];const $fileInput=$('#odoo-file-upload');const $fileList=$('#odoo-file-list');const $submitBtn=$('#odoo-form-submit');const $successMsg=$('#odoo-form-success');const $errorMsg=$('#odoo-form-error');const $loadingIndicator=$('#odoo-form-loading');$fileInput.on('change',function(){const newFiles=Array.from(this.files);newFiles.forEach(file=>{const exists=filesCollection.some(f=>f.name===file.name);if(!exists){filesCollection.push(file)}});$(this).val('');updateFileList()});function validateFile(file){const maxSizeBytes=parseInt(odooHelpdeskForm.messages.maxFileSize||5)*1024*1024;if(file.size>maxSizeBytes){return{valid:!1,message:odooHelpdeskForm.messages.fileTooBig}} 3const allowedTypes=['image/jpeg','image/jpg','image/png','image/gif','image/bmp','image/webp','image/tiff','image/svg+xml','image/heic','image/heif'];if(allowedTypes.indexOf(file.type)===-1){return{valid:!1,message:odooHelpdeskForm.messages.invalidFileType}} 4return{valid:!0}} 5function updateFileList(){$fileList.empty();if(filesCollection.length===0){return} 6let allValid=!0;for(let i=0;i<filesCollection.length;i++){const file=filesCollection[i];const validation=validateFile(file);const $item=$('<div>',{class:'odoo-file-item'+(validation.valid?'':' invalid')});$item.append($('<span>',{class:'file-name',text:file.name}),$('<span>',{class:'file-size',text:formatFileSize(file.size)}));const $removeBtn=$('<button>',{type:'button',class:'odoo-file-remove',text:'x','data-index':i,click:function(){const index=$(this).data('index');filesCollection.splice(index,1);updateFileList()}});$item.append($removeBtn);if(!validation.valid){$item.append($('<span>',{class:'file-error',text:validation.message}));allValid=!1} 7$fileList.append($item)} 8$submitBtn.prop('disabled',!allValid)} 9function formatFileSize(bytes){if(bytes===0)return'0 Bytes';const k=1024;const sizes=['Bytes','KB','MB'];const i=Math.floor(Math.log(bytes)/Math.log(k));return parseFloat((bytes/Math.pow(k,i)).toFixed(2))+' '+sizes[i]} 10function validateForm(){let isValid=!0;let firstInvalid=null;$form.find('[required]').each(function(){const $field=$(this);const $formGroup=$field.closest('.odoo-form-group');const $errorMessage=$formGroup.find('.validation-error');if($errorMessage.length){$errorMessage.remove()} 11if(!$field.val().trim()){isValid=!1;const fieldName=$field.attr('aria-labelledby')?$('#'+$field.attr('aria-labelledby')).text().replace('*','').trim():'This field';const $error=$('<div>',{'class':'validation-error','id':$field.attr('id')+'-error','text':fieldName+' is required.','role':'alert'});$formGroup.append($error);$field.attr('aria-invalid','true').attr('aria-describedby',$field.attr('id')+'-error');if(!firstInvalid){firstInvalid=$field}}else{$field.removeAttr('aria-invalid').removeAttr('aria-describedby')}});if($('input[name="category"]:checked').length===0){isValid=!1;const $radioGroup=$('.odoo-radio-group');$radioGroup.find('.validation-error').remove();const $error=$('<div>',{'class':'validation-error','id':'category-error','text':'Please select a support category.','role':'alert'});$radioGroup.append($error);$radioGroup.attr('aria-describedby','category-error');if(!firstInvalid){firstInvalid=$('input[name="category"]').first()}}else{$('.odoo-radio-group').removeAttr('aria-describedby')} 12if(firstInvalid){firstInvalid.focus()} 13return isValid} 14$form.on('submit',function(e){e.preventDefault();$successMsg.hide();$errorMsg.hide();if(!$form[0].checkValidity()){$form[0].reportValidity();return} 15if(odooHelpdeskForm.recaptchaEnabled){const recaptchaResponse=grecaptcha.getResponse();if(recaptchaResponse.length===0){$errorMsg.text(odooHelpdeskForm.messages.recaptchaError).show();return}} 16$loadingIndicator.show();$submitBtn.prop('disabled',!0);const formData=new FormData($form[0]);formData.append('action','submit_odoo_ticket');formData.append('odoo_helpdesk_nonce',odooHelpdeskForm.nonce);$.ajax({url:odooHelpdeskForm.ajaxUrl,type:'POST',data:formData,processData:!1,contentType:!1,success:function(response){if(response.success){handleSuccessfulSubmission(response.data)}else{$errorMsg.text(response.data.message||odooHelpdeskForm.messages.error).show();$submitBtn.prop('disabled',!1);if(odooHelpdeskForm.recaptchaEnabled){grecaptcha.reset()}}}
16,error:function(){$errorMsg.text(odooHelpdeskForm.messages.error).show();$submitBtn.prop('disabled',!1);if(odooHelpdeskForm.recaptchaEnabled){grecaptcha.reset()}},complete:function(){$loadingIndicator.hide()}})});function showError(message){$errorMsg.text(message).show();$errorMsg.focus()} 17function handleSuccessfulSubmission(data){const ticketId=data.ticket_id;if(filesCollection.length===0){showSuccessMessage();return} 18let uploadedCount=0;let failedUploads=0;const uploadNextFile=function(index){if(index>=filesCollection.length){if(failedUploads>0){$successMsg.text(odooHelpdeskForm.messages.success+' '+failedUploads+' file(s) could not be uploaded.').show()}else{showSuccessMessage()} 19return} 20const file=filesCollection[index];const fileData=new FormData();fileData.append('action','upload_odoo_attachment');fileData.append('odoo_helpdesk_nonce',odooHelpdeskForm.nonce);fileData.append('ticket_id',ticketId);fileData.append('file',file);$.ajax({url:odooHelpdeskForm.ajaxUrl,type:'POST',data:fileData,processData:!1,contentType:!1,success:function(response){if(response.success){uploadedCount++}else{failedUploads++}},error:function(){failedUploads++},complete:function(){uploadNextFile(index+1)}})};uploadNextFile(0)} 21function showSuccessMessage(){$form.hide();$successMsg.text(odooHelpdeskForm.messages.success).show();$form[0].reset();$fileList.empty();filesCollection.length=0;if(odooHelpdeskForm.recaptchaEnabled&&typeof grecaptcha!=='undefined'){grecaptcha.reset()}} 22$('<style>').text(` 23 .validation-error { 24 color: #d63638; 25 font-size: 0.9em; 26 margin-top: 5px; 27 } 28 [aria-invalid="true"] { 29 border-color: #d63638; 30 } 31 `).appendTo('head')})})(jQuery)
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.