A webmail client. Forked from https://git.sr.ht/~migadu/alps
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

201 lines
5.3 KiB

  1. const sendButton = document.getElementById("send-button"),
  2. saveButton = document.getElementById("save-button");
  3. const composeForm = document.getElementById("compose-form");
  4. const sendProgress = document.getElementById("send-progress");
  5. composeForm.addEventListener("submit", ev => {
  6. [...document.querySelectorAll("input, textarea")].map(
  7. i => i.setAttribute("readonly", "readonly"));
  8. sendProgress.style.display = 'flex';
  9. });
  10. saveButton.addEventListener("click", ev => {
  11. sendProgress.querySelector(".info").innerText = "Saving draft...";
  12. });
  13. let attachments = [];
  14. const headers = document.querySelector(".create-update .headers");
  15. headers.classList.remove("no-js");
  16. const attachmentsNode = document.getElementById("attachment-list");
  17. attachmentsNode.style.display = '';
  18. const helpNode = attachmentsNode.querySelector(".help");
  19. const attachmentsInput = headers.querySelector("input[type='file']");
  20. attachmentsInput.removeAttribute("name");
  21. attachmentsInput.addEventListener("input", ev => {
  22. const files = attachmentsInput.files;
  23. for (let i = 0; i < files.length; i++) {
  24. attachFile(files[i]);
  25. }
  26. });
  27. window.addEventListener("dragenter", dragNOP);
  28. window.addEventListener("dragleave", dragNOP);
  29. window.addEventListener("dragover", dragNOP);
  30. window.addEventListener("drop", ev => {
  31. ev.preventDefault();
  32. const files = ev.dataTransfer.files;
  33. for (let i = 0; i < files.length; i++) {
  34. attachFile(files[i]);
  35. }
  36. });
  37. function dragNOP(e) {
  38. e.stopPropagation();
  39. e.preventDefault();
  40. }
  41. const attachmentUUIDsNode = document.getElementById("attachment-uuids");
  42. function updateState() {
  43. let complete = true;
  44. for (let i = 0; i < attachments.length; i++) {
  45. const a = attachments[i];
  46. const progress = a.node.querySelector(".progress");
  47. progress.style.width = `${Math.floor(a.progress * 100)}%`;
  48. complete &= a.progress === 1.0;
  49. if (a.progress === 1.0) {
  50. progress.style.display = 'none';
  51. }
  52. }
  53. if (complete) {
  54. sendButton.removeAttribute("disabled");
  55. saveButton.removeAttribute("disabled");
  56. } else {
  57. sendButton.setAttribute("disabled", "disabled");
  58. saveButton.setAttribute("disabled", "disabled");
  59. }
  60. attachmentUUIDsNode.value = attachments.
  61. filter(a => a.progress === 1.0).
  62. map(a => a.uuid).
  63. join(",");
  64. }
  65. function attachFile(file) {
  66. helpNode.remove();
  67. const xhr = new XMLHttpRequest();
  68. const node = attachmentNodeFor(file);
  69. const attachment = {
  70. node: node,
  71. progress: 0,
  72. xhr: xhr,
  73. };
  74. attachments.push(attachment);
  75. attachmentsNode.appendChild(node);
  76. node.querySelector("button").addEventListener("click", ev => {
  77. attachment.xhr.abort();
  78. attachments = attachments.filter(a => a !== attachment);
  79. node.remove();
  80. updateState();
  81. if (typeof attachment.uuid !== "undefined") {
  82. const cancel = new XMLHttpRequest();
  83. cancel.open("POST", `/compose/attachment/${attachment.uuid}/remove`);
  84. cancel.send();
  85. }
  86. });
  87. let formData = new FormData();
  88. formData.append("attachments", file);
  89. const handleError = msg => {
  90. attachments = attachments.filter(a => a !== attachment);
  91. node.classList.add("error");
  92. node.querySelector(".progress").remove();
  93. node.querySelector(".size").remove();
  94. node.querySelector("button").remove();
  95. node.querySelector(".error").innerText = "Error: " + msg;
  96. updateState();
  97. };
  98. xhr.open("POST", "/compose/attachment");
  99. xhr.upload.addEventListener("progress", ev => {
  100. attachment.progress = ev.loaded / ev.total;
  101. updateState();
  102. });
  103. xhr.addEventListener("load", () => {
  104. let resp;
  105. try {
  106. resp = JSON.parse(xhr.responseText);
  107. } catch {
  108. resp = { "error": "Error: invalid response" };
  109. }
  110. if (xhr.status !== 200) {
  111. handleError(resp["error"]);
  112. return;
  113. }
  114. attachment.uuid = resp[0];
  115. updateState();
  116. });
  117. xhr.addEventListener("error", () => {
  118. handleError("an unexpected problem occured");
  119. });
  120. xhr.send(formData);
  121. updateState();
  122. }
  123. function attachmentNodeFor(file) {
  124. const node = document.createElement("div"),
  125. progress = document.createElement("span"),
  126. filename = document.createElement("span"),
  127. error = document.createElement("span"),
  128. size = document.createElement("span"),
  129. button = document.createElement("button");
  130. node.classList.add("upload");
  131. progress.classList.add("progress");
  132. node.appendChild(progress);
  133. filename.classList.add("filename");
  134. filename.innerText = file.name;
  135. node.appendChild(filename);
  136. error.classList.add("error");
  137. node.appendChild(error);
  138. size.classList.add("size");
  139. size.innerText = formatSI(file.size) + "B";
  140. node.appendChild(size);
  141. button.innerHTML = "&times";
  142. node.appendChild(button);
  143. return node;
  144. }
  145. // via https://github.com/ThomWright/format-si-prefix; MIT license
  146. // Copyright (c) 2015 Thom Wright
  147. const PREFIXES = {
  148. '24': 'Y', '21': 'Z', '18': 'E', '15': 'P', '12': 'T', '9': 'G', '6': 'M',
  149. '3': 'k', '0': '', '-3': 'm', '-6': 'µ', '-9': 'n', '-12': 'p', '-15': 'f',
  150. '-18': 'a', '-21': 'z', '-24': 'y'
  151. };
  152. function formatSI(num) {
  153. if (num === 0) {
  154. return '0';
  155. }
  156. let sig = Math.abs(num); // significand
  157. let exponent = 0;
  158. while (sig >= 1000 && exponent < 24) {
  159. sig /= 1000;
  160. exponent += 3;
  161. }
  162. while (sig < 1 && exponent > -24) {
  163. sig *= 1000;
  164. exponent -= 3;
  165. }
  166. const signPrefix = num < 0 ? '-' : '';
  167. if (sig > 1000) {
  168. return signPrefix + sig.toFixed(0) + PREFIXES[exponent];
  169. }
  170. return signPrefix + parseFloat(sig.toPrecision(3)) + PREFIXES[exponent];
  171. }