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.
 
 
 
 

197 lines
5.2 KiB

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