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.

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