1document.addEventListener('DOMContentLoaded', function () { 2 document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(function (el) { 3 new bootstrap.Tooltip(el); 4 }); 5 /* ================= Completion Slider ================= */ 6 7 function bindSliderDisplay(sliderId, displayId) { 8 const slider = document.getElementById(sliderId); 9 const display = document.getElementById(displayId); 10 if (!slider || !display) return; 11 12 const updateValue = () => { 13 display.textContent = slider.value + '%'; 14 }; 15 16 slider.addEventListener('input', updateValue); 17 slider.addEventListener('change', updateValue); 18 } 19 20 bindSliderDisplay('completion', 'completionValue'); 21 bindSliderDisplay('YTcompletion', 'YTcompletionValue'); 22 bindSliderDisplay('HLScompletion', 'HLScompletionValue'); 23 24 /* ================= COMMON HELPERS ================= */ 25 26 function startLoading(btnSelector) { 27 const loading = document.getElementById('loading'); 28 if (loading) loading.classList.remove('d-none'); 29 30 if (btnSelector) { 31 const btn = document.querySelector(btnSelector); 32 if (btn) btn.disabled = true; 33 } 34 } 35 36 function stopLoading(btnSelector) { 37 const loading = document.getElementById('loading'); 38 if (loading) loading.classList.add('d-none'); 39 40 if (btnSelector) { 41 const btn = document.querySelector(btnSelector); 42 if (btn) btn.disabled = false; 43 } 44 } 45 46 function showFormMessage(form, message, type = 'error') { 47 const typeMap = { 48 error: 'danger', 49 success: 'success', 50 warning: 'warning', 51 info: 'info' 52 }; 53 54 const alertClass = typeMap[type] || 'danger'; 55 56 const html = ` 57 <div class="alert alert-${alertClass} alert-dismissible fade show" role="alert"> 58 ${message} 59 <button type="button" class="btn-close" data-bs-dismiss="alert"></button> 60 </div> 61 `; 62 63 const messagesContainer = form.querySelector('.form-messages'); 64 if (messagesContainer) messagesContainer.innerHTML = html; 65 } 66 67 function handleAjaxError(xhr, form) { 68 let msg = 'Server error. Please try again.'; 69 70 if (xhr.responseJSON) { 71 msg = xhr.responseJSON.message || xhr.responseJSON.detail || msg; 72 } else if (xhr.responseText) { 73 try { 74 const json = JSON.parse(xhr.responseText); 75 msg = json.message || json.detail || msg; 76 } catch (e) { } 77 } 78 79 showFormMessage(form, msg, 'error'); 80 } 81 82 function handleSuccess(res, downloadUrl, form) { 83 // Hidden FastAPI error inside data 84 if (res.data && res.data.detail) { 85 showFormMessage(form, res.data.detail, 'error'); 86 return; 87 } 88 89 // Proper error response 90 if (res.status !== 'success') { 91 showFormMessage(form, res.message || 'Something went wrong', 'error'); 92 return; 93 } 94 95 // Success 96 const data = res.data; 97 98 if (!data || !data.file_name) { 99 showFormMessage(form, 'Invalid server response', 'error'); 100 return; 101 } 102 showFormMessage(form, 'Conversion successful!', 'success'); 103 104 // Build query params 105 const params = new URLSearchParams({ 106 file: data.file_name, 107 size: data.file_size, 108 time: data.conversion_time, 109 og_file_ext: data.og_file_ext, 110 og_file_name: data.og_file_name, 111 converter_type: data.converter_type, 112 source: data.source, 113 }); 114 115 // Redirect to download page WITHOUT session 116 window.location.href = downloadUrl + '?' + params.toString(); 117 } 118 119 /** 120 * Performs an XHR request that mimics the shape of the jQuery.ajax 121 * responses/errors used throughout this file (xhr.responseJSON / 122 * xhr.responseText) so the existing handleAjaxError/handleSuccess 123 * helpers can be reused unchanged. 124 */ 125 function sendFormRequest(url, formData, { headers = {}, onSuccess, onError, onComplete }) { 126 const xhr = new XMLHttpRequest(); 127 xhr.open('POST', url, true); 128 xhr.responseType = 'text'; 129
130 Object.keys(headers).forEach(key => { 131 if (headers[key] !== undefined && headers[key] !== null) { 132 xhr.setRequestHeader(key, headers[key]); 133 } 134 }); 135 136 xhr.onload = function () { 137 const status = xhr.status; 138 let json = null; 139 try { 140 json = JSON.parse(xhr.responseText); 141 } catch (e) { 142 json = null; 143 } 144 145 if (status >= 200 && status < 300 && json !== null) { 146 onSuccess(json); 147 } else { 148 // Build a jQuery-like xhr object for handleAjaxError 149 const fakeXhr = { 150 responseText: xhr.responseText, 151 responseJSON: json 152 }; 153 onError(fakeXhr); 154 } 155 156 if (onComplete) onComplete(); 157 }; 158 159 xhr.onerror = function () { 160 const fakeXhr = { 161 responseText: xhr.responseText, 162 responseJSON: null 163 }; 164 onError(fakeXhr); 165 if (onComplete) onComplete(); 166 }; 167 168 xhr.send(formData); 169 } 170 171 /* ================= VIDEO ================= */ 172 173 const videoConvertForm = document.getElementById('videoConvertForm'); 174 if (videoConvertForm) { 175 videoConvertForm.addEventListener('submit', function (e) { 176 e.preventDefault(); 177 178 const selectedThemeInput = document.querySelector('input[name="player_theme"]:checked'); 179 const selectedTheme = selectedThemeInput ? selectedThemeInput.value : null; 180 181 if (selectedTheme === 'modern') { 182 // Ignore Classic controls 183 document 184 .querySelectorAll('#classicPlayerControls input[type="checkbox"]') 185 .forEach(cb => cb.disabled = true); 186 187 // Enable Modern controls 188 document 189 .querySelectorAll('#modernPlayerControls input[type="checkbox"]') 190 .forEach(cb => cb.disabled = false); 191 } else { 192 // Ignore Modern controls 193 document 194 .querySelectorAll('#modernPlayerControls input[type="checkbox"]') 195 .forEach(cb => cb.disabled = true); 196 197 // Enable Classic controls 198 document 199 .querySelectorAll('#classicPlayerControls input[type="checkbox"]') 200 .forEach(cb => cb.disabled = false); 201 } 202 203 const form = this; 204 const formData = new FormData(form); 205 const fileInput = document.getElementById('videoFile'); 206 const file = fileInput && fileInput.files[0]; 207 208 if (!file) { 209 showFormMessage(form, 'Please select a video file'); 210 return; 211 } 212 213 const convertUrl = form.dataset.convertUrl; 214 const downloadUrl = form.dataset.downloadUrl; 215 216 startLoading('#convertBtn'); 217 218 sendFormRequest(convertUrl, formData, { 219 onSuccess: function (res) { 220 stopLoading('#convertBtn'); 221 handleSuccess(res, downloadUrl, form); 222 }, 223 onError: function (xhr) { 224 stopLoading('#convertBtn'); 225 handleAjaxError(xhr, form); 226 } 227 }); 228 }); 229 } 230 231 /* ================= PPT ================= */ 232 233 const pptConvertForm = document.getElementById('pptConvertForm'); 234 if (pptConvertForm) { 235 pptConvertForm.addEventListener('submit', function (e) { 236 e.preventDefault(); 237 238 const form = this; 239 const formData = new FormData(form); 240 const fileInput = document.getElementById('pptFile'); 241 const file = fileInput && fileInput.files[0]; 242 243 if (!file) { 244 showFormMessage(form, 'Please select a PPT file'); 245 return; 246 } 247 248 // if (file.size > 10 * 1024 * 1024) { 249 // showFormMessage(form, 'File size must not exceed 10 MB.'); 250 // return; 251 // } 252 253 const convertUrl = form.dataset.convertUrl; 254 const downloadUrl = form.dataset.downloadUrl; 255 256 startLoading('#convertBtn'); 257 258 sendFormRequest(convertUrl, formData, { 259 onSuccess: function (res) { 260 stopLoading('#convertBtn'); 261 handleSuccess(res, downloadUrl, form); 262 }, 263 onError: function (xhr) { 264 stopLoading('#convertBtn'); 265 handleAjaxError(xhr, form); 266 } 267 }); 268 }); 269 } 270 271 /* ================= PDF ================= */ 272 273 const pdfConvertForm = document.getElementById('pdfConvertForm'); 274 if (pdfConvertForm) { 275 pdfConvertForm.addEventListener('submit', function (e) { 276 e.preventDefault(); 277 278 const pageCountEl = document.getElementById('pdf_page_count'); 279 const pageCompletionEl = document.getElementById('page_completion'); 280 281 const totalPages = parseInt(pageCountEl ? pageCountEl.value : '', 10); 282 const completedPages = parseInt(pageCompletionEl ? pageCompletionEl.value : '', 10); 283 284 const form = this; 285 const fileInput = document.getElementById('pdfFile'); 286 const file = fileInput && fileInput.files[0]; 287 288 if (!file) { 289 showFormMessage(form, 'Please select a PDF file'); 290 return; 291 } 292 293 if (completedPages > totalPages) { 294 showFormMessage(form, `Pages cannot exceed ${totalPages}`, 'warning'); 295 return; 296 } 297 298 if (file.type !== 'application/pdf') { 299 showFormMessage(form, 'Only PDF files allowed'); 300 return; 301 } 302 303 // if (file.size > 10 * 1024 * 1024) { 304 // showFormMessage(form, 'File size must not exceed 10 MB.'); 305 // return; 306 // } 307 308 const formData = new FormData(form); 309 310 const csrfNameMeta = document.querySelector('meta[name="csrf-token-name"]'); 311 const csrfHashMeta = document.querySelector('meta[name="csrf-token-hash"]'); 312 313 const csrfName = csrfNameMeta ? csrfNameMeta.getAttribute('content') : null; 314 const csrfHash = csrfHashMeta ? csrfHashMeta.getAttribute('content') : null; 315 316 if (csrfName && csrfHash) { 317 formData.append(csrfName, csrfHash); 318 } 319 320 const convertUrl = form.dataset.convertUrl; 321 const downloadUrl = form.dataset.downloadUrl; 322 323 startLoading('#convertBtn'); 324 325 sendFormRequest(convertUrl, formData, { 326 headers: csrfHash ? { 'X-CSRF-TOKEN': csrfHash } : {}, 327 onSuccess: function (res) { 328 // update csrf if returned 329 if (res.csrf_token && csrfHashMeta) { 330 csrfHashMeta.setAttribute('content', res.csrf_token); 331 } 332 333 stopLoading('#convertBtn'); 334 handleSuccess(res, downloadUrl, form); 335 }, 336 onError: function (xhr) { 337 stopLoading('#convertBtn'); 338 handleAjaxError(xhr, form); 339 } 340 }); 341 }); 342 } 343 344 /* ================= YT VIDEO ================= */ 345 346 const youtubeConvertForm = document.getElementById('youtubeConvertForm'); 347 if (youtubeConvertForm) { 348 youtubeConvertForm.addEventListener('submit', function (e) { 349 e.preventDefault(); 350 351 const form = this; 352 const formData = new FormData(form); 353 const convertUrl = form.dataset.convertUrl; 354 const downloadUrl = form.dataset.downloadUrl; 355 356 const ytLoading = document.getElementById('YTloading'); 357 const ytBtn = document.getElementById('YTconvertBtn'); 358 359 if (ytLoading) ytLoading.classList.remove('d-none'); 360 if (ytBtn) ytBtn.disabled = true; 361 362 sendFormRequest(convertUrl, formData, { 363 onSuccess: function (res) { 364 if (ytLoading) ytLoading.classList.add('d-none'); 365 if (ytBtn) ytBtn.disabled = false;
366 handleSuccess(res, downloadUrl, form); 367 }, 368 onError: function (xhr) { 369 if (ytLoading) ytLoading.classList.add('d-none'); 370 if (ytBtn) ytBtn.disabled = false; 371 handleAjaxError(xhr, form); 372 } 373 }); 374 }); 375 } 376 377 /* ================= HLS VIDEO ================= */ 378 379 const hlsConvertForm = document.getElementById('hlsConvertForm'); 380 if (hlsConvertForm) { 381 hlsConvertForm.addEventListener('submit', function (e) { 382 e.preventDefault(); 383 384 const form = this; 385 const formData = new FormData(form); 386 const convertUrl = form.dataset.convertUrl; 387 const downloadUrl = form.dataset.downloadUrl; 388 389 const hlsLoading = document.getElementById('HLSloading'); 390 const hlsBtn = document.getElementById('HLSconvertBtn'); 391 392 if (hlsLoading) hlsLoading.classList.remove('d-none'); 393 if (hlsBtn) hlsBtn.disabled = true; 394 395 sendFormRequest(convertUrl, formData, { 396 onSuccess: function (res) { 397 if (hlsLoading) hlsLoading.classList.add('d-none'); 398 if (hlsBtn) hlsBtn.disabled = false; 399 handleSuccess(res, downloadUrl, form); 400 }, 401 onError: function (xhr) { 402 if (hlsLoading) hlsLoading.classList.add('d-none'); 403 if (hlsBtn) hlsBtn.disabled = false; 404 handleAjaxError(xhr, form); 405 } 406 }); 407 }); 408 } 409 410 /* ================= CONTACT FORMS ================= */ 411 412 function handleContactForm(formSelector, buttonSelector, successSelector, errorSelector) { 413 const form = document.querySelector(formSelector); 414 if (!form) return; 415 416 form.addEventListener('submit', function (e) { 417 e.preventDefault(); 418 419 const successEl = document.querySelector(successSelector); 420 const errorEl = document.querySelector(errorSelector); 421 const btn = document.querySelector(buttonSelector); 422 const btnText = btn ? btn.querySelector('.btn-text') : null; 423 const btnLoader = btn ? btn.querySelector('.btn-loader') : null; 424 425 [successEl, errorEl].forEach(el => { 426 if (el) { 427 el.classList.add('d-none'); 428 el.textContent = ''; 429 } 430 }); 431 432 if (btn) btn.disabled = true; 433 if (btnText) btnText.classList.add('d-none'); 434 if (btnLoader) btnLoader.classList.remove('d-none'); 435 436 const url = form.dataset.email_url || form.dataset.emailUrl; 437 438 const params = new URLSearchParams(new FormData(form)).toString(); 439 440 const xhr = new XMLHttpRequest(); 441 xhr.open('POST', url, true); 442 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); 443 xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 444 445 xhr.onload = function () { 446 let res = null; 447 let parseOk = true; 448 try { 449 res = JSON.parse(xhr.responseText); 450 } catch (e) { 451 parseOk = false; 452 } 453 454 if (parseOk && res && res.status === 'success') { 455 if (successEl) { 456 successEl.classList.remove('d-none'); 457 successEl.textContent = 'â Message sent successfully'; 458 } 459 form.reset(); 460 } else { 461 if (errorEl) { 462 errorEl.classList.remove('d-none'); 463 errorEl.textContent = (res && res.message) || 'â Failed to send message'; 464 } 465 } 466 467 if (btn) btn.disabled = false; 468 if (btnText) btnText.classList.remove('d-none'); 469 if (btnLoader) btnLoader.classList.add('d-none'); 470 }; 471 472 xhr.onerror = function () { 473 if (errorEl) { 474 errorEl.classList.remove('d-none'); 475 errorEl.textContent = 'â Something went wrong'; 476 } 477 478 if (btn) btn.disabled = false;
479 if (btnText) btnText.classList.remove('d-none'); 480 if (btnLoader) btnLoader.classList.add('d-none'); 481 }; 482 483 xhr.send(params); 484 }); 485 } 486 487 // Initialize both forms 488 handleContactForm('#contactus_form', '#submitBtn', '#contactFormSuccessMsg', '#contactFormErrorMsg'); 489 handleContactForm('#contactForm', '#contactForm_submitBtn', '#contactSuccessMsg', '#contactErrorMsg'); 490 491});
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.