1(function($){ 2 function request(action,data,fd){ 3 if(fd){ fd.append('action',action); fd.append('nonce',SFS.nonce); return $.ajax({url:SFS.ajax,method:'POST',data:fd,processData:false,contentType:false}); } 4 return $.post(SFS.ajax,Object.assign({action:action,nonce:SFS.nonce},data||{})); 5 } 6 function esc(v){ return $('<div>').text(v==null?'':v).html(); } 7 function modal(html){ $('[data-modal-body]').html(html); $('[data-modal]').prop('hidden',false); } 8 $(document).on('click','[data-close-modal]',()=> $('[data-modal]').prop('hidden',true)); 9 10 function renderExplorer(app,data,isPublic){ 11 app.attr('data-current-folder',data.current.id); 12 var crumbs=(data.breadcrumbs||[]).map((c,i)=>'<button data-go-folder="'+c.id+'"'+(i===data.breadcrumbs.length-1?' disabled':'')+'>'+esc(c.name)+'</button>').join('<span>âº</span>'); 13 app.find('[data-breadcrumbs]').html(crumbs||'<span>My Files</span>'); 14 if(!isPublic){ app.find('[data-share-current]').prop('hidden',!data.can_share); app.find('[data-storage-stat]').text(data.storage||''); } 15 var currentZip=(data.current&&data.current.zip_download)||'';app.find('[data-zip-current]').prop('hidden',!currentZip).attr('href',currentZip||'#'); 16 var rows=''; 17 (data.rows||[]).forEach(function(r){ 18 if(r.type==='folder'){ 19 rows+='<tr class="sfs-row sfs-folder-row" data-item-type="folder" data-item-id="'+r.id+'"><td><button class="sfs-name-button" data-go-folder="'+r.id+'"><span class="sfs-icon">ð</span><span>'+esc(r.name)+'</span></button></td><td>â</td><td>'+esc(r.modified)+'</td><td class="sfs-expires-in" title="Expires '+esc(r.expires||'')+'">'+esc(r.expires_in||'â')+'</td><td class="sfs-row-actions">'; 20 if(!isPublic) rows+='<button class="sfs-kebab" data-item-menu aria-label="Folder actions">â¯</button><div class="sfs-menu"><button data-row-share>Share</button><a class="sfs-menu-link" href="'+esc(r.zip_download||'#')+'">ZIP & Download All</a><button data-row-rename>Rename</button><button data-row-keep>'+((r.keep_forever)?'Resume Expiration':'Keep Forever')+'</button><button class="is-danger" data-row-delete>Delete</button></div>'; 21 else rows+='<a class="sfs-download" href="'+esc(r.zip_download||'#')+'">Download ZIP</a>'; 22 rows+='</td></tr>'; 23 } else { 24 rows+='<tr class="sfs-row" data-item-type="file" data-item-id="'+r.id+'"><td><span class="sfs-file-name"><span class="sfs-icon">ð</span><span>'+esc(r.name)+'</span></span></td><td>'+esc(r.size)+'</td><td>'+esc(r.modified)+'</td><td class="sfs-expires-in" title="Expires '+esc(r.expires||'')+'">'+esc(r.expires_in||'â')+'</td><td class="sfs-row-actions">'; 25 if(isPublic) rows+='<a class="sfs-download" href="'+esc(r.download)+'">Download</a>'; 26 else rows+='<button class="sfs-kebab" data-item-menu aria-label="File actions">â¯</button><div class="sfs-menu"><button data-row-rename>Rename</button><button class="is-danger" data-row-delete>Delete</button></div>'; 27 rows+='</td></tr>'; 28 } 29 }); 30 if(!rows) rows='<tr><td colspan="5"><div class="sfs-empty">This folder is empty.</div></td></tr>'; 31 app.find('[data-explorer-body]').html(rows); 32 } 33 34 function loadManager(app,folder){ 35 request('sfs_explorer_list',{folder_id:folder}).done(function(r){ if(!r.success){alert(r.data&&r.data.message||'Could not load folder');return;} renderExplorer(app,r.data,false); }); 36 } 37 function loadPublic(app,folder){ 38 request('sfs_public_list',{folder_id:folder,token:app.data('token')}).done(function(r){ if(!r.success){app.find('[data-explorer-body]').html('<tr><td colspan="5"><div class="sfs-notice">'+esc(r.data&&r.data.message||'Could not load folder')+'</div></td></tr>');return;} renderExplorer(app,r.data,true); }); 39 } 40 $('[data-sfs-manager]').each(function(){loadManager($(this),0);}); 41 $('[data-sfs-public]').each(function(){loadPublic($(this),Number($(this).data('root-folder')));}); 42 43 $(document).on('click','[data-go-folder]',function(){ 44 var app=$(this).closest('[data-sfs-manager],[data-sfs-public]'),id=Number($(this).data('go-folder')); if(app.is('[data-sfs-public]'))loadPublic(app,id);else loadManager(app,id); 45 }); 46 $(document).on('click','[data-item-menu]',function(e){ e.stopPropagation(); var menu=$(this).siblings('.sfs-menu'); $('.sfs-menu').not(menu).removeClass('is-open'); menu.toggleClass('is-open'); }); 47 $(document).on('click',function(){ $('.sfs-menu').removeClass('is-open'); }); 48 49 $(document).on('click','[data-create-folder]',function(){ 50 var app=$(this).closest('[data-sfs-manager]'); modal('<h3>New Folder</h3><input class="sfs-input" data-new-folder placeholder="Folder name" autofocus><div class="sfs-modal-actions"><button class="sfs-btn" data-save-folder>Create Folder</button></div>'); $('[data-save-folder]').data('app',app); 51 }); 52 $(document).on('click','[data-save-folder]',function(){ 53 var app=$('[data-sfs-manager]'),parent=Number(app.attr('data-current-folder')); request('sfs_create_folder',{name:$('[data-new-folder]').val(),parent_id:parent}).done(function(r){if(r.success){$('[data-modal]').prop('hidden',true);loadManager(app,parent);}else alert(r.data.message);}); 54 }); 55
56 function renderShareList(folderId){ 57 var box=$('[data-share-list]'); box.html('<div class="sfs-share-loading">Loading shared recipientsâ¦</div>'); 58 request('sfs_list_shares',{folder_id:folderId}).done(function(r){ 59 if(!r.success){box.html('<div class="sfs-modal-message">'+esc(r.data&&r.data.message||'Could not load recipients.')+'</div>');return;} 60 var shares=r.data.shares||[]; 61 if(!shares.length){box.html('<div class="sfs-share-empty">This folder has not been shared with anyone yet.</div>');return;} 62 var html=''; 63 shares.forEach(function(x){ 64 html+='<div class="sfs-share-person'+(x.active?'':' is-revoked')+'"><div class="sfs-share-person-main"><strong>'+esc(x.email)+'</strong><span class="sfs-share-status">'+esc(x.status)+' · '+esc(x.invited)+'</span></div><div class="sfs-share-person-actions">'; 65 if(x.active) html+='<button type="button" class="sfs-share-action" data-resend-share="'+x.id+'" data-folder="'+folderId+'">Resend</button><button type="button" class="sfs-share-action is-danger" data-revoke-share="'+x.id+'" data-folder="'+folderId+'">Revoke</button>'; 66 html+='</div></div>'; 67 }); 68 box.html(html); 69 }); 70 } 71 function shareFolder(app,id,name){ 72 modal('<h3>Share '+esc(name||'Folder')+'</h3><p>The recipient will receive a secure link and verification code. Acce
72ss includes this folder and all subfolders beneath it.</p><div class="sfs-share-add"><input class="sfs-input" type="email" data-share-email placeholder="[email protected]"><button class="sfs-btn" data-send-share data-folder="'+id+'">Send Share Link</button></div><div class="sfs-modal-message" data-share-msg></div><div class="sfs-share-admin"><div class="sfs-share-admin-title">Shared With</div><div data-share-list></div></div>'); 73 renderShareList(id); 74 } 75 $(document).on('click','[data-share-current]',function(){var app=$(this).closest('[data-sfs-manager]');shareFolder(app,Number(app.attr('data-current-folder')),'this folder');}); 76 $(document).on('click','[data-row-share]',function(e){e.stopPropagation();var row=$(this).closest('[data-item-id]');shareFolder(row.closest('[data-sfs-manager]'),row.data('item-id'),row.find('.sfs-name-button span:last').text());}); 77 $(document).on('click','[data-send-share]',function(){var b=$(this),folder=b.data('folder'),email=$('[data-share-email]').val();b.prop('disabled',true);request('sfs_share_folder',{folder_id:folder,email:email}).done(function(r){$('[data-share-msg]').text(r.data&&r.data.message||'');if(r.success){$('[data-share-email]').val('');renderShareList(folder);}}).always(function(){b.prop('disabled',false);});}); 78 $(document).on('click','[data-revoke-share]',function(){var b=$(this),folder=b.data('folder');if(!confirm('Revoke this recipientâs access to the shared folder?'))return;b.prop('disabled',true);request('sfs_revoke_share',{share_id:b.data('revoke-share')}).done(function(r){$('[data-share-msg]').text(r.data&&r.data.message||'');if(r.success)renderShareList(folder);}).always(function(){b.prop('disabled',false);});}); 79 $(document).on('click','[data-resend-share]',function(){var b=$(this),folder=b.data('folder');b.prop('disabled',true);request('sfs_resend_share',{share_id:b.data('resend-share')}).done(function(r){$('[data-share-msg]').text(r.data&&r.data.message||'');if(r.success)renderShareList(folder);}).always(function(){b.prop('disabled',false);});}); 80 81 82 $(document).on('click','[data-row-keep]',function(e){ 83 e.stopPropagation(); 84 var row=$(this).closest('[data-item-id]'),app=row.closest('[data-sfs-manager]'),id=Number(row.data('item-id')),name=row.find('.sfs-name-button span:last').text(),isKeep=$(this).text().indexOf('Resume')===0; 85 var msg=isKeep 86 ? 'Resume automatic expiration for "'+name+'"? A fresh 30-day timer will begin for this folder.' 87 : 'Keep "'+name+'" forever? Only this folder will be kept. Files and subfolders inside it will continue to expire normally.'; 88 if(!confirm(msg))return; 89 request('sfs_toggle_keep_forever',{folder_id:id}).done(function(r){ 90 if(r.success)loadManager(app,Number(app.attr('data-current-folder'))); 91 else alert(r.data&&r.data.message||'Could not change expiration setting.'); 92 }); 93 }); 94 95 $(document).on('click','[data-row-delete]',function(e){ 96 e.stopPropagation();var row=$(this).closest('[data-item-id]'),app=row.closest('[data-sfs-manager]'),type=row.data('item-type'),id=row.data('item-id'),name=row.find('td:first').text().trim(); 97 if(!confirm(type==='folder'?'Delete "'+name+'" and everything inside it?':'Delete "'+name+'"?'))return; 98 request(type==='folder'?'sfs_delete_folder':'sfs_delete_file',type==='folder'?{folder_id:id}:{file_id:id}).done(function(r){if(r.success)loadManager(app,Number(app.attr('data-current-folder')));else alert(r.data.message);}); 99 }); 100 $(document).on('click','[data-row-rename]',function(e){ 101 e.stopPropagation();var row=$(this).closest('[data-item-id]'),type=row.data('item-type'),id=row.data('item-id'),name=row.find('td:first span:last').text();modal('<h3>Rename '+(type==='folder'?'Folder':'File')+'</h3><input class="sfs-input" data-rename-value value="'+esc(name)+'"><div class="sfs-modal-actions"><button class="sfs-btn" data-save-rename data-type="'+type+'" data-id="'+id+'">Save</button></div>'); 102 }); 103 $(document).on('click','[data-save-rename]',function(){var app=$('[data-sfs-manager]'),b=$(this),type=b.data('type');request(type==='folder'?'sfs_rename_folder':'sfs_rename_file',type==='folder'?{folder_id:b.data('id'),name:$('[data-rename-value]').val()}:{file_id:b.data('id'),name:$('[data-rename-value]').val()}).done(function(r){if(r.success){$('[data-modal]').prop('hidden',true);loadManager(app,Number(app.attr('data-current-folder')));}else alert(r.data.message);});}); 104 105 $(document).on('click','[data-upload-files]',function(){var app=$(this).closest('[data-sfs-manager]');if(Number(app.attr('data-current-folder'))===0){alert('Open or create a folder before uploading individual files. Use Upload Folder to upload a complete desktop folder at My Files.');return;}app.find('[data-file-input]').trigger('click');}); 106 $(document).on('click','[data-upload-folder]',function(){$(this).closest('[data-sfs-manager]').find('[data-folder-input]').trigger('click');}); 107 $(document).on('change','[data-file-input]',function(){queueFiles($(this).closest('[data-sfs-manager]'),Array.from(this.files),false);this.value='';}); 108 $(document).on('change','[data-folder-input]',function(){queueFiles($(this).closest('[data-sfs-manager]'),Array.from(this.files),true);this.value='';}); 109 110 function formatBytes(bytes){ 111 bytes=Number(bytes)||0;if(bytes<1024)return bytes+' B'; 112 var units=['KB','MB','GB','TB'],v=bytes/1024,i=0; 113 while(v>=1024&&i<units.length-1){v/=1024;i++;} 114 return (v>=100?v.toFixed(0):v>=10?v.toFixed(1):v.toFixed(2))+' '+units[i]; 115 } 116 function ajaxErrorMessage(err,chunk,total){ 117 var status=err&&err.status?('HTTP '+err.status):'Network error',body=''; 118 if(err&&err.responseJSON&&err.responseJSON.data&&err.responseJSON.data.message)body=err.responseJSON.data.message; 119 else if(err&&err.responseText){try{var j=JSON.parse(err.responseText);body=j&&j.data&&j.data.message?j.data.message:'';}catch(e){}} 120 return (chunk!=null?'Chunk '+(chunk+1)+' of '+total+' failed. ':'')+status+(body?' â '+body:''); 121 } 122 async function queueFiles(app,files,preserve){ 123 if(!files.length)return; 124 var max=Number(SFS.maxFileSize)||1073741824; 125 var q=app.find('[data-upload-queue]').prop('hidden',false).empty(); 126 q.append('<div class="sfs-queue-title">Uploading '+files.length+' file'+(files.length===1?'':'s')+'</div><div class="sfs-overall"><div></div></div><div class="sfs-upload-status" data-queue-status></div>'); 127 var folder=Number(app.attr('data-current-folder')),completed=0,failed=0; 128 for(const file of files){ 129 var rel=preserve?(file.webkitRelativePath||file._relativePath||file.name):''; 130 if(file.size>max){failed++;q.append('<div class="sfs-upload-error"><strong>'+esc(file.name)+'</strong><br>File is '+esc(formatBytes(file.size))+'. Maximum allowed size is '+esc(formatBytes(max))+'.</div>');continue;}
131 try{ 132 await uploadFile(file,folder,rel,function(info){ 133 var pct=info.totalBytes?Math.round(info.sentBytes/info.totalBytes*100):100; 134 q.find('.sfs-overall>div').css('width',(((completed+pct/100)/files.length)*100)+'%'); 135 q.find('[data-queue-status]').html('<strong>'+esc(rel||file.name)+'</strong><br>'+esc(formatBytes(info.sentBytes))+' / '+esc(formatBytes(info.totalBytes))+' · Chunk '+info.chunk+' of '+info.totalChunks+' · '+pct+'%'); 136 }); 137 completed++; 138 }catch(err){failed++;q.append('<div class="sfs-upload-error"><strong>'+esc(file.name)+'</strong><br>'+esc(err.message||String(err))+'</div>');} 139 q.find('.sfs-overall>div').css('width',((completed+failed)/files.length*100)+'%'); 140 } 141 q.find('[data-queue-status]').html(failed?'<strong>Upload finished with errors.</strong> '+completed+' succeeded, '+failed+' failed.':'<strong>Upload complete.</strong> '+completed+' file'+(completed===1?'':'s')+' uploaded.'); 142 loadManager(app,Number(app.attr('data-current-folder'))); 143 } 144 async function uploadFile(file,folder,relative,onProgress){ 145 var chunkSize=Math.max(1048576,Number(SFS.chunkSize)||4194304); 146 var totalChunks=Math.max(1,Math.ceil(file.size/chunkSize)); 147 var init; 148 try{init=await request('sfs_upload_init',{folder_id:folder,name:file.name,relative_path:relative,size:file.size,total_chunks:totalChunks});} 149 catch(e){throw new Error(ajaxErrorMessage(e,null,totalChunks));} 150 if(!init||!init.success)throw new Error((init&&init.data&&init.data.message)||'Could not start upload.'); 151 var id=init.data.upload_id,retries=Math.max(0,Number(SFS.chunkRetries)||3),sent=0; 152 for(let chunkIndex=0;chunkIndex<totalChunks;chunkIndex++){ 153 let start=chunkIndex*chunkSize,end=Math.min(file.size,start+chunkSize),blob=file.slice(start,end); 154 if(file.size===0)blob=new Blob(['']); 155 let attempt=0; 156 while(true){ 157 let fd=new FormData(); 158 fd.append('upload_id',id);fd.append('chunk_index',String(chunkIndex));fd.append('chunk',blob,file.name+'.part'); 159 try{let r=await request('sfs_upload_chunk',{},fd);if(!r||!r.success)throw {status:200,responseJSON:r};break;} 160 catch(e){attempt++;if(attempt>retries)throw new Error(ajaxErrorMessage(e,chunkIndex,totalChunks));await new Promise(resolve=>setTimeout(resolve,750*attempt));} 161 } 162 sent=end;if(file.size===0)sent=0; 163 if(onProgress)onProgress({sentBytes:sent,totalBytes:file.size,chunk:chunkIndex+1,totalChunks:totalChunks}); 164 } 165 var complete; 166 try{complete=await request('sfs_upload_complete',{upload_id:id});} 167 catch(e){throw new Error('Finalization failed. '+ajaxErrorMessage(e,null,totalChunks));} 168 if(!complete||!complete.success)throw new Error('Finalization failed. '+((complete&&complete.data&&complete.data.message)||'Unknown server error.')); 169 return complete.data; 170 } 171 172 // Folder/file drag-and-drop. Chromium/Edge can expose recursive directory entries here. 173 async function entryFiles(entry,path){ 174 if(entry.isFile)return new Promise(resolve=>entry.file(f=>{f._relativePath=path+f.name;resolve([f]);},()=>resolve([]))); 175 if(!entry.isDirectory)return[];var reader=entry.createReader(),entries=[];while(true){var batch=await new Promise(resolve=>reader.readEntries(resolve,()=>resolve([])));if(!batch.length)break;entries=entries.concat(batch);}var out=[];for(const child of entries)out=out.concat(await entryFiles(child,path+entry.name+'/'));return out; 176 } 177 $(document).on('dragover','[data-dropzone]',function(e){e.preventDefault();$(this).addClass('is-dragging');}).on('dragleave','[data-dropzone]',function(){$(this).removeClass('is-dragging');}); 178 $(document).on('drop','[data-dropzone]',async function(e){e.preventDefault();$(this).removeClass('is-dragging');var app=$(this).closest('[data-sfs-manager]'),dt=e.originalEvent.dataTransfer,files=[];if(dt.items&&dt.items.length&&dt.items[0].webkitGetAsEntry){for(const item of dt.items){var ent=item.webkitGetAsEntry();if(ent)files=files.concat(await entryFiles(ent,''));}await queueFiles(app,files,true);}else{await queueFiles(app,Array.from(dt.files||[]),false);}}); 179 180 $(document).on('click','[data-verify]',function(){var box=$(this).closest('[data-sfs-verify]');request('sfs_verify_code',{token:box.data('token'),code:box.find('[data-code]').val()}).done(r=>{box.find('[data-verify-msg]').text(r.success?'Verified. Opening filesâ¦':r.data.message);if(r.success)location.reload();});}); 181})(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.