1/** 2 * File Upload for ajax_form file pickers (dentist referral x-rays/photos). 3 * 4 * Config-driven (reads window.__PRM_CONFIG__) so it works UNCHANGED in both 5 * direct mode (posts to prm.tio.work with TDS-API-KEY header) and proxied mode 6 * (posts same-origin to /api/prm/v2/upload-file/, no key â the proxy injects it). 7 * 8 * Replaces the dead C5 plupload + /themes/_uploads.php flow: 9 * - #pick_files (span) triggers a hidden #file_input (created if absent) 10 * - each file is POSTed to the upload endpoint (multipart) 11 * - the returned URL is written into the next free #doc_N hidden slot, which 12 * is re-enabled so it serializes; empty slots stay DISABLED so no blank 13 * attachment reaches PRM 14 * 15 * Slot fields MUST be scalar names (gdpr-X-Ray-01..NN), not name="â¦[]" arrays 16 * (PRM's note builder 500s on arrays). Requires jQuery (loaded beforeInteractive). 17 */ 18(function () { 19 if (typeof jQuery === "undefined") return; 20 21 jQuery(document).ready(function ($) { 22 var $pick = $("#pick_files"); 23 var $doc = $("#doc_files"); 24 if (!$pick.length || !$doc.length) return; // no upload UI on this page 25 26 var cfg = window.__PRM_CONFIG__ || {}; 27 var UPLOAD_ENDPOINT = cfg.uploadEndpoint || "https://prm.tio.work/api/v2/upload-file"; 28 var TDS_API_KEY = cfg.tdsApiKey || ""; 29 var ACCOUNT_ID = cfg.accountId || ""; 30 var MAX_BYTES = 10 * 1024 * 1024; 31 32 // create the hidden file input if the C5 markup didn't carry one (plupload used to) 33 var $input = $("#file_input"); 34 if (!$input.length) { 35 $input = $( 36 '<input type="file" id="file_input" multiple accept=".jpg,.jpeg,.png,.gif,.pdf,.heic" style="display:none">' 37 ); 38 $pick.before($input); 39 } 40 41 // discover slots #doc_1..#doc_N and disable them (empty slots must not serialize) 42 var slots = []; 43 for (var n = 1; $("#doc_" + n).length; n++) slots.push($("#doc_" + n)); 44 if (!slots.length) return; 45 var MAX = slots.length; 46 slots.forEach(function ($s) { $s.prop("disabled", true); }); 47 48 var $names = $doc.find(".file_names").first(); 49 var $del = $doc.find(".btn_del, .btn_del_one").first(); 50 var $loader = $doc.find(".loader, .loader_one").first(); 51 52 var files = []; 53 var inflight = 0; 54 55 $pick.on("click keypress", function (e) { 56 if (e.type === "keypress" && e.which !== 13 && e.which !== 32) return; 57 e.preventDefault(); 58 if (files.length >= MAX) { alert("Maximum " + MAX + " files allowed."); return; } 59 $input.trigger("click"); 60 }); 61 62 $input.on("change", function () { 63 var f = this.files; 64 if (!f || !f.length) return; 65 var remaining = MAX - files.length; 66 if (remaining <= 0) { alert("Maximum " + MAX + " files allowed."); return; } 67 var count = Math.min(f.length, remaining); 68 if (f.length > remaining) alert("Only " + remaining + " more file(s) allowed."); 69 for (var i = 0; i < count; i++) uploadFile(f[i]); 70 this.value = ""; 71 }); 72 73 $del.on("click", function (e) { 74 e.preventDefault(); 75 e.stopPropagation(); 76 files = []; 77 slots.forEach(function ($s) { $s.val("").prop("disabled", true); }); 78 if ($names.length) $names.empty(); 79 $(this).hide(); 80 }); 81 82 function uploadFile(file) { 83 if (file.size > MAX_BYTES) { alert(file.name + " is too large. Maximum 10MB per file."); return; } 84 var fd = new FormData(); 85 fd.append("file", file); 86 if (ACCOUNT_ID) fd.append("accountid", ACCOUNT_ID); 87 var headers = {}; 88 if (TDS_API_KEY) headers["TDS-API-KEY"] = TDS_API_KEY; // omitted in proxied mode 89 90 inflight++; status(); 91 $.ajax({ 92 type: "POST", 93 url: UPLOAD_ENDPOINT, 94 data: fd, 95 processData: false, 96 contentType: false, 97 headers: headers, 98 dataType: "json", 99 success: function (r) { 100 inflight--; 101 if (r && r.success && r.url) { 102 files.push({ url: r.url, name: r.name || file.name }); 103 var idx = files.length; 104 if (idx <= MAX) slots[idx - 1].val(r.url).prop("disabled", false); 105 render(); 106 } else { 107 alert("Failed to upload " + file.name + ". Please try again."); 108 } 109 status(); 110 }, 111 error: function () { 112 inflight--; 113 alert("Failed to upload " + file.name + ". Please try again."); 114 status(); 115 } 116 }); 117 } 118 119 function render() { 120 if (!$names.length) return; 121 if (!files.length) { $names.empty(); if ($del.length) $del.hide(); return; } 122 $names.text(files.map(function (f) { return f.name; }).join(", ")); 123 if ($del.length) $del.show(); 124 } 125 126 function status() { 127 if (inflight > 0) { 128 if ($loader.length) $loader.css("display", "inline"); 129 $('.ajax_form button[type="submit"]').prop("disabled", true); 130 } else { 131 if ($loader.length) $loader.hide(); 132 $('.ajax_form button[type="submit"]').prop("disabled", false); 133 } 134 } 135 }); 136})();
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.