Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-image media attachments #2378

Merged
merged 25 commits into from
Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3fec1f0
allow media file upload, handle display in ImageView
Jun 8, 2022
4d48e9d
rename most image stuff to file/media/attachment
Jul 14, 2022
0497a9e
use delete icon instead of community trashcan
Aug 23, 2022
4a73e13
improve media file style
Aug 23, 2022
5b09a00
use constants for attachment types
Aug 23, 2022
f5a34ce
rename image controller/service to attachment
Aug 23, 2022
22f70c9
rename most frontend image stuff to attachment (except the Tiptap node)
Aug 23, 2022
c18a030
adjust frontend tests
Aug 24, 2022
4ccc31e
rename 'insert attachment' menu action, wait for metadata before reso…
Aug 26, 2022
bf9728b
use button for attachment deletion, left align media attachments
Aug 26, 2022
fc65299
move attachment resolver axios call to ImageView
Aug 29, 2022
95a4fbb
add tests for attachment metadata url generation
Aug 29, 2022
6f85b62
use computed props to toggle delete icon
Aug 30, 2022
b284ed9
fix image caption text color
Aug 30, 2022
ff9bfe3
avoid mutating image target candidates
Aug 30, 2022
d4ed466
put delete icon next to images
Aug 30, 2022
82b41eb
add border to media attachment
Aug 30, 2022
670f383
avoid image caption offset when delete icon appears
Aug 30, 2022
6f48e34
image caption border
Aug 30, 2022
3ef0743
rename cypress attachment tests, add one for media upload
Aug 30, 2022
5dae528
better border management for image caption
Sep 2, 2022
9764e64
pass upload auth params as GET-like ones to avoid authentication erro…
Sep 6, 2022
4b80cd5
don't even render the image caption if attachment is not an image
Sep 6, 2022
277b8e0
fix tests
Sep 6, 2022
5f0f18a
Compile assets
nextcloud-command Sep 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@

