diff --git a/changelog/unreleased/enhancement-app-provider-create-files b/changelog/unreleased/enhancement-app-provider-create-files new file mode 100644 index 00000000000..a635cde585a --- /dev/null +++ b/changelog/unreleased/enhancement-app-provider-create-files @@ -0,0 +1,6 @@ +Enhancement: File creation via app provider + +For oCIS deployments the integration of the app provider for editing files was enhanced by adding support for the app provider capabilities to create files as well. + +https://github.com/owncloud/web/pull/5890 +https://github.com/owncloud/web/pull/6312 diff --git a/packages/web-app-files/src/components/AppBar/AppBar.vue b/packages/web-app-files/src/components/AppBar/AppBar.vue index ae07be93b14..1e145988b87 100644 --- a/packages/web-app-files/src/components/AppBar/AppBar.vue +++ b/packages/web-app-files/src/components/AppBar/AppBar.vue @@ -102,6 +102,23 @@ + @@ -118,6 +135,7 @@ import { mapActions, mapGetters, mapState, mapMutations } from 'vuex' import pathUtil from 'path' import { useRouter } from 'web-pkg/src/composables' +import get from 'lodash-es/get' import Mixins from '../../mixins' import MixinFileActions, { EDITOR_MODE_CREATE } from '../../mixins/fileActions' @@ -148,7 +166,8 @@ export default { setup() { const router = useRouter() return { - isSpacesLocation: isLocationSpacesActive(router, 'files-spaces-personal-home') + isPersonalLocation: isLocationSpacesActive(router, 'files-spaces-personal-home'), + isPublicLocation: isLocationPublicActive(router, 'files-public-files') } }, data: () => ({ @@ -157,11 +176,24 @@ export default { fileFolderCreationLoading: false }), computed: { - ...mapGetters(['getToken', 'configuration', 'newFileHandlers', 'quota', 'user']), + ...mapGetters('External', ['mimeTypes']), + ...mapGetters([ + 'getToken', + 'capabilities', + 'configuration', + 'newFileHandlers', + 'quota', + 'user' + ]), ...mapGetters('Files', ['files', 'currentFolder', 'selectedFiles', 'publicLinkPassword']), - ...mapState(['route']), ...mapState('Files', ['areHiddenFilesShown']), + mimetypesAllowedForCreation() { + if (!get(this, 'mimeTypes', []).length) { + return [] + } + return this.mimeTypes.filter((mimetype) => mimetype.allow_creation) || [] + }, newButtonTooltip() { if (!this.canUpload) { return this.$gettext('You have no permission to upload!') @@ -201,7 +233,7 @@ export default { } }, canUpload() { - if (this.currentFolder === null) { + if (!this.currentFolder) { return false } return this.currentFolder.canUpload() @@ -218,9 +250,7 @@ export default { }, breadcrumbs() { - const isPublic = isLocationPublicActive(this.$router, 'files-public-files') - const isSpaces = isLocationSpacesActive(this.$router, 'files-spaces-personal-home') - if (!(isPublic || isSpaces)) { + if (!(this.isPublicLocation || this.isPersonalLocation)) { return [] } @@ -244,9 +274,11 @@ export default { }) if (i === rawItems.length - 1) { - isPublic && acc.shift() + this.isPublicLocation && acc.shift() acc.length && - (acc[0].text = isSpaces ? this.$gettext('All files') : this.$gettext('Public link')) + (acc[0].text = this.isPersonalLocation + ? this.$gettext('All files') + : this.$gettext('Public link')) acc.length && delete acc[acc.length - 1].to } else { delete acc[i].onClick @@ -312,7 +344,12 @@ export default { ...mapMutations('Files', ['UPSERT_RESOURCE', 'SET_HIDDEN_FILES_VISIBILITY']), ...mapMutations(['SET_QUOTA']), - showCreateResourceModal(isFolder = true, ext = 'txt', openAction = null) { + showCreateResourceModal( + isFolder = true, + ext = 'txt', + openAction = null, + addAppProviderFile = false + ) { const defaultName = isFolder ? this.$gettext('New folder') : this.$gettext('New file') + '.' + ext @@ -339,7 +376,11 @@ export default { ? this.checkNewFolderName(defaultName) : this.checkNewFileName(defaultName), onCancel: this.hideModal, - onConfirm: isFolder ? this.addNewFolder : this.addNewFile, + onConfirm: isFolder + ? this.addNewFolder + : addAppProviderFile + ? this.addAppProviderFile + : this.addNewFile, onInput: checkInputValue } @@ -357,7 +398,7 @@ export default { const path = pathUtil.join(this.currentPath, folderName) let resource - if (this.isSpacesLocation) { + if (this.isPersonalLocation) { await this.$client.files.createFolder(path) resource = await this.$client.files.fileInfo(path, DavProperties.Default) } else { @@ -373,7 +414,7 @@ export default { this.UPSERT_RESOURCE(resource) this.hideModal() - if (this.isSpacesLocation) { + if (this.isPersonalLocation) { this.loadIndicators({ client: this.$client, currentFolder: this.currentFolder.path @@ -440,7 +481,7 @@ export default { try { const path = pathUtil.join(this.currentPath, fileName) let resource - if (this.isSpacesLocation) { + if (this.isPersonalLocation) { await this.$client.files.putFileContents(path, '') resource = await this.$client.files.fileInfo(path, DavProperties.Default) } else { @@ -466,7 +507,7 @@ export default { this.UPSERT_RESOURCE(resource) this.hideModal() - if (this.isSpacesLocation) { + if (this.isPersonalLocation) { this.loadIndicators({ client: this.$client, currentFolder: this.currentFolder.path @@ -488,7 +529,83 @@ export default { this.fileFolderCreationLoading = false }, + async addAppProviderFile(fileName) { + // FIXME: this belongs in web-app-external, but the app provider handles file creation differently than other editor extensions. Needs more refactoring. + if (fileName === '') { + return + } + try { + const parent = this.currentFolder.fileId + const publicToken = (this.$router.currentRoute.params.item || '').split('/')[0] + + const configUrl = this.configuration.server + const appNewUrl = this.capabilities.files.app_providers[0].new_url.replace(/^\/+/, '') + const url = + configUrl + + appNewUrl + + `?parent_container_id=${parent}&filename=${encodeURIComponent(fileName)}` + + const headers = { + 'X-Requested-With': 'XMLHttpRequest', + ...(this.isPublicLocation && + publicToken && { + 'public-token': publicToken + }), + ...(this.isPublicLocation && + this.publicLinkPassword && { + Authorization: + 'Basic ' + + Buffer.from(['public', this.publicLinkPassword].join(':')).toString('base64') + }), + ...(this.getToken && { + Authorization: 'Bearer ' + this.getToken + }) + } + + const response = await fetch(url, { + method: 'POST', + headers + }) + + if (response.status !== 200) { + throw new Error(`An error has occurred: ${response.status}`) + } + + const path = pathUtil.join(this.currentPath, fileName) + let resource + if (this.isPersonalLocation) { + resource = await this.$client.files.fileInfo(path, DavProperties.Default) + } else { + resource = await this.$client.publicFiles.getFileInfo( + path, + this.publicLinkPassword, + DavProperties.PublicLink + ) + } + resource = buildResource(resource) + this.$_fileActions_triggerDefaultAction(resource) + this.UPSERT_RESOURCE(resource) + this.hideModal() + if (this.isPersonalLocation) { + this.loadIndicators({ + client: this.$client, + currentFolder: this.currentFolder.path + }) + } + this.showMessage({ + title: this.$gettextInterpolate(this.$gettext('"%{fileName}" was created successfully'), { + fileName + }) + }) + } catch (error) { + console.error(error) + this.showMessage({ + title: this.$gettext('Failed to create file'), + status: 'danger' + }) + } + }, checkNewFileName(fileName) { if (fileName === '') { return this.$gettext('File name cannot be empty') @@ -528,7 +645,7 @@ export default { await this.$nextTick() const path = pathUtil.join(this.currentPath, file) - let resource = this.isSpacesLocation + let resource = this.isPersonalLocation ? await this.$client.files.fileInfo(path, DavProperties.Default) : await this.$client.publicFiles.getFileInfo( path, @@ -539,7 +656,7 @@ export default { resource = buildResource(resource) this.UPSERT_RESOURCE(resource) - if (this.isSpacesLocation) { + if (this.isPersonalLocation) { this.loadIndicators({ client: this.$client, currentFolder: this.currentFolder.path,