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.

352 lines
10 KiB

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