editor-image.js 11 KB

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