editor-image.js 8.0 KB

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