From c3a1b17381037bacebc74224100fbf1760a2ff5f Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Tue, 29 Mar 2022 11:29:45 +0200 Subject: [PATCH] add image upload via drag'n'drop remove image upload by link move 'image insertion from files' from MenuBar to EditorWrapper allow uploading multiple files Signed-off-by: Julien Veyssier --- cypress/integration/images.spec.js | 19 +---- src/components/EditorWrapper.vue | 75 +++++++++++++++++++- src/components/MenuBar.vue | 107 +++-------------------------- src/nodes/ImageView.vue | 1 + 4 files changed, 84 insertions(+), 118 deletions(-) diff --git a/cypress/integration/images.spec.js b/cypress/integration/images.spec.js index a63f7dbba83..67952fe0709 100644 --- a/cypress/integration/images.spec.js +++ b/cypress/integration/images.spec.js @@ -139,23 +139,6 @@ describe('Test all image insertion methods', () => { }) }) - it('Insert an image from a link', () => { - cy.openFile('test.md') - clickOnImageAction(ACTION_INSERT_FROM_LINK, (popoverId) => { - const requestAlias = 'insertLinkRequest' - cy.intercept({ method: 'POST', url: '**/link' }).as(requestAlias) - - cy.log('Type and validate') - cy.get('div#' + popoverId + ' li:nth-child(3) input[type=text]') - .type('https://nextcloud.com/wp-content/themes/next/assets/img/headers/engineering-small.jpg', { waitForAnimations: true }) - .type('{enter}', { waitForAnimations: true }) - // Clicking on the validation button is an alternative to typing {enter} - // cy.get('div#' + popoverId + ' li:nth-child(3) form > label').click() - - waitForRequestAndCheckImage(requestAlias) - }) - }) - it('Upload a local image', () => { cy.openFile('test.md') // in this case we almost could just attach the file to the input @@ -174,7 +157,7 @@ describe('Test all image insertion methods', () => { it('test if image files are in the attachment folder', () => { // check we stored the image names/ids - cy.expect(Object.keys(attachmentFileNameToId)).to.have.lengthOf(3) + cy.expect(Object.keys(attachmentFileNameToId)).to.have.lengthOf(2) cy.get(`#fileList tr[data-file="test.md"]`, { timeout: 10000 }) .should('have.attr', 'data-id') diff --git a/src/components/EditorWrapper.vue b/src/components/EditorWrapper.vue index e72f769d812..e589b221954 100644 --- a/src/components/EditorWrapper.vue +++ b/src/components/EditorWrapper.vue @@ -34,7 +34,12 @@

-
+
+ :uploading-image="nbUploadingImages > 0" + @show-help="showHelp" + @image-insert="insertImagePath" + @image-upload="uploadImageFiles">
{{ lastSavedStatus }} @@ -81,6 +89,7 @@ import Vue from 'vue' import escapeHtml from 'escape-html' import moment from '@nextcloud/moment' +import { showError } from '@nextcloud/dialogs' import { SyncService, ERROR_TYPE, IDLE_TIMEOUT } from './../services/SyncService' import { endpointUrl, getRandomGuestName } from './../helpers' @@ -99,6 +108,18 @@ import { Step } from 'prosemirror-transform' const EDITOR_PUSH_DEBOUNCE = 200 +const imageMimes = [ + 'image/png', + 'image/jpeg', + 'image/jpg', + 'image/gif', + 'image/x-xbitmap', + 'image/x-ms-bmp', + 'image/bmp', + 'image/svg+xml', + 'image/webp', +] + export default { name: 'EditorWrapper', components: { @@ -179,6 +200,8 @@ export default { readOnly: true, forceRecreate: false, menubarLoaded: false, + nbUploadingImages: 0, + draggedOver: false, saveStatusPolling: null, displayHelp: false, @@ -543,6 +566,51 @@ export default { hideHelp() { this.displayHelp = false }, + onEditorDrop(e) { + this.uploadImageFiles(e.dataTransfer.files) + this.draggedOver = false + }, + uploadImageFiles(files) { + if (files) { + files.forEach((file) => { + this.uploadImageFile(file) + }) + } + }, + uploadImageFile(file) { + if (!imageMimes.includes(file.type)) { + showError(t('text', 'Image file format not supported')) + return + } + + this.nbUploadingImages++ + this.syncService.uploadImage(file).then((response) => { + this.insertAttachmentImage(response.data?.name, response.data?.id) + }).catch((error) => { + console.error(error) + showError(error?.response?.data?.error) + }).then(() => { + this.nbUploadingImages-- + }) + }, + insertImagePath(imagePath) { + this.nbUploadingImages++ + this.syncService.insertImageFile(imagePath).then((response) => { + this.insertAttachmentImage(response.data?.name, response.data?.id) + }).catch((error) => { + console.error(error) + showError(error?.response?.data?.error) + }).then(() => { + this.nbUploadingImages-- + }) + }, + insertAttachmentImage(name, fileId) { + const src = 'text://image?imageFileName=' + encodeURIComponent(name) + // simply get rid of brackets to make sure link text is valid + // as it does not need to be unique and matching the real file name + const alt = name.replaceAll(/[[\]]/g, '') + this.tiptap.chain().setImage({ src, alt }).focus().run() + }, }, } @@ -778,6 +846,9 @@ export default { // relative position for the alignment of the menububble #editor { + &.draggedOver { + background-color: var(--color-primary-light); + } .content-wrapper { position: relative; } diff --git a/src/components/MenuBar.vue b/src/components/MenuBar.vue index 7c27439daa4..60af3cc97a5 100644 --- a/src/components/MenuBar.vue +++ b/src/components/MenuBar.vue @@ -27,6 +27,7 @@ accept="image/*" aria-hidden="true" class="hidden-visually" + :multiple="true" @change="onImageUploadFilePicked">