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.

407 lines
13 KiB

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