2
0

editor-file.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. <template lang="pug">
  2. transition(:duration="400")
  3. .modal(v-show='isShown', v-cloak)
  4. transition(name='modal-background')
  5. .modal-background(v-show='isShown')
  6. .modal-container
  7. transition(name='modal-content')
  8. .modal-content.is-expanded(v-show='isShown')
  9. header.is-green
  10. span {{ $t('editor.filetitle') }}
  11. p.modal-notify(:class='{ "is-active": isLoading }')
  12. span {{ isLoadingText }}
  13. i
  14. .modal-toolbar.is-green
  15. a.button(@click='newFolder')
  16. i.icon-folder2
  17. span {{ $t('editor.newfolder') }}
  18. a.button#btn-editor-file-upload
  19. i.icon-cloud-upload
  20. span {{ $t('editor.fileupload') }}
  21. label
  22. input(type='file', multiple, :disabled='isLoading', ref='editorFileUploadInput')
  23. section.is-gapless
  24. .columns.is-stretched
  25. .column.is-one-quarter.modal-sidebar.is-green(style={'max-width':'350px'})
  26. .model-sidebar-header {{ $t('editor.folders') }}
  27. ul.model-sidebar-list
  28. li(v-for='fld in folders')
  29. a(@click='selectFolder(fld)', :class='{ "is-active": currentFolder === fld }')
  30. i.icon-folder2
  31. span / {{ fld }}
  32. .column.editor-modal-file-choices
  33. figure(v-for='fl in files', :class='{ "is-active": currentFile === fl._id }', @click='selectFile(fl._id)', :data-uid='fl._id')
  34. i(class='icon-file')
  35. span: strong {{ fl.filename }}
  36. span {{ fl.mime }}
  37. span {{ filesize(fl.filesize) }}
  38. em(v-show='files.length < 1')
  39. i.icon-marquee-minus
  40. | {{ $t('editor.filefolderempty') }}
  41. footer
  42. a.button.is-grey.is-outlined(@click='cancel') {{ $t('editor.discard') }}
  43. a.button.is-green(@click='insertFileLink') {{ $t('editor.fileinsert') }}
  44. transition(:duration="400")
  45. .modal.is-superimposed(v-show='newFolderShow')
  46. transition(name='modal-background')
  47. .modal-background(v-show='newFolderShow')
  48. .modal-container
  49. transition(name='modal-content')
  50. .modal-content(v-show='newFolderShow')
  51. header.is-light-blue {{ $t('modal.newfoldertitle') }}
  52. section
  53. label.label {{ $t('modal.newfoldername') }}
  54. p.control.is-fullwidth
  55. input.input(type='text', :placeholder='$t("modal.newfoldernameplaceholder")', v-model='newFolderName', ref='editorFileNewFolderInput', @keyup.enter='newFolderCreate', @keyup.esc='newFolderDiscard')
  56. span.help.is-danger(v-show='newFolderError') {{ $t('modal.newfolderinvalid') }}
  57. footer
  58. a.button.is-grey.is-outlined(@click='newFolderDiscard') {{ $t('modal.discard') }}
  59. a.button.is-light-blue(@click='newFolderCreate') {{ $t('modal.create') }}
  60. transition(:duration="400")
  61. .modal.is-superimposed(v-show='renameFileShow')
  62. transition(name='modal-background')
  63. .modal-background(v-show='renameFileShow')
  64. .modal-container
  65. transition(name='modal-content')
  66. .modal-content(v-show='renameFileShow')
  67. header.is-indigo {{ $t('modal.renamefiletitle') }}
  68. section
  69. label.label {{ $t('modal.renamefilename') }}
  70. p.control.is-fullwidth
  71. input.input#txt-editor-file-rename(type='text', :placeholder='$t("modal.renamefilenameplaceholder")', v-model='renameFileFilename', ref='editorFileRenameInput', @keyup.enter='renameFileGo', @keyup.esc='renameFileDiscard')
  72. span.help.is-danger.is-hidden {{ $t('modal.renamefileinvalid') }}
  73. footer
  74. a.button.is-grey.is-outlined(@click='renameFileDiscard') {{ $t('modal.discard') }}
  75. a.button.is-light-blue(@click='renameFileGo') {{ $t('modal.renamefile') }}
  76. transition(:duration="400")
  77. .modal.is-superimposed(v-show='deleteFileShow')
  78. transition(name='modal-background')
  79. .modal-background(v-show='deleteFileShow')
  80. .modal-container
  81. transition(name='modal-content')
  82. .modal-content(v-show='deleteFileShow')
  83. header.is-red {{ $t('modal.deletefiletitle') }}
  84. section
  85. span {{ $t('modal.deletefilewarn') }} #[strong {{deleteFileFilename}}]?
  86. footer
  87. a.button.is-grey.is-outlined(@click='deleteFileWarn(false)') {{ $t('modal.discard') }}
  88. a.button.is-red(@click='deleteFileGo') {{ $t('modal.delete') }}
  89. </template>
  90. <script>
  91. export default {
  92. name: 'editor-file',
  93. data () {
  94. return {
  95. isLoading: false,
  96. isLoadingText: '',
  97. newFolderName: '',
  98. newFolderShow: false,
  99. newFolderError: false,
  100. folders: [],
  101. currentFolder: '',
  102. currentFile: '',
  103. files: [],
  104. uploadSucceeded: false,
  105. postUploadChecks: 0,
  106. renameFileShow: false,
  107. renameFileId: '',
  108. renameFileFilename: '',
  109. deleteFileShow: false,
  110. deleteFileId: '',
  111. deleteFileFilename: ''
  112. }
  113. },
  114. computed: {
  115. isShown () {
  116. return this.$store.state.editorFile.shown
  117. }
  118. },
  119. methods: {
  120. init () {
  121. $(this.$refs.editorFileUploadInput).on('change', this.upload)
  122. this.refreshFolders()
  123. },
  124. cancel () {
  125. $(this.$refs.editorFileUploadInput).off('change', this.upload)
  126. this.$store.dispatch('editorFile/close')
  127. },
  128. filesize (rawSize) {
  129. return this.$helpers.common.filesize(rawSize)
  130. }
  131. // -------------------------------------------
  132. // INSERT LINK TO FILE
  133. // -------------------------------------------
  134. selectFile(fileId) {
  135. this.currentFile = fileId
  136. },
  137. insertFileLink() {
  138. let selFile = this._.find(this.files, ['_id', this.currentFile])
  139. selFile.normalizedPath = (selFile.folder === 'f:') ? selFile.filename : selFile.folder.slice(2) + '/' + selFile.filename
  140. selFile.titleGuess = this._.startCase(selFile.basename)
  141. let fileText = '[' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")'
  142. this.$store.dispatch('editor/insert', fileText)
  143. this.$store.dispatch('alert', {
  144. style: 'blue',
  145. icon: 'paper',
  146. msg: this.$t('editor.filesuccess')
  147. })
  148. this.cancel()
  149. },
  150. // -------------------------------------------
  151. // NEW FOLDER
  152. // -------------------------------------------
  153. newFolder() {
  154. let self = this
  155. this.newFolderName = ''
  156. this.newFolderError = false
  157. this.newFolderShow = true
  158. this._.delay(() => { self.$refs.editorFileNewFolderInput.focus() }, 400)
  159. },
  160. newFolderDiscard() {
  161. this.newFolderShow = false
  162. },
  163. newFolderCreate() {
  164. let self = this
  165. let regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$')
  166. this.newFolderName = this._.kebabCase(this._.trim(this.newFolderName))
  167. if (this._.isEmpty(this.newFolderName) || !regFolderName.test(this.newFolderName)) {
  168. this.newFolderError = true
  169. return
  170. }
  171. this.newFolderDiscard()
  172. this.isLoadingText = this.$t('modal.newfolderloading')
  173. this.isLoading = true
  174. this.$nextTick(() => {
  175. socket.emit('uploadsCreateFolder', { foldername: self.newFolderName }, (data) => {
  176. self.folders = data
  177. self.currentFolder = self.newFolderName
  178. self.files = []
  179. self.isLoading = false
  180. self.$store.dispatch('alert', {
  181. style: 'blue',
  182. icon: 'folder2',
  183. msg: self.$t('modal.newfoldersuccess', { name: self.newFolderName })
  184. })
  185. })
  186. })
  187. },
  188. // -------------------------------------------
  189. // RENAME FILE
  190. // -------------------------------------------
  191. renameFile() {
  192. let self = this
  193. let c = this._.find(this.files, [ '_id', this.renameFileId ])
  194. this.renameFileFilename = c.basename || ''
  195. this.renameFileShow = true
  196. this._.delay(() => {
  197. self.$refs.editorFileRenameInput.select()
  198. }, 100)
  199. },
  200. renameFileDiscard() {
  201. this.renameFileShow = false
  202. },
  203. renameFileGo() {
  204. let self = this
  205. this.renameFileDiscard()
  206. this.isLoadingText = this.$t('modal.renamefileloading')
  207. this.isLoading = true
  208. this.$nextTick(() => {
  209. socket.emit('uploadsRenameFile', { uid: self.renameFileId, folder: self.currentFolder, filename: self.renameFileFilename }, (data) => {
  210. if (data.ok) {
  211. self.waitChangeComplete(self.files.length, false)
  212. } else {
  213. self.isLoading = false
  214. self.$store.dispatch('alert', {
  215. style: 'red',
  216. icon: 'square-cross',
  217. msg: self.$t('modal.renamefileerror', { err: data.msg })
  218. })
  219. }
  220. })
  221. })
  222. },
  223. // -------------------------------------------
  224. // MOVE FILE
  225. // -------------------------------------------
  226. moveFile(uid, fld) {
  227. let self = this
  228. this.isLoadingText = this.$t('editor.filemoveloading')
  229. this.isLoading = true
  230. this.$nextTick(() => {
  231. socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
  232. if (data.ok) {
  233. self.loadFiles()
  234. self.$store.dispatch('alert', {
  235. style: 'blue',
  236. icon: 'arrow-right2',
  237. msg: self.$t('editor.filemovesuccess')
  238. })
  239. } else {
  240. self.isLoading = false
  241. self.$store.dispatch('alert', {
  242. style: 'red',
  243. icon: 'square-cross',
  244. msg: self.$t('editor.filemoveerror', { err: data.msg })
  245. })
  246. }
  247. })
  248. })
  249. },
  250. // -------------------------------------------
  251. // DELETE FILE
  252. // -------------------------------------------
  253. deleteFileWarn(show) {
  254. if (show) {
  255. let c = this._.find(this.files, [ '_id', this.deleteFileId ])
  256. this.deleteFileFilename = c.filename || this.$t('editor.filedeletedefault')
  257. }
  258. this.deleteFileShow = show
  259. },
  260. deleteFileGo() {
  261. let self = this
  262. this.deleteFileWarn(false)
  263. this.isLoadingText = this.$t('editor.filedeleteloading')
  264. this.isLoading = true
  265. this.$nextTick(() => {
  266. socket.emit('uploadsDeleteFile', { uid: this.deleteFileId }, (data) => {
  267. self.loadFiles()
  268. self.$store.dispatch('alert', {
  269. style: 'blue',
  270. icon: 'trash2',
  271. msg: self.$t('editor.filedeletesuccess')
  272. })
  273. })
  274. })
  275. },
  276. // -------------------------------------------
  277. // LOAD FROM REMOTE
  278. // -------------------------------------------
  279. selectFolder(fldName) {
  280. this.currentFolder = fldName
  281. this.loadFiles()
  282. },
  283. refreshFolders() {
  284. let self = this
  285. this.isLoadingText = this.$t('editor.foldersloading')
  286. this.isLoading = true
  287. this.currentFolder = ''
  288. this.currentImage = ''
  289. this.$nextTick(() => {
  290. socket.emit('uploadsGetFolders', { }, (data) => {
  291. self.folders = data
  292. self.loadFiles()
  293. })
  294. })
  295. },
  296. loadFiles(silent) {
  297. let self = this
  298. if (!silent) {
  299. this.isLoadingText = this.$t('editor.fileloading')
  300. this.isLoading = true
  301. }
  302. return new Promise((resolve, reject) => {
  303. self.$nextTick(() => {
  304. socket.emit('uploadsGetFiles', { folder: self.currentFolder }, (data) => {
  305. self.files = data
  306. if (!silent) {
  307. self.isLoading = false
  308. }
  309. self.attachContextMenus()
  310. resolve(true)
  311. })
  312. })
  313. })
  314. },
  315. waitChangeComplete(oldAmount, expectChange) {
  316. let self = this
  317. expectChange = (this._.isBoolean(expectChange)) ? expectChange : true
  318. this.postUploadChecks++
  319. this.isLoadingText = this.$t('editor.fileprocessing')
  320. this.$nextTick(() => {
  321. self.loadFiles(true).then(() => {
  322. if ((self.files.length !== oldAmount) === expectChange) {
  323. self.postUploadChecks = 0
  324. self.isLoading = false
  325. } else if (self.postUploadChecks > 5) {
  326. self.postUploadChecks = 0
  327. self.isLoading = false
  328. self.$store.dispatch('alert', {
  329. style: 'red',
  330. icon: 'square-cross',
  331. msg: self.$t('editor.fileerror')
  332. })
  333. } else {
  334. self._.delay(() => {
  335. self.waitChangeComplete(oldAmount, expectChange)
  336. }, 1500)
  337. }
  338. })
  339. })
  340. },
  341. // -------------------------------------------
  342. // IMAGE CONTEXT MENU
  343. // -------------------------------------------
  344. attachContextMenus() {
  345. let self = this
  346. let moveFolders = this._.map(this.folders, (f) => {
  347. return {
  348. name: (f !== '') ? f : '/ (root)',
  349. icon: 'icon-folder2',
  350. callback: (key, opt) => {
  351. let moveFileId = self._.toString($(opt.$trigger).data('uid'))
  352. let moveFileDestFolder = self._.nth(self.folders, key)
  353. self.moveFile(moveFileId, moveFileDestFolder)
  354. }
  355. }
  356. })
  357. $.contextMenu('destroy', '.editor-modal-file-choices > figure')
  358. $.contextMenu({
  359. selector: '.editor-modal-file-choices > figure',
  360. appendTo: '.editor-modal-file-choices',
  361. position: (opt, x, y) => {
  362. $(opt.$trigger).addClass('is-contextopen')
  363. let trigPos = $(opt.$trigger).position()
  364. let trigDim = { w: $(opt.$trigger).width() / 5, h: $(opt.$trigger).height() / 2 }
  365. opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w })
  366. },
  367. events: {
  368. hide: (opt) => {
  369. $(opt.$trigger).removeClass('is-contextopen')
  370. }
  371. },
  372. items: {
  373. rename: {
  374. name: self.$t('editor.filerenameaction'),
  375. icon: 'icon-edit',
  376. callback: (key, opt) => {
  377. self.renameFileId = self._.toString(opt.$trigger[0].dataset.uid)
  378. self.renameFile()
  379. }
  380. },
  381. move: {
  382. name: self.$t('editor.filemoveaction'),
  383. icon: 'fa-folder-open-o',
  384. items: moveFolders
  385. },
  386. delete: {
  387. name: self.$t('editor.filedeleteaction'),
  388. icon: 'icon-trash2',
  389. callback: (key, opt) => {
  390. self.deleteFileId = self._.toString(opt.$trigger[0].dataset.uid)
  391. self.deleteFileWarn(true)
  392. }
  393. }
  394. }
  395. })
  396. },
  397. upload() {
  398. let self = this
  399. let curFileAmount = this.files.length
  400. $(this.$refs.editorFileUploadInput).simpleUpload('/uploads/file', {
  401. name: 'binfile',
  402. data: {
  403. folder: self.currentFolder
  404. },
  405. limit: 20,
  406. expect: 'json',
  407. maxFileSize: 0,
  408. init: (totalUploads) => {
  409. self.uploadSucceeded = false
  410. self.isLoadingText = 'Preparing to upload...'
  411. self.isLoading = true
  412. },
  413. progress: (progress) => {
  414. self.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
  415. },
  416. success: (data) => {
  417. if (data.ok) {
  418. let failedUpls = self._.filter(data.results, ['ok', false])
  419. if (failedUpls.length) {
  420. self._.forEach(failedUpls, (u) => {
  421. self.$store.dispatch('alert', {
  422. style: 'red',
  423. icon: 'square-cross',
  424. msg: self.$t('editor.fileuploaderror', { err: u.msg })
  425. })
  426. })
  427. if (failedUpls.length < data.results.length) {
  428. self.uploadSucceeded = true
  429. }
  430. } else {
  431. self.uploadSucceeded = true
  432. self.$store.dispatch('alert', {
  433. style: 'blue',
  434. icon: 'cloud-upload',
  435. msg: self.$t('editor.fileuploadsuccess')
  436. })
  437. }
  438. } else {
  439. self.$store.dispatch('alert', {
  440. style: 'red',
  441. icon: 'square-cross',
  442. msg: self.$t('editor.fileuploaderror', { err: data.msg })
  443. })
  444. }
  445. },
  446. error: (error) => {
  447. self.$store.dispatch('alert', {
  448. style: 'red',
  449. icon: 'square-cross',
  450. msg: self.$t('editor.fileuploaderror', { err: error.message })
  451. })
  452. },
  453. finish: () => {
  454. if (self.uploadSucceeded) {
  455. self.waitChangeComplete(curFileAmount, true)
  456. } else {
  457. self.isLoading = false
  458. }
  459. }
  460. })
  461. }
  462. },
  463. mounted() {
  464. this.$root.$on('editorFile/init', this.init)
  465. }
  466. }
  467. </script>