index.ts 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. import {getEditorRange, setSelectionFocus} from "../util/selection";
  2. import {getElement} from "./getElement";
  3. import {setHeaders} from "./setHeaders";
  4. class Upload {
  5. public element: HTMLElement;
  6. public isUploading: boolean;
  7. public range: Range;
  8. constructor() {
  9. this.isUploading = false;
  10. this.element = document.createElement("div");
  11. this.element.className = "vditor-upload";
  12. }
  13. }
  14. const validateFile = (vditor: IVditor, files: File[]) => {
  15. vditor.tip.hide();
  16. const uploadFileList = [];
  17. let errorTip = "";
  18. let uploadingStr = "";
  19. const lang: keyof II18n | "" = vditor.options.lang;
  20. const options: IOptions = vditor.options;
  21. for (let iMax = files.length, i = 0; i < iMax; i++) {
  22. const file = files[i];
  23. let validate = true;
  24. if (!file.name) {
  25. errorTip += `<li>${window.VditorI18n.nameEmpty}</li>`;
  26. validate = false;
  27. }
  28. if (file.size > vditor.options.upload.max) {
  29. errorTip += `<li>${file.name} ${window.VditorI18n.over} ${vditor.options.upload.max / 1024 / 1024}M</li>`;
  30. validate = false;
  31. }
  32. const lastIndex = file.name.lastIndexOf(".");
  33. const fileExt = file.name.substr(lastIndex);
  34. const filename = vditor.options.upload.filename(file.name.substr(0, lastIndex)) + fileExt;
  35. if (vditor.options.upload.accept) {
  36. const isAccept = vditor.options.upload.accept.split(",").some((item) => {
  37. const type = item.trim();
  38. if (type.indexOf(".") === 0) {
  39. if (fileExt.toLowerCase() === type.toLowerCase()) {
  40. return true;
  41. }
  42. } else {
  43. if (file.type.split("/")[0] === type.split("/")[0]) {
  44. return true;
  45. }
  46. }
  47. return false;
  48. });
  49. if (!isAccept) {
  50. errorTip += `<li>${file.name} ${window.VditorI18n.fileTypeError}</li>`;
  51. validate = false;
  52. }
  53. }
  54. if (validate) {
  55. uploadFileList.push(file);
  56. uploadingStr += `<li>${filename} ${window.VditorI18n.uploading}</li>`;
  57. }
  58. }
  59. vditor.tip.show(`<ul>${errorTip}${uploadingStr}</ul>`);
  60. return uploadFileList;
  61. };
  62. const genUploadedLabel = (responseText: string, vditor: IVditor) => {
  63. const editorElement = getElement(vditor);
  64. editorElement.focus();
  65. const response = JSON.parse(responseText);
  66. let errorTip = "";
  67. if (response.code === 1) {
  68. errorTip = `${response.msg}`;
  69. }
  70. if (response.data.errFiles && response.data.errFiles.length > 0) {
  71. errorTip = `<ul><li>${errorTip}</li>`;
  72. response.data.errFiles.forEach((data: string) => {
  73. const lastIndex = data.lastIndexOf(".");
  74. const filename = vditor.options.upload.filename(data.substr(0, lastIndex)) + data.substr(lastIndex);
  75. errorTip += `<li>${filename} ${window.VditorI18n.uploadError}</li>`;
  76. });
  77. errorTip += "</ul>";
  78. }
  79. if (errorTip) {
  80. vditor.tip.show(errorTip);
  81. } else {
  82. vditor.tip.hide();
  83. }
  84. let succFileText = "";
  85. Object.keys(response.data.succMap).forEach((key) => {
  86. const path = response.data.succMap[key];
  87. const lastIndex = key.lastIndexOf(".");
  88. let type = key.substr(lastIndex);
  89. const filename = vditor.options.upload.filename(key.substr(0, lastIndex)) + type;
  90. type = type.toLowerCase();
  91. if (type.indexOf(".wav") === 0 || type.indexOf(".mp3") === 0 || type.indexOf(".ogg") === 0) {
  92. if (vditor.currentMode === "wysiwyg") {
  93. succFileText += `<div class="vditor-wysiwyg__block" data-type="html-block"
  94. data-block="0"><pre><code>&lt;audio controls="controls" src="${path}"&gt;&lt;/audio&gt;</code></pre>`;
  95. } else if (vditor.currentMode === "ir") {
  96. succFileText += `<audio controls="controls" src="${path}"></audio>\n`;
  97. } else {
  98. succFileText += `[${filename}](${path})\n`;
  99. }
  100. } else if (type.indexOf(".apng") === 0
  101. || type.indexOf(".bmp") === 0
  102. || type.indexOf(".gif") === 0
  103. || type.indexOf(".ico") === 0 || type.indexOf(".cur") === 0
  104. || type.indexOf(".jpg") === 0 || type.indexOf(".jpeg") === 0 || type.indexOf(".jfif") === 0 || type.indexOf(".pjp") === 0 || type.indexOf(".pjpeg") === 0
  105. || type.indexOf(".png") === 0
  106. || type.indexOf(".svg") === 0
  107. || type.indexOf(".webp") === 0) {
  108. if (vditor.currentMode === "wysiwyg") {
  109. succFileText += `<img alt="${filename}" src="${path}">`;
  110. } else {
  111. succFileText += `![${filename}](${path})\n`;
  112. }
  113. } else {
  114. if (vditor.currentMode === "wysiwyg") {
  115. succFileText += `<a href="${path}">${filename}</a>`;
  116. } else {
  117. succFileText += `[${filename}](${path})\n`;
  118. }
  119. }
  120. });
  121. setSelectionFocus(vditor.upload.range);
  122. document.execCommand("insertHTML", false, succFileText);
  123. vditor.upload.range = getSelection().getRangeAt(0).cloneRange();
  124. };
  125. const uploadFiles =
  126. async (vditor: IVditor, files: FileList | DataTransferItemList | File[], element?: HTMLInputElement) => {
  127. // FileList | DataTransferItemList | File[] => File[]
  128. let fileList = [];
  129. const filesMax = vditor.options.upload.multiple === true ? files.length : 1;
  130. for (let i = 0; i < filesMax; i++) {
  131. let fileItem = files[i];
  132. if (fileItem instanceof DataTransferItem) {
  133. fileItem = fileItem.getAsFile();
  134. }
  135. fileList.push(fileItem);
  136. }
  137. if (vditor.options.upload.handler) {
  138. const isValidate = await vditor.options.upload.handler(fileList);
  139. if (typeof isValidate === "string") {
  140. vditor.tip.show(isValidate);
  141. return;
  142. }
  143. return;
  144. }
  145. if (!vditor.options.upload.url || !vditor.upload) {
  146. if (element) {
  147. element.value = "";
  148. }
  149. vditor.tip.show("please config: options.upload.url");
  150. return;
  151. }
  152. if (vditor.options.upload.file) {
  153. fileList = await vditor.options.upload.file(fileList);
  154. }
  155. if (vditor.options.upload.validate) {
  156. const isValidate = vditor.options.upload.validate(fileList);
  157. if (typeof isValidate === "string") {
  158. vditor.tip.show(isValidate);
  159. return;
  160. }
  161. }
  162. const editorElement = getElement(vditor);
  163. vditor.upload.range = getEditorRange(vditor);
  164. const validateResult = validateFile(vditor, fileList);
  165. if (validateResult.length === 0) {
  166. if (element) {
  167. element.value = "";
  168. }
  169. return;
  170. }
  171. const formData = new FormData();
  172. const extraData = vditor.options.upload.extraData;
  173. for (const key of Object.keys(extraData)) {
  174. formData.append(key, extraData[key]);
  175. }
  176. for (let i = 0, iMax = validateResult.length; i < iMax; i++) {
  177. formData.append(vditor.options.upload.fieldName, validateResult[i]);
  178. }
  179. const xhr = new XMLHttpRequest();
  180. xhr.open("POST", vditor.options.upload.url);
  181. if (vditor.options.upload.token) {
  182. xhr.setRequestHeader("X-Upload-Token", vditor.options.upload.token);
  183. }
  184. if (vditor.options.upload.withCredentials) {
  185. xhr.withCredentials = true;
  186. }
  187. setHeaders(vditor, xhr);
  188. vditor.upload.isUploading = true;
  189. editorElement.setAttribute("contenteditable", "false");
  190. xhr.onreadystatechange = () => {
  191. if (xhr.readyState === XMLHttpRequest.DONE) {
  192. vditor.upload.isUploading = false;
  193. editorElement.setAttribute("contenteditable", "true");
  194. if (xhr.status >= 200 && xhr.status < 300) {
  195. if (vditor.options.upload.success) {
  196. vditor.options.upload.success(editorElement, xhr.responseText);
  197. } else {
  198. let responseText = xhr.responseText;
  199. if (vditor.options.upload.format) {
  200. responseText = vditor.options.upload.format(files as File [], xhr.responseText);
  201. }
  202. genUploadedLabel(responseText, vditor);
  203. }
  204. } else {
  205. if (vditor.options.upload.error) {
  206. vditor.options.upload.error(xhr.responseText);
  207. } else {
  208. vditor.tip.show(xhr.responseText);
  209. }
  210. }
  211. if (element) {
  212. element.value = "";
  213. }
  214. vditor.upload.element.style.display = "none";
  215. }
  216. };
  217. xhr.upload.onprogress = (event: ProgressEvent) => {
  218. if (!event.lengthComputable) {
  219. return;
  220. }
  221. const progress = event.loaded / event.total * 100;
  222. vditor.upload.element.style.display = "block";
  223. const progressBar = vditor.upload.element;
  224. progressBar.style.width = progress + "%";
  225. };
  226. xhr.send(formData);
  227. };
  228. export {Upload, uploadFiles};