editor-file.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  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(v-bind:class='{ "is-active": isLoading }')
  12. span {{ isLoadingText }}
  13. i
  14. .modal-toolbar.is-green
  15. a.button(v-on: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)
  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(v-on:click='selectFolder(fld)', v-bind: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', v-bind:class='{ "is-active": currentFile === fl._id }', v-on:click='selectFile(fl._id)', v-bind: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(v-on:click='cancel') {{ $t('editor.discard') }}
  43. a.button.is-green(v-on:click='insertFileLink') {{ $t('editor.fileinsert') }}
  44. .modal.is-superimposed(v-show='newFolderShow')
  45. transition(name='modal-background')
  46. .modal-background
  47. .modal-container(v-show='newFolderShow')
  48. transition(name='modal-content')
  49. .modal-content(v-show='newFolderShow')
  50. header.is-light-blue {{ $t('modal.newfoldertitle') }}
  51. section
  52. label.label {{ $t('modal.newfoldername') }}
  53. p.control.is-fullwidth
  54. input.input(type='text', v-bind:placeholder='$t("modal.newfoldernameplaceholder")', v-model='newFolderName', ref='editorFileNewFolderInput', v-on:keyup.enter='newFolderCreate', v-on:keyup.esc='newFolderDiscard')
  55. span.help.is-danger(v-show='newFolderError') {{ $t('modal.newfolderinvalid') }}
  56. footer
  57. a.button.is-grey.is-outlined(v-on:click='newFolderDiscard') {{ $t('modal.discard') }}
  58. a.button.is-light-blue(v-on:click='newFolderCreate') {{ $t('modal.create') }}
  59. .modal.is-superimposed(v-show='renameFileShow')
  60. .modal-background
  61. .modal-container
  62. .modal-content
  63. header.is-indigo {{ $t('modal.renamefiletitle') }}
  64. section
  65. label.label {{ $t('modal.renamefilename') }}
  66. p.control.is-fullwidth
  67. input.input#txt-editor-file-rename(type='text', v-bind:placeholder='$t("modal.renamefilenameplaceholder")', v-model='renameFileFilename', ref='editorFileRenameInput', v-on:keyup.enter='renameFileGo', v-on:keyup.esc='renameFileDiscard')
  68. span.help.is-danger.is-hidden {{ $t('modal.renamefileinvalid') }}
  69. footer
  70. a.button.is-grey.is-outlined(v-on:click='renameFileDiscard') {{ $t('modal.discard') }}
  71. a.button.is-light-blue(v-on:click='renameFileGo') {{ $t('modal.renamefile') }}
  72. .modal.is-superimposed(v-show='deleteFileShow')
  73. .modal-background
  74. .modal-container
  75. .modal-content
  76. header.is-red {{ $t('modal.deletefiletitle') }}
  77. section
  78. span {{ $t('modal.deletefilewarn') }} #[strong {{deleteFileFilename}}]?
  79. footer
  80. a.button.is-grey.is-outlined(v-on:click='deleteFileWarn(false)') {{ $t('modal.discard') }}
  81. a.button.is-red(v-on:click='deleteFileGo') {{ $t('modal.delete') }}
  82. </template>
  83. <script>
  84. export default {
  85. name: 'editor-file',
  86. data () {
  87. return {
  88. isLoading: false,
  89. isLoadingText: '',
  90. newFolderName: '',
  91. newFolderShow: false,
  92. newFolderError: false,
  93. folders: [],
  94. currentFolder: '',
  95. currentFile: '',
  96. files: [],
  97. uploadSucceeded: false,
  98. postUploadChecks: 0,
  99. renameFileShow: false,
  100. renameFileId: '',
  101. renameFileFilename: '',
  102. deleteFileShow: false,
  103. deleteFileId: '',
  104. deleteFileFilename: ''
  105. }
  106. },
  107. computed: {
  108. isShown () {
  109. return this.$store.state.editorFile.shown
  110. }
  111. },
  112. methods: {
  113. init () {
  114. this.refreshFolders()
  115. },
  116. cancel () {
  117. this.$store.dispatch('editorFile/close')
  118. },
  119. filesize (rawSize) {
  120. return this.$helpers.common.filesize(rawSize)
  121. }
  122. // -------------------------------------------
  123. // INSERT LINK TO FILE
  124. // -------------------------------------------
  125. selectFile(fileId) {
  126. this.currentFile = fileId
  127. },
  128. insertFileLink() {
  129. let selFile = this._.find(this.files, ['_id', this.currentFile])
  130. selFile.normalizedPath = (selFile.folder === 'f:') ? selFile.filename : selFile.folder.slice(2) + '/' + selFile.filename
  131. selFile.titleGuess = this._.startCase(selFile.basename)
  132. let fileText = '[' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")'
  133. this.$store.dispatch('editor/insert', fileText)
  134. this.$store.dispatch('alert', {
  135. style: 'blue',
  136. icon: 'paper',
  137. msg: this.$t('editor.filesuccess')
  138. })
  139. this.cancel()
  140. },
  141. // -------------------------------------------
  142. // NEW FOLDER
  143. // -------------------------------------------
  144. newFolder() {
  145. let self = this
  146. this.newFolderName = ''
  147. this.newFolderError = false
  148. this.newFolderShow = true
  149. this._.delay(() => { self.$refs.editorFileNewFolderInput.focus() }, 400)
  150. },
  151. newFolderDiscard() {
  152. this.newFolderShow = false
  153. },
  154. newFolderCreate() {
  155. let self = this
  156. let regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$')
  157. this.newFolderName = this._.kebabCase(this._.trim(this.newFolderName))
  158. if (this._.isEmpty(this.newFolderName) || !regFolderName.test(this.newFolderName)) {
  159. this.newFolderError = true
  160. return
  161. }
  162. this.newFolderDiscard()
  163. this.isLoadingText = this.$t('modal.newfolderloading')
  164. this.isLoading = true
  165. this.$nextTick(() => {
  166. socket.emit('uploadsCreateFolder', { foldername: self.newFolderName }, (data) => {
  167. self.folders = data
  168. self.currentFolder = self.newFolderName
  169. self.files = []
  170. self.isLoading = false
  171. self.$store.dispatch('alert', {
  172. style: 'blue',
  173. icon: 'folder2',
  174. msg: self.$t('modal.newfoldersuccess', { name: self.newFolderName })
  175. })
  176. })
  177. })
  178. },
  179. // -------------------------------------------
  180. // RENAME FILE
  181. // -------------------------------------------
  182. renameFile() {
  183. let self = this
  184. let c = this._.find(this.files, [ '_id', this.renameFileId ])
  185. this.renameFileFilename = c.basename || ''
  186. this.renameFileShow = true
  187. this._.delay(() => {
  188. self.$refs.editorFileRenameInput.select()
  189. }, 100)
  190. },
  191. renameFileDiscard() {
  192. this.renameFileShow = false
  193. },
  194. renameFileGo() {
  195. let self = this
  196. this.renameFileDiscard()
  197. this.isLoadingText = this.$t('modal.renamefileloading')
  198. this.isLoading = true
  199. this.$nextTick(() => {
  200. socket.emit('uploadsRenameFile', { uid: self.renameFileId, folder: self.currentFolder, filename: self.renameFileFilename }, (data) => {
  201. if (data.ok) {
  202. self.waitChangeComplete(self.files.length, false)
  203. } else {
  204. self.isLoading = false
  205. self.$store.dispatch('alert', {
  206. style: 'red',
  207. icon: 'square-cross',
  208. msg: self.$t('modal.renamefileerror', { err: data.msg })
  209. })
  210. }
  211. })
  212. })
  213. },
  214. // -------------------------------------------
  215. // MOVE FILE
  216. // -------------------------------------------
  217. moveFile(uid, fld) {
  218. let self = this
  219. this.isLoadingText = this.$t('editor.filemoveloading')
  220. this.isLoading = true
  221. this.$nextTick(() => {
  222. socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
  223. if (data.ok) {
  224. self.loadFiles()
  225. } else {
  226. self.isLoading = false
  227. self.$store.dispatch('alert', {
  228. style: 'red',
  229. icon: 'square-cross',
  230. msg: self.$t('editor.filemoveerror', { err: data.msg })
  231. })
  232. }
  233. })
  234. })
  235. },
  236. // -------------------------------------------
  237. // DELETE FILE
  238. // -------------------------------------------
  239. deleteFileWarn(show) {
  240. if (show) {
  241. let c = this._.find(this.files, [ '_id', this.deleteFileId ])
  242. this.deleteFileFilename = c.filename || this.$t('editor.filedeletedefault')
  243. }
  244. this.deleteFileShow = show
  245. },
  246. deleteFileGo() {
  247. let self = this
  248. this.deleteFileWarn(false)
  249. this.isLoadingText = this.$t('editor.filedeleteloading')
  250. this.isLoading = true
  251. this.$nextTick(() => {
  252. socket.emit('uploadsDeleteFile', { uid: this.deleteFileId }, (data) => {
  253. self.loadFiles()
  254. })
  255. })
  256. },
  257. // -------------------------------------------
  258. // LOAD FROM REMOTE
  259. // -------------------------------------------
  260. selectFolder(fldName) {
  261. this.currentFolder = fldName
  262. this.loadFiles()
  263. },
  264. refreshFolders() {
  265. let self = this
  266. this.isLoadingText = this.$t('editor.foldersloading')
  267. this.isLoading = true
  268. this.currentFolder = ''
  269. this.currentImage = ''
  270. this.$nextTick(() => {
  271. socket.emit('uploadsGetFolders', { }, (data) => {
  272. self.folders = data
  273. self.loadFiles()
  274. })
  275. })
  276. },
  277. loadFiles(silent) {
  278. let self = this
  279. if (!silent) {
  280. this.isLoadingText = this.$t('editor.fileloading')
  281. this.isLoading = true
  282. }
  283. return new Promise((resolve, reject) => {
  284. self.$nextTick(() => {
  285. socket.emit('uploadsGetFiles', { folder: self.currentFolder }, (data) => {
  286. self.files = data
  287. if (!silent) {
  288. self.isLoading = false
  289. }
  290. self.attachContextMenus()
  291. resolve(true)
  292. })
  293. })
  294. })
  295. },
  296. waitChangeComplete(oldAmount, expectChange) {
  297. let self = this
  298. expectChange = (this._.isBoolean(expectChange)) ? expectChange : true
  299. this.postUploadChecks++
  300. this.isLoadingText = this.$t('editor.fileprocessing')
  301. this.$nextTick(() => {
  302. self.loadFiles(true).then(() => {
  303. if ((self.files.length !== oldAmount) === expectChange) {
  304. self.postUploadChecks = 0
  305. self.isLoading = false
  306. } else if (self.postUploadChecks > 5) {
  307. self.postUploadChecks = 0
  308. self.isLoading = false
  309. self.$store.dispatch('alert', {
  310. style: 'red',
  311. icon: 'square-cross',
  312. msg: self.$t('editor.fileerror')
  313. })
  314. } else {
  315. self._.delay(() => {
  316. self.waitChangeComplete(oldAmount, expectChange)
  317. }, 1500)
  318. }
  319. })
  320. })
  321. },
  322. // -------------------------------------------
  323. // IMAGE CONTEXT MENU
  324. // -------------------------------------------
  325. attachContextMenus() {
  326. let self = this
  327. let moveFolders = this._.map(this.folders, (f) => {
  328. return {
  329. name: (f !== '') ? f : '/ (root)',
  330. icon: 'icon-folder2',
  331. callback: (key, opt) => {
  332. let moveFileId = self._.toString($(opt.$trigger).data('uid'))
  333. let moveFileDestFolder = self._.nth(self.folders, key)
  334. self.moveFile(moveFileId, moveFileDestFolder)
  335. }
  336. }
  337. })
  338. $.contextMenu('destroy', '.editor-modal-file-choices > figure')
  339. $.contextMenu({
  340. selector: '.editor-modal-file-choices > figure',
  341. appendTo: '.editor-modal-file-choices',
  342. position: (opt, x, y) => {
  343. $(opt.$trigger).addClass('is-contextopen')
  344. let trigPos = $(opt.$trigger).position()
  345. let trigDim = { w: $(opt.$trigger).width() / 5, h: $(opt.$trigger).height() / 2 }
  346. opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w })
  347. },
  348. events: {
  349. hide: (opt) => {
  350. $(opt.$trigger).removeClass('is-contextopen')
  351. }
  352. },
  353. items: {
  354. rename: {
  355. name: self.$t('editor.filerenameaction'),
  356. icon: 'icon-edit',
  357. callback: (key, opt) => {
  358. self.renameFileId = self._.toString(opt.$trigger[0].dataset.uid)
  359. self.renameFile()
  360. }
  361. },
  362. move: {
  363. name: self.$t('editor.filemoveaction'),
  364. icon: 'fa-folder-open-o',
  365. items: moveFolders
  366. },
  367. delete: {
  368. name: self.$t('editor.filedeleteaction'),
  369. icon: 'icon-trash2',
  370. callback: (key, opt) => {
  371. self.deleteFileId = self._.toString(opt.$trigger[0].dataset.uid)
  372. self.deleteFileWarn(true)
  373. }
  374. }
  375. }
  376. })
  377. }
  378. },
  379. mounted() {
  380. this.$root.$on('editorFile/init', this.init)
  381. }
  382. }
  383. </script>