editor-image.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. let vueImage = new Vue({
  2. el: '#modal-editor-image',
  3. data: {
  4. isLoading: false,
  5. isLoadingText: '',
  6. newFolderName: '',
  7. newFolderShow: false,
  8. newFolderError: false,
  9. fetchFromUrlURL: '',
  10. fetchFromUrlShow: false,
  11. folders: [],
  12. currentFolder: '',
  13. currentImage: '',
  14. currentAlign: 'left',
  15. images: [],
  16. uploadSucceeded: false,
  17. postUploadChecks: 0
  18. },
  19. methods: {
  20. open: () => {
  21. mdeModalOpenState = true;
  22. $('#modal-editor-image').slideDown();
  23. vueImage.refreshFolders();
  24. },
  25. cancel: (ev) => {
  26. mdeModalOpenState = false;
  27. $('#modal-editor-image').slideUp();
  28. },
  29. insertImage: (ev) => {
  30. if(mde.codemirror.doc.somethingSelected()) {
  31. mde.codemirror.execCommand('singleSelection');
  32. }
  33. let selImage = _.find(vueImage.images, ['uid', vueImage.currentImage]);
  34. selImage.normalizedPath = (selImage.folder === '') ? selImage.filename : selImage.folder + '/' + selImage.filename;
  35. selImage.titleGuess = _.startCase(selImage.basename);
  36. let imageText = '![' + selImage.titleGuess + '](/uploads/' + selImage.normalizedPath + ' "' + selImage.titleGuess + '")';
  37. switch(vueImage.currentAlign) {
  38. case 'center':
  39. imageText += '{.align-center}';
  40. break;
  41. case 'right':
  42. imageText += '{.align-right}';
  43. break;
  44. case 'logo':
  45. imageText += '{.pagelogo}';
  46. break;
  47. }
  48. mde.codemirror.doc.replaceSelection(imageText);
  49. vueImage.cancel();
  50. },
  51. newFolder: (ev) => {
  52. vueImage.newFolderName = '';
  53. vueImage.newFolderError = false;
  54. vueImage.newFolderShow = true;
  55. _.delay(() => { $('#txt-editor-newfoldername').focus(); }, 400);
  56. },
  57. newFolderDiscard: (ev) => {
  58. vueImage.newFolderShow = false;
  59. },
  60. newFolderCreate: (ev) => {
  61. let regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");
  62. vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName));
  63. if(_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) {
  64. vueImage.newFolderError = true;
  65. return;
  66. }
  67. vueImage.newFolderDiscard();
  68. vueImage.isLoading = true;
  69. vueImage.isLoadingText = 'Creating new folder...';
  70. Vue.nextTick(() => {
  71. socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => {
  72. vueImage.folders = data;
  73. vueImage.currentFolder = vueImage.newFolderName;
  74. vueImage.images = [];
  75. vueImage.isLoading = false;
  76. });
  77. });
  78. },
  79. fetchFromUrl: (ev) => {
  80. vueImage.fetchFromUrlShow = true;
  81. },
  82. fetchFromUrlDiscard: (ev) => {
  83. vueImage.fetchFromUrlShow = false;
  84. },
  85. /**
  86. * Select a folder
  87. *
  88. * @param {string} fldName The folder name
  89. * @return {Void} Void
  90. */
  91. selectFolder: (fldName) => {
  92. vueImage.currentFolder = fldName;
  93. vueImage.loadImages();
  94. },
  95. /**
  96. * Refresh folder list and load images from root
  97. *
  98. * @return {Void} Void
  99. */
  100. refreshFolders: () => {
  101. vueImage.isLoading = true;
  102. vueImage.isLoadingText = 'Fetching folders list...';
  103. vueImage.currentFolder = '';
  104. vueImage.currentImage = '';
  105. Vue.nextTick(() => {
  106. socket.emit('uploadsGetFolders', { }, (data) => {
  107. vueImage.folders = data;
  108. vueImage.loadImages();
  109. });
  110. });
  111. },
  112. /**
  113. * Loads images in selected folder
  114. *
  115. * @return {Void} Void
  116. */
  117. loadImages: (silent) => {
  118. if(!silent) {
  119. vueImage.isLoading = true;
  120. vueImage.isLoadingText = 'Fetching images...';
  121. }
  122. Vue.nextTick(() => {
  123. socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => {
  124. vueImage.images = data;
  125. if(!silent) {
  126. vueImage.isLoading = false;
  127. }
  128. vueImage.attachContextMenus();
  129. });
  130. });
  131. },
  132. /**
  133. * Select an image
  134. *
  135. * @param {String} imageId The image identifier
  136. * @return {Void} Void
  137. */
  138. selectImage: (imageId) => {
  139. vueImage.currentImage = imageId;
  140. },
  141. /**
  142. * Set image alignment
  143. *
  144. * @param {String} align The alignment
  145. * @return {Void} Void
  146. */
  147. selectAlignment: (align) => {
  148. vueImage.currentAlign = align;
  149. },
  150. /**
  151. * Attach right-click context menus to images and folders
  152. *
  153. * @return {Void} Void
  154. */
  155. attachContextMenus: () => {
  156. let moveFolders = _.map(vueImage.folders, (f) => {
  157. return {
  158. name: (f !== '') ? f : '/ (root)',
  159. icon: 'fa-folder'
  160. };
  161. });
  162. $.contextMenu('destroy', '.editor-modal-imagechoices > figure');
  163. $.contextMenu({
  164. selector: '.editor-modal-imagechoices > figure',
  165. appendTo: '.editor-modal-imagechoices',
  166. position: (opt, x, y) => {
  167. $(opt.$trigger).addClass('is-contextopen');
  168. let trigPos = $(opt.$trigger).position();
  169. let trigDim = { w: $(opt.$trigger).width() / 2, h: $(opt.$trigger).height() / 2 }
  170. opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w });
  171. },
  172. events: {
  173. hide: (opt) => {
  174. $(opt.$trigger).removeClass('is-contextopen');
  175. }
  176. },
  177. items: {
  178. rename: {
  179. name: "Rename",
  180. icon: "fa-edit",
  181. callback: (key, opt) => {
  182. alert("Clicked on " + key);
  183. }
  184. },
  185. move: {
  186. name: "Move to...",
  187. icon: "fa-folder-open-o",
  188. items: moveFolders
  189. },
  190. delete: {
  191. name: "Delete",
  192. icon: "fa-trash",
  193. callback: (key, opt) => {
  194. alert("Clicked on " + key);
  195. }
  196. }
  197. }
  198. });
  199. },
  200. waitUploadComplete: () => {
  201. vueImage.postUploadChecks++;
  202. vueImage.isLoadingText = 'Processing uploads...';
  203. let currentUplAmount = vueImage.images.length;
  204. vueImage.loadImages(true);
  205. Vue.nextTick(() => {
  206. _.delay(() => {
  207. if(currentUplAmount !== vueImage.images.length) {
  208. vueImage.postUploadChecks = 0;
  209. vueImage.isLoading = false;
  210. } else if(vueImage.postUploadChecks > 5) {
  211. vueImage.postUploadChecks = 0;
  212. vueImage.isLoading = false;
  213. alerts.pushError('Unable to fetch new uploads', 'Try again later');
  214. } else {
  215. vueImage.waitUploadComplete();
  216. }
  217. }, 2000);
  218. });
  219. }
  220. }
  221. });
  222. $('#btn-editor-uploadimage input').on('change', (ev) => {
  223. $(ev.currentTarget).simpleUpload("/uploads/img", {
  224. name: 'imgfile',
  225. data: {
  226. folder: vueImage.currentFolder
  227. },
  228. limit: 20,
  229. expect: 'json',
  230. allowedExts: ["jpg", "jpeg", "gif", "png", "webp"],
  231. allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
  232. maxFileSize: 3145728, // max 3 MB
  233. init: (totalUploads) => {
  234. vueImage.uploadSucceeded = false;
  235. vueImage.isLoading = true;
  236. vueImage.isLoadingText = 'Preparing to upload...';
  237. },
  238. progress: function(progress) {
  239. vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%';
  240. },
  241. success: (data) => {
  242. if(data.ok) {
  243. let failedUpls = _.filter(data.results, ['ok', false]);
  244. if(failedUpls.length) {
  245. _.forEach(failedUpls, (u) => {
  246. alerts.pushError('Upload error', u.msg);
  247. });
  248. if(failedUpls.length < data.results.length) {
  249. alerts.push({
  250. title: 'Some uploads succeeded',
  251. message: 'Files that are not mentionned in the errors above were uploaded successfully.'
  252. });
  253. vueImage.uploadSucceeded = true;
  254. }
  255. } else {
  256. vueImage.uploadSucceeded = true;
  257. }
  258. } else {
  259. alerts.pushError('Upload error', data.msg);
  260. }
  261. },
  262. error: function(error) {
  263. alerts.pushError(error.message, this.upload.file.name);
  264. },
  265. finish: () => {
  266. if(vueImage.uploadSucceeded) {
  267. vueImage.waitUploadComplete();
  268. } else {
  269. vueImage.isLoading = false;
  270. }
  271. }
  272. });
  273. });