return [
'routes' => [
['name' => 'Image#insertImageFile', 'url' => '/image/filepath', 'verb' => 'POST'],
['name' => 'Image#uploadImage', 'url' => '/image/upload', 'verb' => 'POST'],
['name' => 'Image#getImage', 'url' => '/image', 'verb' => 'GET'],
['name' => 'Attachment#insertAttachmentFile', 'url' => '/attachment/filepath', 'verb' => 'POST'],
['name' => 'Attachment#uploadAttachment', 'url' => '/attachment/upload', 'verb' => 'POST'],
['name' => 'Attachment#getImageFile', 'url' => '/image', 'verb' => 'GET'],
['name' => 'Attachment#getMediaFile', 'url' => '/media', 'verb' => 'GET'],
['name' => 'Attachment#getMediaFilePreview', 'url' => '/mediaPreview', 'verb' => 'GET'],
['name' => 'Attachment#getMediaFileMetadata', 'url' => '/mediaMetadata', 'verb' => 'GET'],

['name' => 'Session#create', 'url' => '/session/create', 'verb' => 'PUT'],
['name' => 'Session#fetch', 'url' => '/session/fetch', 'verb' => 'POST'],
Expand Down
113 changes: 69 additions & 44 deletions cypress/e2e/images.spec.js → cypress/e2e/attachments.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,19 @@ const randUser2 = randHash()
let currentUser = randUser
const attachmentFileNameToId = {}

const ACTION_UPLOAD_LOCAL_FILE = 'insert-image-upload'
const ACTION_INSERT_FROM_FILES = 'insert-image-insert'
// const ACTION_INSERT_FROM_LINK = 3
const ACTION_UPLOAD_LOCAL_FILE = 'insert-attachment-upload'
const ACTION_INSERT_FROM_FILES = 'insert-attachment-insert'

/**
* @param {string} name name of file
* @param {string|null} requestAlias alias name
*/
function attachFile(name, requestAlias = null) {
if (requestAlias) {
cy.intercept({ method: 'POST', url: '**/upload' }).as(requestAlias)
cy.intercept({ method: 'POST', url: '**/text/attachment/upload?**' }).as(requestAlias)
}
return cy.getEditor()
.find('input[type="file"][data-text-el="image-file-input"]')
.find('input[type="file"][data-text-el="attachment-file-input"]')
.attachFile(name)
}

Expand All @@ -56,12 +55,12 @@ function fixedEncodeURIComponent(str) {
}

/**
* Open the image action menu and click one action
* Open the attachment action menu and click one action
*
* @param {string} actionName position of the action to be clicked
*/
const clickOnImageAction = (actionName) => {
cy.getActionEntry('insert-image')
const clickOnAttachmentAction = (actionName) => {
cy.getActionEntry('insert-attachment')
.click()

return cy.get('.v-popper__wrapper .open')
Expand All @@ -70,69 +69,79 @@ const clickOnImageAction = (actionName) => {
}

/**
* Check if an image is visible in the document
* Check if an attachment is visible in the document
*
* @param {number} documentId file ID of the current document
* @param {string} imageName file name to be checked
* @param {number} imageId file id
* @param {number|undefined} index index of image in the document
* @param {string} fileName attachment file name to be checked
* @param {number} fileId attachment file id
* @param {number|undefined} index index of the attachment in the document
* @param {boolean} isImage is the attachment an image or a media file?
*/
const checkImage = (documentId, imageName, imageId, index) => {
const encodedName = fixedEncodeURIComponent(imageName)
const checkAttachment = (documentId, fileName, fileId, index, isImage = true) => {
const encodedName = fixedEncodeURIComponent(fileName)
const src = `.attachments.${documentId}/${encodedName}`

cy.log('Check the image is visible and well formed', documentId, imageName, imageId, index, encodedName)
cy.log('Check the attachment is visible and well formed', documentId, fileName, fileId, index, encodedName)
return new Cypress.Promise((resolve, reject) => {
cy.get(`#editor [data-component="image-view"][data-src="${src}"]`)
.find('.image__view') // wait for load finish
.within(($el) => {
// keep track that we have created this image in the attachment dir
// keep track that we have created this attachment in the attachment dir
if (!attachmentFileNameToId[documentId]) {
attachmentFileNameToId[documentId] = {}
}

attachmentFileNameToId[documentId][imageName] = imageId
attachmentFileNameToId[documentId][fileName] = fileId

if (index > 0) {
expect(imageName).include(`(${index + 1})`)
expect(fileName).include(`(${index + 1})`)
}

const srcPathEnd = isImage ? 'image' : 'mediaPreview'
const srcFileNameParam = isImage ? 'imageFileName' : 'mediaFileName'

cy.wrap($el)
.should('be.visible')
.find('img')
.should('have.attr', 'src')
.should('contain', 'apps/text/image?documentId=' + documentId)
.should('contain', 'imageFileName=' + encodeURIComponent(imageName))

return cy.wrap($el)
.find('.image__caption input')
.should('be.visible')
.should('have.value', imageName)
.should('contain', 'apps/text/' + srcPathEnd + '?documentId=' + documentId)
.should('contain', srcFileNameParam + '=' + encodeURIComponent(fileName))

return isImage
? cy.wrap($el)
.find('.image__caption input')
.should('be.visible')
.should('have.value', fileName)
: cy.wrap($el)
.find('.metadata .name')
.should('be.visible')
.should('have.text', fileName)

})
.then(resolve, reject)
})
}

/**
* Wait for the image insertion request to finish and check if the image is visible
* Wait for the attachment insertion request to finish and check if the attachment is visible
*
* @param {string} requestAlias Alias of the request we are waiting for
* @param {number|undefined} index of image
* @param {number|undefined} index of the attachment
* @param {boolean} isImage is the attachment an image or a media file?
*/
const waitForRequestAndCheckImage = (requestAlias, index) => {
const waitForRequestAndCheckAttachment = (requestAlias, index, isImage = true) => {
return cy.wait('@' + requestAlias)
.then((req) => {
// the name of the created file on NC side is returned in the response
const fileId = req.response.body.id
const fileName = req.response.body.name
const documentId = req.response.body.documentId

return checkImage(documentId, fileName, fileId, index)
return checkAttachment(documentId, fileName, fileId, index, isImage)
})
}

describe('Test all image insertion methods', () => {
describe('Test all attachment insertion methods', () => {
before(() => {
initUserAndFiles(randUser, 'test.md', 'empty.md')

Expand All @@ -154,10 +163,10 @@ describe('Test all image insertion methods', () => {
cy.showHiddenFiles()
})

it('Insert an image from files', () => {
it('Insert an image file from Files', () => {
cy.openFile('test.md')

clickOnImageAction(ACTION_INSERT_FROM_FILES)
clickOnAttachmentAction(ACTION_INSERT_FROM_FILES)
.then(() => {
const requestAlias = 'insertPathRequest'
cy.intercept({ method: 'POST', url: '**/filepath' }).as(requestAlias)
Expand All @@ -167,27 +176,43 @@ describe('Test all image insertion methods', () => {
cy.log('Click OK in the filepicker')
cy.get('.oc-dialog > .oc-dialog-buttonrow button').click()

return waitForRequestAndCheckImage(requestAlias)
return waitForRequestAndCheckAttachment(requestAlias)
})
})

it('Upload a local image', () => {
it('Upload a local image file', () => {
cy.openFile('test.md')
// in this case we almost could just attach the file to the input
// BUT we still need to click on the action because otherwise the command
// is not handled correctly when the upload has been done in <MenuBar>
clickOnImageAction(ACTION_UPLOAD_LOCAL_FILE)
clickOnAttachmentAction(ACTION_UPLOAD_LOCAL_FILE)
.then(() => {
const requestAlias = 'uploadRequest'
cy.log('Upload the file through the input')

attachFile('table.png', requestAlias)

return waitForRequestAndCheckImage(requestAlias)
return waitForRequestAndCheckAttachment(requestAlias)
})
})

it('Upload a local media file', () => {
cy.openFile('test.md')
// in this case we almost could just attach the file to the input
// BUT we still need to click on the action because otherwise the command
// is not handled correctly when the upload has been done in <MenuBar>
clickOnAttachmentAction(ACTION_UPLOAD_LOCAL_FILE)
.then(() => {
const requestAlias = 'uploadMediaRequest'
cy.log('Upload the file through the input')

attachFile('file.txt.gz', requestAlias)

return waitForRequestAndCheckAttachment(requestAlias, undefined, false)
})
})

it('Upload images with the same name', () => {
it('Upload image files with the same name', () => {
// make sure we start from an emtpy file even on retries
const filename = randHash() + '.md'

Expand All @@ -196,14 +221,14 @@ describe('Test all image insertion methods', () => {
cy.openFile(filename)

const assertImage = index => {
return clickOnImageAction(ACTION_UPLOAD_LOCAL_FILE)
return clickOnAttachmentAction(ACTION_UPLOAD_LOCAL_FILE)
.then(() => {
const requestAlias = `uploadRequest${index}`
cy.log('Upload the file through the input', { index })

attachFile('github.png', requestAlias)

return waitForRequestAndCheckImage(requestAlias, index)
return waitForRequestAndCheckAttachment(requestAlias, index)
})
}

Expand All @@ -219,15 +244,15 @@ describe('Test all image insertion methods', () => {
})
})

it('test if image files are in the attachment folder', () => {
// check we stored the image names/ids
it('test if attachment files are in the attachment folder', () => {
// check we stored the attachment names/ids

cy.get('.files-fileList tr[data-file="test.md"]', { timeout: 10000 })
.should('have.attr', 'data-id')
.then((documentId) => {
const files = attachmentFileNameToId[documentId]

cy.expect(Object.keys(files)).to.have.lengthOf(2)
cy.expect(Object.keys(files)).to.have.lengthOf(3)
cy.openFolder('.attachments.' + documentId)
cy.screenshot()
for (const name in files) {
Expand Down Expand Up @@ -277,7 +302,7 @@ describe('Test all image insertion methods', () => {
.should('exist')
.should('have.attr', 'data-id')
// these are new copied attachment files
// so they should not have the same IDs than the ones created when uploading the images
// so they should not have the same IDs than the ones created when uploading the files
.should('not.eq', String(files[name]))
}
})
Expand Down Expand Up @@ -339,7 +364,7 @@ describe('Test all image insertion methods', () => {
.should('exist')
.should('have.attr', 'data-id')
// these are new copied attachment files
// so they should not have the same IDs than the ones created when uploading the images
// so they should not have the same IDs than the ones created when uploading the files
.should('not.eq', String(files[name]))
}
})
Expand Down
Binary file added cypress/fixtures/file.txt.gz
Binary file not shown.
6 changes: 3 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
OC = { webroot: '/OC_WEBROOT' }
</script>
<script type="module">
import { RichTextReader, ImageResolver, IMAGE_RESOLVER } from './src/package.js'
import { RichTextReader, AttachmentResolver, ATTACHMENT_RESOLVER } from './src/package.js'
import Vue from 'vue'
import content from './src/tests/fixtures/basic.md?raw'
const app = new Vue({
Expand All @@ -25,7 +25,7 @@
provide() {
const val = {}
Object.defineProperties(val, {
[IMAGE_RESOLVER]: { get: () => this.$imageResolver },
[ATTACHMENT_RESOLVER]: { get: () => this.$attachmentResolver },
})
return val
},
Expand All @@ -47,7 +47,7 @@
}),
]),
created() {
this.$imageResolver = new ImageResolver({
this.$attachmentResolver = new AttachmentResolver({
currentDirectory: '/dir/',
shareToken: 'SHARE_TOKEN',
user: { uid: 'USER_UID' },
Expand Down
4 changes: 2 additions & 2 deletions js/editor-rich.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/editor-rich.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions js/editor.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/editor.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions js/files-modal.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading