diff --git a/README.md b/README.md index c187e8a3..3fa04d2f 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Current and upcoming milestones *(major features only, see the [changelog](https ### Beta 12 > *Planned for early June release* -![Progress](http://progressed.io/bar/85) +![Progress](http://progressed.io/bar/95) - [x] Anchor with Copy to clipboard function - [x] Full UI Localization diff --git a/client/js/components/editor-file.js b/client/js/components/editor-file.js deleted file mode 100644 index 214e32b9..00000000 --- a/client/js/components/editor-file.js +++ /dev/null @@ -1,361 +0,0 @@ -'use strict' - -import $ from 'jquery' -import Vue from 'vue' -import _ from 'lodash' -import 'jquery-contextmenu' -import 'jquery-simple-upload' - -module.exports = (alerts, mde, mdeModalOpenState, socket) => { - let vueFile = new Vue({ - el: '#modal-editor-file', - data: { - isLoading: false, - isLoadingText: '', - newFolderName: '', - newFolderShow: false, - newFolderError: false, - folders: [], - currentFolder: '', - currentFile: '', - files: [], - uploadSucceeded: false, - postUploadChecks: 0, - renameFileShow: false, - renameFileId: '', - renameFileFilename: '', - deleteFileShow: false, - deleteFileId: '', - deleteFileFilename: '' - }, - methods: { - - open: () => { - mdeModalOpenState = true // eslint-disable-line no-undef - $('#modal-editor-file').addClass('is-active') - vueFile.refreshFolders() - }, - cancel: (ev) => { - mdeModalOpenState = false // eslint-disable-line no-undef - $('#modal-editor-file').removeClass('is-active') - }, - - // ------------------------------------------- - // INSERT LINK TO FILE - // ------------------------------------------- - - selectFile: (fileId) => { - vueFile.currentFile = fileId - }, - insertFileLink: (ev) => { - if (mde.codemirror.doc.somethingSelected()) { - mde.codemirror.execCommand('singleSelection') - } - - let selFile = _.find(vueFile.files, ['_id', vueFile.currentFile]) - selFile.normalizedPath = (selFile.folder === 'f:') ? selFile.filename : selFile.folder.slice(2) + '/' + selFile.filename - selFile.titleGuess = _.startCase(selFile.basename) - - let fileText = '[' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")' - - mde.codemirror.doc.replaceSelection(fileText) - vueFile.cancel() - }, - - // ------------------------------------------- - // NEW FOLDER - // ------------------------------------------- - - newFolder: (ev) => { - vueFile.newFolderName = '' - vueFile.newFolderError = false - vueFile.newFolderShow = true - _.delay(() => { $('#txt-editor-file-newfoldername').focus() }, 400) - }, - newFolderDiscard: (ev) => { - vueFile.newFolderShow = false - }, - newFolderCreate: (ev) => { - let regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$') - vueFile.newFolderName = _.kebabCase(_.trim(vueFile.newFolderName)) - - if (_.isEmpty(vueFile.newFolderName) || !regFolderName.test(vueFile.newFolderName)) { - vueFile.newFolderError = true - return - } - - vueFile.newFolderDiscard() - vueFile.isLoadingText = 'Creating new folder...' - vueFile.isLoading = true - - Vue.nextTick(() => { - socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => { - vueFile.folders = data - vueFile.currentFolder = vueFile.newFolderName - vueFile.files = [] - vueFile.isLoading = false - }) - }) - }, - - // ------------------------------------------- - // RENAME FILE - // ------------------------------------------- - - renameFile: () => { - let c = _.find(vueFile.files, [ '_id', vueFile.renameFileId ]) - vueFile.renameFileFilename = c.basename || '' - vueFile.renameFileShow = true - _.delay(() => { - $('#txt-editor-renamefile').focus() - _.defer(() => { $('#txt-editor-file-rename').select() }) - }, 400) - }, - renameFileDiscard: () => { - vueFile.renameFileShow = false - }, - renameFileGo: () => { - vueFile.renameFileDiscard() - vueFile.isLoadingText = 'Renaming file...' - vueFile.isLoading = true - - Vue.nextTick(() => { - socket.emit('uploadsRenameFile', { uid: vueFile.renameFileId, folder: vueFile.currentFolder, filename: vueFile.renameFileFilename }, (data) => { - if (data.ok) { - vueFile.waitChangeComplete(vueFile.files.length, false) - } else { - vueFile.isLoading = false - alerts.pushError('Rename error', data.msg) - } - }) - }) - }, - - // ------------------------------------------- - // MOVE FILE - // ------------------------------------------- - - moveFile: (uid, fld) => { - vueFile.isLoadingText = 'Moving file...' - vueFile.isLoading = true - Vue.nextTick(() => { - socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => { - if (data.ok) { - vueFile.loadFiles() - } else { - vueFile.isLoading = false - alerts.pushError('Rename error', data.msg) - } - }) - }) - }, - - // ------------------------------------------- - // DELETE FILE - // ------------------------------------------- - - deleteFileWarn: (show) => { - if (show) { - let c = _.find(vueFile.files, [ '_id', vueFile.deleteFileId ]) - vueFile.deleteFileFilename = c.filename || 'this file' - } - vueFile.deleteFileShow = show - }, - deleteFileGo: () => { - vueFile.deleteFileWarn(false) - vueFile.isLoadingText = 'Deleting file...' - vueFile.isLoading = true - Vue.nextTick(() => { - socket.emit('uploadsDeleteFile', { uid: vueFile.deleteFileId }, (data) => { - vueFile.loadFiles() - }) - }) - }, - - // ------------------------------------------- - // LOAD FROM REMOTE - // ------------------------------------------- - - selectFolder: (fldName) => { - vueFile.currentFolder = fldName - vueFile.loadFiles() - }, - - refreshFolders: () => { - vueFile.isLoadingText = 'Fetching folders list...' - vueFile.isLoading = true - vueFile.currentFolder = '' - vueFile.currentImage = '' - Vue.nextTick(() => { - socket.emit('uploadsGetFolders', { }, (data) => { - vueFile.folders = data - vueFile.loadFiles() - }) - }) - }, - - loadFiles: (silent) => { - if (!silent) { - vueFile.isLoadingText = 'Fetching files...' - vueFile.isLoading = true - } - return new Promise((resolve, reject) => { - Vue.nextTick(() => { - socket.emit('uploadsGetFiles', { folder: vueFile.currentFolder }, (data) => { - vueFile.files = data - if (!silent) { - vueFile.isLoading = false - } - vueFile.attachContextMenus() - resolve(true) - }) - }) - }) - }, - - waitChangeComplete: (oldAmount, expectChange) => { - expectChange = (_.isBoolean(expectChange)) ? expectChange : true - - vueFile.postUploadChecks++ - vueFile.isLoadingText = 'Processing...' - - Vue.nextTick(() => { - vueFile.loadFiles(true).then(() => { - if ((vueFile.files.length !== oldAmount) === expectChange) { - vueFile.postUploadChecks = 0 - vueFile.isLoading = false - } else if (vueFile.postUploadChecks > 5) { - vueFile.postUploadChecks = 0 - vueFile.isLoading = false - alerts.pushError('Unable to fetch updated listing', 'Try again later') - } else { - _.delay(() => { - vueFile.waitChangeComplete(oldAmount, expectChange) - }, 1500) - } - }) - }) - }, - - // ------------------------------------------- - // IMAGE CONTEXT MENU - // ------------------------------------------- - - attachContextMenus: () => { - let moveFolders = _.map(vueFile.folders, (f) => { - return { - name: (f !== '') ? f : '/ (root)', - icon: 'fa-folder', - callback: (key, opt) => { - let moveFileId = _.toString($(opt.$trigger).data('uid')) - let moveFileDestFolder = _.nth(vueFile.folders, key) - vueFile.moveFile(moveFileId, moveFileDestFolder) - } - } - }) - - $.contextMenu('destroy', '.editor-modal-file-choices > figure') - $.contextMenu({ - selector: '.editor-modal-file-choices > figure', - appendTo: '.editor-modal-file-choices', - position: (opt, x, y) => { - $(opt.$trigger).addClass('is-contextopen') - let trigPos = $(opt.$trigger).position() - let trigDim = { w: $(opt.$trigger).width() / 5, h: $(opt.$trigger).height() / 2 } - opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w }) - }, - events: { - hide: (opt) => { - $(opt.$trigger).removeClass('is-contextopen') - } - }, - items: { - rename: { - name: 'Rename', - icon: 'fa-edit', - callback: (key, opt) => { - vueFile.renameFileId = _.toString(opt.$trigger[0].dataset.uid) - vueFile.renameFile() - } - }, - move: { - name: 'Move to...', - icon: 'fa-folder-open-o', - items: moveFolders - }, - delete: { - name: 'Delete', - icon: 'fa-trash', - callback: (key, opt) => { - vueFile.deleteFileId = _.toString(opt.$trigger[0].dataset.uid) - vueFile.deleteFileWarn(true) - } - } - } - }) - } - - } - }) - - $('#btn-editor-file-upload input').on('change', (ev) => { - let curFileAmount = vueFile.files.length - - $(ev.currentTarget).simpleUpload('/uploads/file', { - - name: 'binfile', - data: { - folder: vueFile.currentFolder - }, - limit: 20, - expect: 'json', - maxFileSize: 0, - - init: (totalUploads) => { - vueFile.uploadSucceeded = false - vueFile.isLoadingText = 'Preparing to upload...' - vueFile.isLoading = true - }, - - progress: (progress) => { - vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%' - }, - - success: (data) => { - if (data.ok) { - let failedUpls = _.filter(data.results, ['ok', false]) - if (failedUpls.length) { - _.forEach(failedUpls, (u) => { - alerts.pushError('Upload error', u.msg) - }) - if (failedUpls.length < data.results.length) { - alerts.push({ - title: 'Some uploads succeeded', - message: 'Files that are not mentionned in the errors above were uploaded successfully.' - }) - vueFile.uploadSucceeded = true - } - } else { - vueFile.uploadSucceeded = true - } - } else { - alerts.pushError('Upload error', data.msg) - } - }, - - error: (error) => { - alerts.pushError('Upload error', error.message) - }, - - finish: () => { - if (vueFile.uploadSucceeded) { - vueFile.waitChangeComplete(curFileAmount, true) - } else { - vueFile.isLoading = false - } - } - - }) - }) - return vueFile -} diff --git a/client/js/components/editor-file.vue b/client/js/components/editor-file.vue index 0cbf739b..66a42dbc 100644 --- a/client/js/components/editor-file.vue +++ b/client/js/components/editor-file.vue @@ -7,7 +7,7 @@ transition(name='modal-content') .modal-content.is-expanded(v-show='isShown') header.is-green - span {{ $t('editor.filetitle') }} + span {{ (mode === 'file') ? $t('editor.filetitle') : $t('editor.imagetitle') }} p.modal-notify(:class='{ "is-active": isLoading }') span {{ isLoadingText }} i @@ -17,9 +17,12 @@ span {{ $t('editor.newfolder') }} a.button#btn-editor-file-upload i.icon-cloud-upload - span {{ $t('editor.fileupload') }} + span {{ (mode === 'file') ? $t('editor.fileupload') : $t('editor.imageupload') }} label input(type='file', multiple, :disabled='isLoading', ref='editorFileUploadInput') + a.button(v-if='mode === "image"', @click='fetchFromUrl') + i.icon-cloud-download + span Fetch from URL section.is-gapless .columns.is-stretched .column.is-one-quarter.modal-sidebar.is-green(style={'max-width':'350px'}) @@ -29,7 +32,15 @@ a(@click='selectFolder(fld)', :class='{ "is-active": currentFolder === fld }') i.icon-folder2 span / {{ fld }} - .column.editor-modal-file-choices + .model-sidebar-header(v-if='mode === "image"') Alignment + .model-sidebar-content(v-if='mode === "image"') + p.control.is-fullwidth + select(v-model='currentAlign') + option(value='left') {{ $t('editor.imagealignleft') }} + option(value='center') {{ $t('editor.imagealigncenter') }} + option(value='right') {{ $t('editor.imagealignright') }} + option(value='logo') {{ $t('editor.imagealignlogo') }} + .column.editor-modal-choices.editor-modal-file-choices(v-if='mode === "file"') figure(v-for='fl in files', :class='{ "is-active": currentFile === fl._id }', @click='selectFile(fl._id)', :data-uid='fl._id') i(class='icon-file') span: strong {{ fl.filename }} @@ -38,9 +49,17 @@ em(v-show='files.length < 1') i.icon-marquee-minus | {{ $t('editor.filefolderempty') }} + .column.editor-modal-choices.editor-modal-image-choices(v-if='mode === "image"') + figure(v-for='img in files', v-bind:class='{ "is-active": currentFile === img._id }', v-on:click='selectFile(img._id)', v-bind:data-uid='img._id') + img(v-bind:src='"/uploads/t/" + img._id + ".png"') + span: strong {{ img.basename }} + span {{ filesize(img.filesize) }} + em(v-show='files.length < 1') + i.icon-marquee-minus + | {{ $t('editor.filefolderempty') }} footer a.button.is-grey.is-outlined(@click='cancel') {{ $t('editor.discard') }} - a.button.is-green(@click='insertFileLink') {{ $t('editor.fileinsert') }} + a.button.is-green(@click='insertFileLink') {{ (mode === 'file') ? $t('editor.fileinsert') : $t('editor.imageinsert') }} transition(:duration="400") .modal.is-superimposed(v-show='newFolderShow') @@ -59,6 +78,23 @@ a.button.is-grey.is-outlined(@click='newFolderDiscard') {{ $t('modal.discard') }} a.button.is-light-blue(@click='newFolderCreate') {{ $t('modal.create') }} + transition(:duration="400") + .modal.is-superimposed(v-show='fetchFromUrlShow') + transition(name='modal-background') + .modal-background(v-show='fetchFromUrlShow') + .modal-container + transition(name='modal-content') + .modal-content(v-show='fetchFromUrlShow') + header.is-light-blue Fetch Image from URL + section + label.label Enter full URL path to the image: + p.control.is-fullwidth + input.input(type='text', placeholder='http://www.example.com/some-image.png', v-model='fetchFromUrlURL', ref='editorFileFetchInput', @keyup.enter='fetchFromUrlGo', @keyup.esc='fetchFromUrlDiscard') + span.help.is-danger.is-hidden This URL path is invalid! + footer + a.button.is-grey.is-outlined(v-on:click='fetchFromUrlDiscard') Discard + a.button.is-light-blue(v-on:click='fetchFromUrlGo') Fetch + transition(:duration="400") .modal.is-superimposed(v-show='renameFileShow') transition(name='modal-background') @@ -101,9 +137,12 @@ newFolderName: '', newFolderShow: false, newFolderError: false, + fetchFromUrlURL: '', + fetchFromUrlShow: false, folders: [], currentFolder: '', currentFile: '', + currentAlign: 'left', files: [], uploadSucceeded: false, postUploadChecks: 0, @@ -118,6 +157,9 @@ computed: { isShown () { return this.$store.state.editorFile.shown + }, + mode () { + return this.$store.state.editorFile.mode } }, methods: { @@ -145,13 +187,30 @@ selFile.normalizedPath = (selFile.folder === 'f:') ? selFile.filename : selFile.folder.slice(2) + '/' + selFile.filename selFile.titleGuess = this._.startCase(selFile.basename) - let fileText = '[' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")' + let textToInsert = '' + + if (this.mode === 'image') { + textToInsert = '![' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")' + switch (this.currentAlign) { + case 'center': + textToInsert += '{.align-center}' + break + case 'right': + textToInsert += '{.align-right}' + break + case 'logo': + textToInsert += '{.pagelogo}' + break + } + } else { + textToInsert = '[' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")' + } - this.$store.dispatch('editor/insert', fileText) + this.$store.dispatch('editor/insert', textToInsert) this.$store.dispatch('alert', { style: 'blue', icon: 'paper', - msg: this.$t('editor.filesuccess') + msg: (this.mode === 'file') ? this.$t('editor.filesuccess') : this.$t('editor.imagesuccess') }) this.cancel() }, @@ -199,6 +258,41 @@ }) }, + // ------------------------------------------- + // FETCH FROM URL + // ------------------------------------------- + + fetchFromUrl() { + let self = this + this.fetchFromUrlURL = '' + this.fetchFromUrlShow = true + this._.delay(() => { self.$refs.editorFileFetchInput.focus() }, 400) + }, + fetchFromUrlDiscard() { + this.fetchFromUrlShow = false + }, + fetchFromUrlGo() { + let self = this + this.fetchFromUrlDiscard() + this.isLoadingText = 'Fetching image...' + this.isLoading = true + + this.$nextTick(() => { + socket.emit('uploadsFetchFileFromURL', { folder: self.currentFolder, fetchUrl: self.fetchFromUrlURL }, (data) => { + if (data.ok) { + self.waitChangeComplete(self.files.length, true) + } else { + self.isLoading = false + self.$store.dispatch('alert', { + style: 'red', + icon: 'square-cross', + msg: self.$t('editor.fileuploaderror', { err: data.msg }) + }) + } + }) + }) + }, + // ------------------------------------------- // RENAME FILE // ------------------------------------------- @@ -325,7 +419,8 @@ } return new Promise((resolve, reject) => { self.$nextTick(() => { - socket.emit('uploadsGetFiles', { folder: self.currentFolder }, (data) => { + let loadAction = (self.mode === 'image') ? 'uploadsGetImages' : 'uploadsGetFiles' + socket.emit(loadAction, { folder: self.currentFolder }, (data) => { self.files = data if (!silent) { self.isLoading = false @@ -384,10 +479,10 @@ } }) - $.contextMenu('destroy', '.editor-modal-file-choices > figure') + $.contextMenu('destroy', '.editor-modal-choices > figure') $.contextMenu({ - selector: '.editor-modal-file-choices > figure', - appendTo: '.editor-modal-file-choices', + selector: '.editor-modal-choices > figure', + appendTo: '.editor-modal-choices', position: (opt, x, y) => { $(opt.$trigger).addClass('is-contextopen') let trigPos = $(opt.$trigger).position() @@ -427,16 +522,19 @@ upload() { let self = this let curFileAmount = this.files.length + let uplUrl = (self.mode === 'image') ? '/uploads/img' : '/uploads/file' - $(this.$refs.editorFileUploadInput).simpleUpload('/uploads/file', { + $(this.$refs.editorFileUploadInput).simpleUpload(uplUrl, { - name: 'binfile', + name: (self.mode === 'image') ? 'imgfile' : 'binfile', data: { folder: self.currentFolder }, limit: 20, expect: 'json', - maxFileSize: 0, + allowedExts: (self.mode === 'image') ? ['jpg', 'jpeg', 'gif', 'png', 'webp'] : undefined, + allowedTypes: (self.mode === 'image') ? ['image/png', 'image/jpeg', 'image/gif', 'image/webp'] : undefined, + maxFileSize: (self.mode === 'image') ? 3145728 : 0, // max 3 MB init: (totalUploads) => { self.uploadSucceeded = false diff --git a/client/js/components/editor-image.js b/client/js/components/editor-image.js deleted file mode 100644 index f38092e9..00000000 --- a/client/js/components/editor-image.js +++ /dev/null @@ -1,407 +0,0 @@ -'use strict' - -import $ from 'jquery' -import Vue from 'vue' -import _ from 'lodash' -import 'jquery-contextmenu' -import 'jquery-simple-upload' - -module.exports = (alerts, mde, mdeModalOpenState, socket) => { - let vueImage = new Vue({ - el: '#modal-editor-image', - data: { - isLoading: false, - isLoadingText: '', - newFolderName: '', - newFolderShow: false, - newFolderError: false, - fetchFromUrlURL: '', - fetchFromUrlShow: false, - folders: [], - currentFolder: '', - currentImage: '', - currentAlign: 'left', - images: [], - uploadSucceeded: false, - postUploadChecks: 0, - renameImageShow: false, - renameImageId: '', - renameImageFilename: '', - deleteImageShow: false, - deleteImageId: '', - deleteImageFilename: '' - }, - methods: { - - open: () => { - mdeModalOpenState = true - $('#modal-editor-image').addClass('is-active') - vueImage.refreshFolders() - }, - cancel: (ev) => { - mdeModalOpenState = false - $('#modal-editor-image').removeClass('is-active') - }, - - // ------------------------------------------- - // INSERT IMAGE - // ------------------------------------------- - - selectImage: (imageId) => { - vueImage.currentImage = imageId - }, - insertImage: (ev) => { - console.log(mde) - if (mde.codemirror.doc.somethingSelected()) { - mde.codemirror.execCommand('singleSelection') - } - - let selImage = _.find(vueImage.images, ['_id', vueImage.currentImage]) - selImage.normalizedPath = (selImage.folder === 'f:') ? selImage.filename : selImage.folder.slice(2) + '/' + selImage.filename - selImage.titleGuess = _.startCase(selImage.basename) - - let imageText = '![' + selImage.titleGuess + '](/uploads/' + selImage.normalizedPath + ' "' + selImage.titleGuess + '")' - switch (vueImage.currentAlign) { - case 'center': - imageText += '{.align-center}' - break - case 'right': - imageText += '{.align-right}' - break - case 'logo': - imageText += '{.pagelogo}' - break - } - - mde.codemirror.doc.replaceSelection(imageText) - vueImage.cancel() - }, - - // ------------------------------------------- - // NEW FOLDER - // ------------------------------------------- - - newFolder: (ev) => { - vueImage.newFolderName = '' - vueImage.newFolderError = false - vueImage.newFolderShow = true - _.delay(() => { $('#txt-editor-image-newfoldername').focus() }, 400) - }, - newFolderDiscard: (ev) => { - vueImage.newFolderShow = false - }, - newFolderCreate: (ev) => { - let regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$') - vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName)) - - if (_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) { - vueImage.newFolderError = true - return - } - - vueImage.newFolderDiscard() - vueImage.isLoadingText = 'Creating new folder...' - vueImage.isLoading = true - - Vue.nextTick(() => { - socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => { - vueImage.folders = data - vueImage.currentFolder = vueImage.newFolderName - vueImage.images = [] - vueImage.isLoading = false - }) - }) - }, - - // ------------------------------------------- - // FETCH FROM URL - // ------------------------------------------- - - fetchFromUrl: (ev) => { - vueImage.fetchFromUrlURL = '' - vueImage.fetchFromUrlShow = true - _.delay(() => { $('#txt-editor-image-fetchurl').focus() }, 400) - }, - fetchFromUrlDiscard: (ev) => { - vueImage.fetchFromUrlShow = false - }, - fetchFromUrlGo: (ev) => { - vueImage.fetchFromUrlDiscard() - vueImage.isLoadingText = 'Fetching image...' - vueImage.isLoading = true - - Vue.nextTick(() => { - socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => { - if (data.ok) { - vueImage.waitChangeComplete(vueImage.images.length, true) - } else { - vueImage.isLoading = false - alerts.pushError('Upload error', data.msg) - } - }) - }) - }, - - // ------------------------------------------- - // RENAME IMAGE - // ------------------------------------------- - - renameImage: () => { - let c = _.find(vueImage.images, [ '_id', vueImage.renameImageId ]) - vueImage.renameImageFilename = c.basename || '' - vueImage.renameImageShow = true - _.delay(() => { - $('#txt-editor-image-rename').focus() - _.defer(() => { $('#txt-editor-image-rename').select() }) - }, 400) - }, - renameImageDiscard: () => { - vueImage.renameImageShow = false - }, - renameImageGo: () => { - vueImage.renameImageDiscard() - vueImage.isLoadingText = 'Renaming image...' - vueImage.isLoading = true - - Vue.nextTick(() => { - socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => { - if (data.ok) { - vueImage.waitChangeComplete(vueImage.images.length, false) - } else { - vueImage.isLoading = false - alerts.pushError('Rename error', data.msg) - } - }) - }) - }, - - // ------------------------------------------- - // MOVE IMAGE - // ------------------------------------------- - - moveImage: (uid, fld) => { - vueImage.isLoadingText = 'Moving image...' - vueImage.isLoading = true - Vue.nextTick(() => { - socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => { - if (data.ok) { - vueImage.loadImages() - } else { - vueImage.isLoading = false - alerts.pushError('Rename error', data.msg) - } - }) - }) - }, - - // ------------------------------------------- - // DELETE IMAGE - // ------------------------------------------- - - deleteImageWarn: (show) => { - if (show) { - let c = _.find(vueImage.images, [ '_id', vueImage.deleteImageId ]) - vueImage.deleteImageFilename = c.filename || 'this image' - } - vueImage.deleteImageShow = show - }, - deleteImageGo: () => { - vueImage.deleteImageWarn(false) - vueImage.isLoadingText = 'Deleting image...' - vueImage.isLoading = true - Vue.nextTick(() => { - socket.emit('uploadsDeleteFile', { uid: vueImage.deleteImageId }, (data) => { - vueImage.loadImages() - }) - }) - }, - - // ------------------------------------------- - // LOAD FROM REMOTE - // ------------------------------------------- - - selectFolder: (fldName) => { - vueImage.currentFolder = fldName - vueImage.loadImages() - }, - - refreshFolders: () => { - vueImage.isLoadingText = 'Fetching folders list...' - vueImage.isLoading = true - vueImage.currentFolder = '' - vueImage.currentImage = '' - Vue.nextTick(() => { - socket.emit('uploadsGetFolders', { }, (data) => { - vueImage.folders = data - vueImage.loadImages() - }) - }) - }, - - loadImages: (silent) => { - if (!silent) { - vueImage.isLoadingText = 'Fetching images...' - vueImage.isLoading = true - } - return new Promise((resolve, reject) => { - Vue.nextTick(() => { - socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => { - vueImage.images = data - if (!silent) { - vueImage.isLoading = false - } - vueImage.attachContextMenus() - resolve(true) - }) - }) - }) - }, - - waitChangeComplete: (oldAmount, expectChange) => { - expectChange = (_.isBoolean(expectChange)) ? expectChange : true - - vueImage.postUploadChecks++ - vueImage.isLoadingText = 'Processing...' - - Vue.nextTick(() => { - vueImage.loadImages(true).then(() => { - if ((vueImage.images.length !== oldAmount) === expectChange) { - vueImage.postUploadChecks = 0 - vueImage.isLoading = false - } else if (vueImage.postUploadChecks > 5) { - vueImage.postUploadChecks = 0 - vueImage.isLoading = false - alerts.pushError('Unable to fetch updated listing', 'Try again later') - } else { - _.delay(() => { - vueImage.waitChangeComplete(oldAmount, expectChange) - }, 1500) - } - }) - }) - }, - - // ------------------------------------------- - // IMAGE CONTEXT MENU - // ------------------------------------------- - - attachContextMenus: () => { - let moveFolders = _.map(vueImage.folders, (f) => { - return { - name: (f !== '') ? f : '/ (root)', - icon: 'fa-folder', - callback: (key, opt) => { - let moveImageId = _.toString($(opt.$trigger).data('uid')) - let moveImageDestFolder = _.nth(vueImage.folders, key) - vueImage.moveImage(moveImageId, moveImageDestFolder) - } - } - }) - - $.contextMenu('destroy', '.editor-modal-image-choices > figure') - $.contextMenu({ - selector: '.editor-modal-image-choices > figure', - appendTo: '.editor-modal-image-choices', - position: (opt, x, y) => { - $(opt.$trigger).addClass('is-contextopen') - let trigPos = $(opt.$trigger).position() - let trigDim = { w: $(opt.$trigger).width() / 2, h: $(opt.$trigger).height() / 2 } - opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w }) - }, - events: { - hide: (opt) => { - $(opt.$trigger).removeClass('is-contextopen') - } - }, - items: { - rename: { - name: 'Rename', - icon: 'fa-edit', - callback: (key, opt) => { - vueImage.renameImageId = _.toString(opt.$trigger[0].dataset.uid) - vueImage.renameImage() - } - }, - move: { - name: 'Move to...', - icon: 'fa-folder-open-o', - items: moveFolders - }, - delete: { - name: 'Delete', - icon: 'fa-trash', - callback: (key, opt) => { - vueImage.deleteImageId = _.toString(opt.$trigger[0].dataset.uid) - vueImage.deleteImageWarn(true) - } - } - } - }) - } - - } - }) - - $('#btn-editor-image-upload input').on('change', (ev) => { - let curImageAmount = vueImage.images.length - - $(ev.currentTarget).simpleUpload('/uploads/img', { - - name: 'imgfile', - data: { - folder: vueImage.currentFolder - }, - limit: 20, - expect: 'json', - allowedExts: ['jpg', 'jpeg', 'gif', 'png', 'webp'], - allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'], - maxFileSize: 3145728, // max 3 MB - - init: (totalUploads) => { - vueImage.uploadSucceeded = false - vueImage.isLoadingText = 'Preparing to upload...' - vueImage.isLoading = true - }, - - progress: (progress) => { - vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%' - }, - - success: (data) => { - if (data.ok) { - let failedUpls = _.filter(data.results, ['ok', false]) - if (failedUpls.length) { - _.forEach(failedUpls, (u) => { - alerts.pushError('Upload error', u.msg) - }) - if (failedUpls.length < data.results.length) { - alerts.push({ - title: 'Some uploads succeeded', - message: 'Files that are not mentionned in the errors above were uploaded successfully.' - }) - vueImage.uploadSucceeded = true - } - } else { - vueImage.uploadSucceeded = true - } - } else { - alerts.pushError('Upload error', data.msg) - } - }, - - error: (error) => { - alerts.pushError(error.message, this.upload.file.name) - }, - - finish: () => { - if (vueImage.uploadSucceeded) { - vueImage.waitChangeComplete(curImageAmount, true) - } else { - vueImage.isLoading = false - } - } - - }) - }) - return vueImage -} diff --git a/client/js/components/editor.component.js b/client/js/components/editor.component.js index b5db8c71..ba43e928 100644 --- a/client/js/components/editor.component.js +++ b/client/js/components/editor.component.js @@ -127,7 +127,7 @@ export default { { name: 'image', action: (editor) => { - self.$store.dispatch('editorImage/open') + self.$store.dispatch('editorFile/open', { mode: 'image' }) }, className: 'icon-image', title: 'Insert Image' @@ -135,7 +135,7 @@ export default { { name: 'file', action: (editor) => { - self.$store.dispatch('editorFile/open') + self.$store.dispatch('editorFile/open', { mode: 'file' }) }, className: 'icon-paper', title: 'Insert File' diff --git a/client/js/store/modules/editor-file.js b/client/js/store/modules/editor-file.js index ded21980..2f6f2304 100644 --- a/client/js/store/modules/editor-file.js +++ b/client/js/store/modules/editor-file.js @@ -3,15 +3,18 @@ export default { namespaced: true, state: { - shown: false + shown: false, + mode: 'image' }, getters: {}, mutations: { - shownChange: (state, shownState) => { state.shown = shownState } + shownChange: (state, shownState) => { state.shown = shownState }, + modeChange: (state, modeState) => { state.mode = modeState } }, actions: { open({ commit }, opts) { commit('shownChange', true) + commit('modeChange', opts.mode) wikijs.$emit('editorFile/init') }, close({ commit }) { commit('shownChange', false) } diff --git a/server/locales/en/browser.json b/server/locales/en/browser.json index fc00a4b3..f2175928 100644 --- a/server/locales/en/browser.json +++ b/server/locales/en/browser.json @@ -23,11 +23,19 @@ "filerenameaction": "Rename", "filesuccess": "File link has been inserted.", "filetitle": "Insert File", - "fileupload": "Upload File", + "fileupload": "Upload File(s)", "fileuploaderror": "Upload Error: {{err}}", "fileuploadsuccess": "File(s) uploaded successfully.", "folders": "Folders", "foldersloading": "Fetching folders list...", + "imagetitle": "Insert Image", + "imageinsert": "Insert Image", + "imagesuccess": "Image has been inserted.", + "imageupload": "Upload Image(s)", + "imagealignleft": "Left (default)", + "imagealigncenter": "Centered", + "imagealignright": "Right", + "imagealignlogo": "Page Logo", "newfolder": "New Folder", "videoanymp4file": "Any standard MP4 file", "videoinsert": "Insert Video", diff --git a/server/views/modals/editor-file.pug b/server/views/modals/editor-file.pug deleted file mode 100644 index 89bb72b6..00000000 --- a/server/views/modals/editor-file.pug +++ /dev/null @@ -1,80 +0,0 @@ - -.modal#modal-editor-file - .modal-background - .modal-container - .modal-content.is-expanded - - header.is-green - span Insert File - p.modal-notify(v-bind:class='{ "is-active": isLoading }') - span {{ isLoadingText }} - i - .modal-toolbar.is-green - a.button(v-on:click='newFolder') - i.fa.fa-folder - span New Folder - a.button#btn-editor-file-upload - i.fa.fa-upload - span Upload File - label - input(type='file', multiple) - section.is-gapless - .columns.is-stretched - .column.is-one-quarter.modal-sidebar.is-green(style={'max-width':'350px'}) - .model-sidebar-header Folders - ul.model-sidebar-list - li(v-for='fld in folders') - a(v-on:click='selectFolder(fld)', v-bind:class='{ "is-active": currentFolder === fld }') - i.icon-folder2 - span / {{ fld }} - .column.editor-modal-file-choices - 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') - i(class='icon-file') - span: strong {{ fl.filename }} - span {{ fl.mime }} - span {{ fl.filesize | filesize }} - em(v-show='files.length < 1') - i.icon-marquee-minus - | This folder is empty. - footer - a.button.is-grey.is-outlined(v-on:click='cancel') Discard - a.button.is-green(v-on:click='insertFileLink') Insert Link to File - - .modal.is-superimposed(v-bind:class='{ "is-active": newFolderShow }') - .modal-background - .modal-container - .modal-content - header.is-light-blue New Folder - section - label.label Enter the new folder name: - p.control.is-fullwidth - input.input#txt-editor-file-newfoldername(type='text', placeholder='folder name', v-model='newFolderName', v-on:keyup.enter='newFolderCreate', v-on:keyup.esc='newFolderDiscard') - span.help.is-danger(v-show='newFolderError') This folder name is invalid! - footer - a.button.is-grey.is-outlined(v-on:click='newFolderDiscard') Discard - a.button.is-light-blue(v-on:click='newFolderCreate') Create - - .modal.is-superimposed(v-bind:class='{ "is-active": renameFileShow }') - .modal-background - .modal-container - .modal-content - header.is-indigo Rename File - section - label.label Enter the new filename (without the extension) of the file: - p.control.is-fullwidth - input.input#txt-editor-file-rename(type='text', placeholder='filename', v-model='renameFileFilename') - span.help.is-danger.is-hidden This filename is invalid! - footer - a.button.is-grey.is-outlined(v-on:click='renameFileDiscard') Discard - a.button.is-light-blue(v-on:click='renameFileGo') Rename - - .modal.is-superimposed(v-bind:class='{ "is-active": deleteFileShow }') - .modal-background - .modal-container - .modal-content - header.is-red Delete file? - section - span Are you sure you want to delete #[strong {{deleteFileFilename}}]? - footer - a.button.is-grey.is-outlined(v-on:click='deleteFileWarn(false)') Discard - a.button.is-red(v-on:click='deleteFileGo') Delete diff --git a/server/views/modals/editor-image.pug b/server/views/modals/editor-image.pug deleted file mode 100644 index 935e99aa..00000000 --- a/server/views/modals/editor-image.pug +++ /dev/null @@ -1,104 +0,0 @@ - -.modal#modal-editor-image - .modal-background - .modal-container - .modal-content.is-expanded - - header.is-green - span Insert Image - p.modal-notify(v-bind:class='{ "is-active": isLoading }') - span {{ isLoadingText }} - i - .modal-toolbar.is-green - a.button(v-on:click='newFolder') - i.fa.fa-folder - span New Folder - a.button#btn-editor-image-upload - i.fa.fa-upload - span Upload Image - label - input(type='file', multiple) - a.button(v-on:click='fetchFromUrl') - i.fa.fa-download - span Fetch from URL - section.is-gapless - .columns.is-stretched - .column.is-one-quarter.modal-sidebar.is-green(style={'max-width':'350px'}) - .model-sidebar-header Folders - ul.model-sidebar-list - li(v-for='fld in folders') - a(v-on:click='selectFolder(fld)', v-bind:class='{ "is-active": currentFolder === fld }') - i.icon-folder2 - span / {{ fld }} - .model-sidebar-header Alignment - .model-sidebar-content - p.control.is-fullwidth - select(v-model='currentAlign') - option(value='left') Left (default) - option(value='center') Centered - option(value='right') Right - option(value='logo') Page Logo - .column.editor-modal-image-choices - figure(v-for='img in images', v-bind:class='{ "is-active": currentImage === img._id }', v-on:click='selectImage(img._id)', v-bind:data-uid='img._id') - img(v-bind:src='"/uploads/t/" + img._id + ".png"') - span: strong {{ img.basename }} - span {{ img.filesize | filesize }} - em(v-show='images.length < 1') - i.icon-marquee-minus - | This folder is empty. - footer - a.button.is-grey.is-outlined(v-on:click='cancel') Discard - a.button.is-green(v-on:click='insertImage') Insert Image - - .modal.is-superimposed(v-bind:class='{ "is-active": newFolderShow }') - .modal-background - .modal-container - .modal-content - header.is-light-blue New Folder - section - label.label Enter the new folder name: - p.control.is-fullwidth - input.input#txt-editor-image-newfoldername(type='text', placeholder='folder name', v-model='newFolderName', v-on:keyup.enter='newFolderCreate', v-on:keyup.esc='newFolderDiscard') - span.help.is-danger(v-show='newFolderError') This folder name is invalid! - footer - a.button.is-grey.is-outlined(v-on:click='newFolderDiscard') Discard - a.button.is-light-blue(v-on:click='newFolderCreate') Create - - .modal.is-superimposed(v-bind:class='{ "is-active": fetchFromUrlShow }') - .modal-background - .modal-container - .modal-content - header.is-light-blue Fetch Image from URL - section - label.label Enter full URL path to the image: - p.control.is-fullwidth - input.input#txt-editor-image-fetchurl(type='text', placeholder='http://www.example.com/some-image.png', v-model='fetchFromUrlURL') - span.help.is-danger.is-hidden This URL path is invalid! - footer - a.button.is-grey.is-outlined(v-on:click='fetchFromUrlDiscard') Discard - a.button.is-light-blue(v-on:click='fetchFromUrlGo') Fetch - - .modal.is-superimposed(v-bind:class='{ "is-active": renameImageShow }') - .modal-background - .modal-container - .modal-content - header.is-indigo Rename Image - section - label.label Enter the new filename (without the extension) of the image: - p.control.is-fullwidth - input.input#txt-editor-image-rename(type='text', placeholder='filename', v-model='renameImageFilename') - span.help.is-danger.is-hidden This filename is invalid! - footer - a.button.is-grey.is-outlined(v-on:click='renameImageDiscard') Discard - a.button.is-light-blue(v-on:click='renameImageGo') Rename - - .modal.is-superimposed(v-bind:class='{ "is-active": deleteImageShow }') - .modal-background - .modal-container - .modal-content - header.is-red Delete image? - section - span Are you sure you want to delete #[strong {{deleteImageFilename}}]? - footer - a.button.is-grey.is-outlined(v-on:click='deleteImageWarn(false)') Discard - a.button.is-red(v-on:click='deleteImageGo') Delete diff --git a/server/views/pages/create.pug b/server/views/pages/create.pug index a2b75b99..ea5f2bf1 100644 --- a/server/views/pages/create.pug +++ b/server/views/pages/create.pug @@ -18,6 +18,7 @@ block content .editor-area textarea(ref='editorTextArea', v-pre)= pageData.markdown + editor-file editor-video editor-codeblock modal-discard-page(mode='create', current-path=pageData.meta.path)