editor-image.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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. renameImageShow: false,
  19. renameImageId: '',
  20. renameImageFilename: '',
  21. deleteImageShow: false,
  22. deleteImageId: '',
  23. deleteImageFilename: ''
  24. },
  25. methods: {
  26. open: () => {
  27. mdeModalOpenState = true;
  28. $('#modal-editor-image').slideDown();
  29. vueImage.refreshFolders();
  30. },
  31. cancel: (ev) => {
  32. mdeModalOpenState = false;
  33. $('#modal-editor-image').slideUp();
  34. },
  35. // -------------------------------------------
  36. // INSERT IMAGE
  37. // -------------------------------------------
  38. selectImage: (imageId) => {
  39. vueImage.currentImage = imageId;
  40. },
  41. selectAlignment: (align) => {
  42. vueImage.currentAlign = align;
  43. },
  44. insertImage: (ev) => {
  45. if(mde.codemirror.doc.somethingSelected()) {
  46. mde.codemirror.execCommand('singleSelection');
  47. }
  48. let selImage = _.find(vueImage.images, ['_id', vueImage.currentImage]);
  49. selImage.normalizedPath = (selImage.folder === 'f:') ? selImage.filename : selImage.folder.slice(2) + '/' + selImage.filename;
  50. selImage.titleGuess = _.startCase(selImage.basename);
  51. let imageText = '![' + selImage.titleGuess + '](/uploads/' + selImage.normalizedPath + ' "' + selImage.titleGuess + '")';
  52. switch(vueImage.currentAlign) {
  53. case 'center':
  54. imageText += '{.align-center}';
  55. break;
  56. case 'right':
  57. imageText += '{.align-right}';
  58. break;
  59. case 'logo':
  60. imageText += '{.pagelogo}';
  61. break;
  62. }
  63. mde.codemirror.doc.replaceSelection(imageText);
  64. vueImage.cancel();
  65. },
  66. // -------------------------------------------
  67. // NEW FOLDER
  68. // -------------------------------------------
  69. newFolder: (ev) => {
  70. vueImage.newFolderName = '';
  71. vueImage.newFolderError = false;
  72. vueImage.newFolderShow = true;
  73. _.delay(() => { $('#txt-editor-newfoldername').focus(); }, 400);
  74. },
  75. newFolderDiscard: (ev) => {
  76. vueImage.newFolderShow = false;
  77. },
  78. newFolderCreate: (ev) => {
  79. let regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");
  80. vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName));
  81. if(_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) {
  82. vueImage.newFolderError = true;
  83. return;
  84. }
  85. vueImage.newFolderDiscard();
  86. vueImage.isLoadingText = 'Creating new folder...';
  87. vueImage.isLoading = true;
  88. Vue.nextTick(() => {
  89. socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => {
  90. vueImage.folders = data;
  91. vueImage.currentFolder = vueImage.newFolderName;
  92. vueImage.images = [];
  93. vueImage.isLoading = false;
  94. });
  95. });
  96. },
  97. // -------------------------------------------
  98. // FETCH FROM URL
  99. // -------------------------------------------
  100. fetchFromUrl: (ev) => {
  101. vueImage.fetchFromUrlURL = '';
  102. vueImage.fetchFromUrlShow = true;
  103. _.delay(() => { $('#txt-editor-fetchimgurl').focus(); }, 400);
  104. },
  105. fetchFromUrlDiscard: (ev) => {
  106. vueImage.fetchFromUrlShow = false;
  107. },
  108. fetchFromUrlGo: (ev) => {
  109. vueImage.fetchFromUrlDiscard();
  110. vueImage.isLoadingText = 'Fetching image...';
  111. vueImage.isLoading = true;
  112. Vue.nextTick(() => {
  113. socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => {
  114. if(data.ok) {
  115. vueImage.waitChangeComplete(vueImage.images.length, true);
  116. } else {
  117. vueImage.isLoading = false;
  118. alerts.pushError('Upload error', data.msg);
  119. }
  120. });
  121. });
  122. },
  123. // -------------------------------------------
  124. // RENAME IMAGE
  125. // -------------------------------------------
  126. renameImage: () => {
  127. let c = _.find(vueImage.images, ['_id', vueImage.renameImageId ]);
  128. vueImage.renameImageFilename = c.basename || '';
  129. vueImage.renameImageShow = true;
  130. _.delay(() => {
  131. $('#txt-editor-renameimage').focus();
  132. _.defer(() => { $('#txt-editor-renameimage').select(); });
  133. }, 400);
  134. },
  135. renameImageDiscard: () => {
  136. vueImage.renameImageShow = false;
  137. },
  138. renameImageGo: () => {
  139. vueImage.renameImageDiscard();
  140. vueImage.isLoadingText = 'Renaming image...';
  141. vueImage.isLoading = true;
  142. Vue.nextTick(() => {
  143. socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => {
  144. if(data.ok) {
  145. vueImage.waitChangeComplete(vueImage.images.length, false);
  146. } else {
  147. vueImage.isLoading = false;
  148. alerts.pushError('Rename error', data.msg);
  149. }
  150. });
  151. });
  152. },
  153. // -------------------------------------------
  154. // MOVE IMAGE
  155. // -------------------------------------------
  156. moveImage: (uid, fld) => {
  157. vueImage.isLoadingText = 'Moving image...';
  158. vueImage.isLoading = true;
  159. Vue.nextTick(() => {
  160. socket.emit('uploadsMoveFile', { uid: uid, folder: fld }, (data) => {
  161. if(data.ok) {
  162. vueImage.loadImages();
  163. } else {
  164. vueImage.isLoading = false;
  165. alerts.pushError('Rename error', data.msg);
  166. }
  167. });
  168. });
  169. },
  170. // -------------------------------------------
  171. // DELETE IMAGE
  172. // -------------------------------------------
  173. deleteImageWarn: (show) => {
  174. if(show) {
  175. let c = _.find(vueImage.images, ['_id', vueImage.deleteImageId ]);
  176. vueImage.deleteImageFilename = c.filename || 'this image';
  177. }
  178. vueImage.deleteImageShow = show;
  179. },
  180. deleteImageGo: () => {
  181. vueImage.deleteImageWarn(false);
  182. vueImage.isLoadingText = 'Deleting image...';
  183. vueImage.isLoading = true;
  184. Vue.nextTick(() => {
  185. socket.emit('uploadsDeleteFile', { uid: vueImage.deleteImageId }, (data) => {
  186. vueImage.loadImages();
  187. });
  188. });
  189. },
  190. // -------------------------------------------
  191. // LOAD FROM REMOTE
  192. // -------------------------------------------
  193. selectFolder: (fldName) => {
  194. vueImage.currentFolder = fldName;
  195. vueImage.loadImages();
  196. },
  197. refreshFolders: () => {
  198. vueImage.isLoadingText = 'Fetching folders list...';
  199. vueImage.isLoading = true;
  200. vueImage.currentFolder = '';
  201. vueImage.currentImage = '';
  202. Vue.nextTick(() => {
  203. socket.emit('uploadsGetFolders', { }, (data) => {
  204. vueImage.folders = data;
  205. vueImage.loadImages();
  206. });
  207. });
  208. },
  209. loadImages: (silent) => {
  210. if(!silent) {
  211. vueImage.isLoadingText = 'Fetching images...';
  212. vueImage.isLoading = true;
  213. }
  214. return new Promise((resolve, reject) => {
  215. Vue.nextTick(() => {
  216. socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => {
  217. vueImage.images = data;
  218. if(!silent) {
  219. vueImage.isLoading = false;
  220. }
  221. vueImage.attachContextMenus();
  222. resolve(true);
  223. });
  224. });
  225. });
  226. },
  227. waitChangeComplete: (oldAmount, expectChange) => {
  228. expectChange = (_.isBoolean(expectChange)) ? expectChange : true;
  229. vueImage.postUploadChecks++;
  230. vueImage.isLoadingText = 'Processing...';
  231. Vue.nextTick(() => {
  232. vueImage.loadImages(true).then(() => {
  233. if((vueImage.images.length !== oldAmount) === expectChange) {
  234. vueImage.postUploadChecks = 0;
  235. vueImage.isLoading = false;
  236. } else if(vueImage.postUploadChecks > 5) {
  237. vueImage.postUploadChecks = 0;
  238. vueImage.isLoading = false;
  239. alerts.pushError('Unable to fetch updated listing', 'Try again later');
  240. } else {
  241. _.delay(() => {
  242. vueImage.waitChangeComplete(oldAmount, expectChange);
  243. }, 1500);
  244. }
  245. });
  246. });
  247. },
  248. // -------------------------------------------
  249. // IMAGE CONTEXT MENU
  250. // -------------------------------------------
  251. attachContextMenus: () => {
  252. let moveFolders = _.map(vueImage.folders, (f) => {
  253. return {
  254. name: (f !== '') ? f : '/ (root)',
  255. icon: 'fa-folder',
  256. callback: (key, opt) => {
  257. let moveImageId = _.toString($(opt.$trigger).data('uid'));
  258. let moveImageDestFolder = _.nth(vueImage.folders, key);
  259. vueImage.moveImage(moveImageId, moveImageDestFolder);
  260. }
  261. };
  262. });
  263. $.contextMenu('destroy', '.editor-modal-imagechoices > figure');
  264. $.contextMenu({
  265. selector: '.editor-modal-imagechoices > figure',
  266. appendTo: '.editor-modal-imagechoices',
  267. position: (opt, x, y) => {
  268. $(opt.$trigger).addClass('is-contextopen');
  269. let trigPos = $(opt.$trigger).position();
  270. let trigDim = { w: $(opt.$trigger).width() / 2, h: $(opt.$trigger).height() / 2 }
  271. opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w });
  272. },
  273. events: {
  274. hide: (opt) => {
  275. $(opt.$trigger).removeClass('is-contextopen');
  276. }
  277. },
  278. items: {
  279. rename: {
  280. name: "Rename",
  281. icon: "fa-edit",
  282. callback: (key, opt) => {
  283. vueImage.renameImageId = _.toString(opt.$trigger[0].dataset.uid);
  284. vueImage.renameImage();
  285. }
  286. },
  287. move: {
  288. name: "Move to...",
  289. icon: "fa-folder-open-o",
  290. items: moveFolders
  291. },
  292. delete: {
  293. name: "Delete",
  294. icon: "fa-trash",
  295. callback: (key, opt) => {
  296. vueImage.deleteImageId = _.toString(opt.$trigger[0].dataset.uid);
  297. vueImage.deleteImageWarn(true);
  298. }
  299. }
  300. }
  301. });
  302. }
  303. }
  304. });
  305. $('#btn-editor-uploadimage input').on('change', (ev) => {
  306. let curImageAmount = vueImage.images.length;
  307. $(ev.currentTarget).simpleUpload("/uploads/img", {
  308. name: 'imgfile',
  309. data: {
  310. folder: vueImage.currentFolder
  311. },
  312. limit: 20,
  313. expect: 'json',
  314. allowedExts: ["jpg", "jpeg", "gif", "png", "webp"],
  315. allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
  316. maxFileSize: 3145728, // max 3 MB
  317. init: (totalUploads) => {
  318. vueImage.uploadSucceeded = false;
  319. vueImage.isLoadingText = 'Preparing to upload...';
  320. vueImage.isLoading = true;
  321. },
  322. progress: function(progress) {
  323. vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%';
  324. },
  325. success: (data) => {
  326. if(data.ok) {
  327. let failedUpls = _.filter(data.results, ['ok', false]);
  328. if(failedUpls.length) {
  329. _.forEach(failedUpls, (u) => {
  330. alerts.pushError('Upload error', u.msg);
  331. });
  332. if(failedUpls.length < data.results.length) {
  333. alerts.push({
  334. title: 'Some uploads succeeded',
  335. message: 'Files that are not mentionned in the errors above were uploaded successfully.'
  336. });
  337. vueImage.uploadSucceeded = true;
  338. }
  339. } else {
  340. vueImage.uploadSucceeded = true;
  341. }
  342. } else {
  343. alerts.pushError('Upload error', data.msg);
  344. }
  345. },
  346. error: function(error) {
  347. alerts.pushError(error.message, this.upload.file.name);
  348. },
  349. finish: () => {
  350. if(vueImage.uploadSucceeded) {
  351. vueImage.waitChangeComplete(curImageAmount, true);
  352. } else {
  353. vueImage.isLoading = false;
  354. }
  355. }
  356. });
  357. });