1 // Handle any tasks which require uploading files to the server (such as attachments)
2 // h/t https://www.smashingmagazine.com/2018/01/drag-drop-file-uploader-vanilla-js/ which inspired the design of this module
4 // Copyright (c) 2016-2023 by the citadel.org team
6 // This program is open source software. Use, duplication, or
7 // disclosure are subject to the GNU General Public License v3.
9 var uploads_in_progress = 0;
10 var uploads = [] ; // everything the user has uploaded
11 var attachment_counter_divs = [] ; // list of divs containing attachment counters
13 // Remove the upload window completely (even if it's hidden)
14 function deactivate_uploads() {
15 upload_window = document.getElementById('ctdl-upload');
17 upload_window.remove();
22 // Turn the specified div into a place where we can upload. (Note: permanently changes the drag-and-drop behavior of that div.)
23 function activate_uploads(parent_div) {
25 attachment_counter_divs = [ "num_attachments" ] ;
27 document.getElementById(parent_div).innerHTML += `
28 <div class="ctdl-upload" id="ctdl-upload">
29 <div id="ctdl_attachments_title" class="ctdl-compose-attachments-title">
30 <div><h1><i class="fa fa-paperclip" style="color:grey"></i>` + _("Attachments:") + ` <span id="${attachment_counter_divs[0]}">` + uploads.length + `</span></h1></div>
31 <div><h1><i class="fas fa-window-close" style="color:red" onClick="show_or_hide_upload_window()"></i></h1></div>
34 <ul id="ctdl-upload_list">
37 <div id="drop-area" class="ctdl-upload-drop-area">
38 <form class="ctdl-upload-form">
39 <p>${_("Drop files here to upload")}</p>
40 <input type="file" id="fileElem" multiple accept="*/*" onChange="handle_upload_files(this.files)">
46 // activate drag and drop
47 let dropArea = document.getElementById(parent_div);
48 ;['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
49 dropArea.addEventListener(eventName, upload_prevent_defaults, false)
51 ;["dragenter", "dragover"].forEach(eventName => {
52 dropArea.addEventListener(eventName, upload_highlight, false)
54 ;['dragleave', 'drop'].forEach(eventName => {
55 dropArea.addEventListener(eventName, upload_unhighlight, false)
57 dropArea.addEventListener('drop', upload_handle_drop, false);
61 // prevent drag and drop events from propagating up through the DOM
62 function upload_prevent_defaults(e) {
68 function upload_handle_drop(e) {
69 let dt = e.dataTransfer;
71 handle_upload_files(files);
75 function handle_upload_files(files) {
76 ([...files]).forEach(upload_file)
80 // update the attachment counter div(s) with the current number of items
81 function update_attachment_count() {
82 for (let i = 0; i < attachment_counter_divs.length; ++i) {
83 document.getElementById(attachment_counter_divs[i]).innerHTML = uploads.length;
88 // Delete an uploaded item from the list
89 delete_upload = async(ref) => {
91 response = await fetch(
92 "/ctdl/p/" + ref, { method: "DELETE" }
95 if (response.ok) { // If the server accepted the delete...
96 var el = document.getElementById(ref); // ...remove it from the screen...
97 el.parentNode.removeChild(el);
98 uploads = uploads.filter((r) => r.ref != ref); // ...remove it from the array...
99 update_attachment_count();
100 // document.getElementById(attachment_counter_divs[0]).innerHTML = uploads.length; // ...and update our count
105 function upload_file(file) {
106 var url = '/ctdl/p/';
107 var xhr = new XMLHttpRequest();
108 var formData = new FormData();
109 xhr.open('POST', url, true);
111 xhr.addEventListener('readystatechange', function(e) {
112 if (xhr.readyState == 4 && xhr.status == 200) {
113 // remove the "uploading in progress" message
114 let li = document.getElementById("ctdl_uploading_" + uploads_in_progress.toString());
115 li.parentNode.removeChild(li);
116 uploads_in_progress -= 1;
118 // The response body will be a JSON array of completed uploads.
119 var j_response = JSON.parse(xhr.response);
121 // Add these uploads to the displayed list
122 j_response.forEach((item) => {
123 let new_upl = document.createElement("li");
124 new_upl.setAttribute("id", item["ref"]); // set the element id to the upload reference
125 new_upl.innerHTML = `<i class="fa-solid fa-circle-xmark" style="color:red" onClick="delete_upload('` + item["ref"] + `')"></i>`
127 + item["uploadfilename"] + " (" + item["contenttype"] + ", " + item["contentlength"] + " " + _("bytes") + ")";
128 document.getElementById("ctdl-upload_list").appendChild(new_upl);
130 // append it to the global list of uploads
133 update_attachment_count();
134 // document.getElementById(attachment_counter_divs[0]).innerHTML = uploads.length;
136 else if (xhr.readyState == 4 && xhr.status != 200) {
137 // remove the "uploading in progress" message (there was an error, so just let it disappear)
138 let li = document.getElementById("ctdl_uploading_" + uploads_in_progress.toString());
139 li.parentNode.removeChild(li);
140 uploads_in_progress -= 1;
144 formData.append('file', file);
146 uploads_in_progress += 1;
148 // Make an "uploading in progress" message appear in the uploads list!
149 progress = document.createElement("li");
150 progress.setAttribute("id", "ctdl_uploading_" + uploads_in_progress.toString());
151 progress.innerHTML = `<img src="/ctdl/s/images/dotcrawl.gif" /> ` + _("Processing dropped files...");
152 document.getElementById("ctdl-upload_list").appendChild(progress);
156 // called when the user drags a file into the upload area
157 function upload_highlight(e) {
158 let dropArea = document.getElementById("ctdl-upload");
159 dropArea.classList.add('highlight')
161 document.getElementById("ctdl-upload").style.display = "block"; // also make it appear
165 // called when the user is no longer dragging a file into the upload area
166 function upload_unhighlight(e) {
167 let dropArea = document.getElementById("ctdl-upload");
168 dropArea.classList.remove('highlight')
172 // Show or hide the attachments window in the composer
173 function show_or_hide_upload_window() {
175 if (document.getElementById("ctdl-upload").style.display == "block") {
176 document.getElementById("ctdl-upload").style.display = "none"; // turn it off
179 document.getElementById("ctdl-upload").style.display = "block"; // turn it on
184 // Helper function for flush_uploads()
185 flush_one_upload = async(ref) => {
186 response = await fetch(
187 "/ctdl/p/" + ref, { method: "DELETE" }
189 // We don't have any interest in the server response.
193 // Flush all uploaded files and close the window
194 function flush_uploads() {
195 upload_window = document.getElementById('ctdl-upload');
198 upload_window.style.display='none';
201 // tell the server to delete the files
202 uploads.forEach(u => {
203 flush_one_upload(u.ref);
206 update_attachment_count();
207 // document.getElementById(attachment_counter_divs[0]).innerHTML = uploads.length;
209 deactivate_uploads(); // this makes the window get destroyed too