You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

397 lines
12 KiB

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