Merge pull request #5186 from nextcloud/fix/moved-to-root

fix: document created in wrong location on public share
This commit is contained in:
Elizabeth Danzberger 2025-11-19 17:47:24 -05:00 committed by GitHub
commit bd709674bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 74 additions and 161 deletions

View file

@ -2,8 +2,8 @@
* SPDX-FileCopyrightText: 2023 Julius Härtl <jus@bitgrid.net>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
// FIXME: Re-renable once 28 has file creation again working
describe.skip('Create new office files', function() {
describe('New file menu', function() {
let randUser
before(function() {
@ -18,51 +18,34 @@ describe.skip('Create new office files', function() {
})
it('Shows create file entries', function() {
cy.get('.files-controls .button.new')
cy.get('form[data-cy-upload-picker=""]')
.should('be.visible')
.click()
cy.get('.newFileMenu', { timeout: 10000 })
cy.get('button[role="menuitem"]')
.contains('New document')
.should('be.visible')
.contains('.menuitem', 'New document')
.should('be.visible')
.find('.icon')
.should('have.css', 'background-image')
cy.get('.files-controls .button.new')
cy.get('form[data-cy-upload-picker=""]')
.click()
cy.get('.newFileMenu', { timeout: 10000 })
cy.get('li[data-cy-upload-picker-menu-entry="upload-file"]')
.should('not.be.visible')
})
const newFileTypeLabels = [
'document', 'spreadsheet', 'presentation', 'diagram',
]
newFileTypeLabels.forEach((filetype) => {
it('Create empty ' + filetype + ' file', function() {
cy.get('.files-controls .button.new')
.should('be.visible')
.click()
describe('Creates a new file', function() {
const newFileTypeLabels = [
'document', 'spreadsheet', 'presentation', 'diagram',
]
newFileTypeLabels.forEach((filetype) => {
it('Create empty ' + filetype + ' file', function() {
cy.newFileFromMenu(filetype, 'MyNewFile')
cy.waitForViewer()
cy.waitForCollabora()
cy.get('.newFileMenu', { timeout: 10000 })
.should('be.visible')
.contains('.menuitem', 'New ' + filetype)
.as('menuitem')
.should('be.visible')
.click()
cy.screenshot('new-file-' + filetype)
cy.get('@menuitem').find('.filenameform input[type=text]').type('MyNewFile')
cy.get('@menuitem').find('.filenameform .icon-confirm').click()
cy.waitForViewer()
cy.waitForCollabora()
cy.screenshot('new-file-' + filetype)
cy.get('@loleafletframe').within(() => {
cy.get('#closebutton').click()
cy.waitForViewerClose()
cy.closeDocument()
})
})
})

View file

@ -8,7 +8,7 @@ import { randHash } from '../utils/index.js'
const shareOwner = new User(randHash(), randHash())
const otherUser = new User(randHash(), randHash())
describe.skip('Public sharing of office documents', () => {
describe('Public sharing of office documents', () => {
before(function() {
cy.nextcloudTestingAppConfigSet('richdocuments', 'doc_format', '')
cy.createUser(shareOwner)
@ -105,6 +105,37 @@ describe.skip('Public sharing of office documents', () => {
})
})
})
describe('New file', () => {
before(() => {
cy.createFolder(shareOwner, '/Shared-Folder')
cy.createFolder(shareOwner, '/Shared-Folder/Subfolder')
})
it('Creates a new file in a public share as a guest', () => {
cy.shareLink(shareOwner, '/Shared-Folder', { permissions: 13 }).then((token) => {
cy.logout()
cy.visit(`/s/${token}`, {
onBeforeLoad(win) {
cy.spy(win, 'postMessage').as('postMessage')
},
})
cy.get('tr[data-cy-files-list-row-name="Subfolder"]')
.should('be.visible')
.click()
cy.newFileFromMenu('document', 'MyNewFile')
waitForCollabora()
// Make sure the document is still in the correct subfolder
cy.reload()
cy.get('tr[data-cy-files-list-row-name="MyNewFile.odt"]')
.should('be.visible')
})
})
})
})
})

View file

@ -454,3 +454,18 @@ Cypress.Commands.add('makeTalkRoomPublic', (user, token, password = '') => {
})
})
Cypress.Commands.add('newFileFromMenu', (fileType = 'document', fileName = 'MyNewFile') => {
cy.get('div[data-cy-files-content-breadcrumbs=""]')
.find('form[data-cy-upload-picker=""]')
.should('be.visible')
.click()
cy.get('button[role="menuitem"]')
.contains('New ' + fileType)
.should('be.visible')
.click()
cy.get('input[data-cy-files-new-node-dialog-input=""]')
.should('be.visible')
.type(fileName + '{enter}')
})

View file

@ -34,7 +34,13 @@ class RegisterTemplateFileCreatorListener implements IEventListener {
return;
}
if (!$this->permissionManager->isEnabledForUser() || !$this->permissionManager->userCanEdit() || empty($this->capabilitiesService->getCapabilities())) {
if (empty($this->capabilitiesService->getCapabilities())) {
return;
}
$user = $this->permissionManager->loggedInUser();
$userCanCreate = $this->permissionManager->isEnabledForUser($user) && $this->permissionManager->userCanEdit($user);
if ($user && !$userCanCreate) {
return;
}

View file

@ -10,15 +10,12 @@ import {
isDownloadHidden,
} from './helpers/index.js'
import { getCapabilities } from './services/capabilities.ts'
import { registerNewFileMenuEntries } from './view/NewFileMenu.js'
document.addEventListener('DOMContentLoaded', () => {
if (!isPublicShare() || !OCA.Viewer) {
return
}
registerNewFileMenuEntries()
const isEnabledFilesPdfViewer = getCapabilities().mimetypesNoDefaultOpen.includes('application/pdf')
if ((isDownloadHidden() || !isEnabledFilesPdfViewer) && isPdf()) {

View file

@ -4,22 +4,7 @@
*/
import axios from '@nextcloud/axios'
import { generateOcsUrl, generateFilePath } from '@nextcloud/router'
import { getSharingToken } from '@nextcloud/sharing/public'
export const createEmptyFile = async (context, mimeType, fileName, templateId = null) => {
const shareToken = getSharingToken()
const response = await axios.post(generateOcsUrl('apps/richdocuments/api/v1/file', 2), {
mimeType,
fileName,
directoryPath: context.dirname,
shareToken,
templateId,
})
return response.data
}
import { generateFilePath } from '@nextcloud/router'
export const savePersonalSetting = (data) => {
return axios.post(generateFilePath('richdocuments', 'ajax', 'personal.php'), data)

View file

@ -1,105 +0,0 @@
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import axios from '@nextcloud/axios'
import { emit } from '@nextcloud/event-bus'
import Types from '../helpers/types.js'
import { createEmptyFile } from '../services/api.js'
import { generateOcsUrl } from '@nextcloud/router'
import { showError, spawnDialog } from '@nextcloud/dialogs'
import { getCapabilities } from '../services/capabilities.ts'
import { File, addNewFileMenuEntry, isFilenameValid, getUniqueName } from '@nextcloud/files'
import TemplatePicker from '../components/Modal/TemplatePicker.vue'
const createFileTypeEntry = (type, displayName, iconClass, index) => ({
id: 'add-' + type.extension,
displayName: t('richdocuments', displayName),
iconClass,
order: 10 + index,
async handler(context, content) {
const filename = t('richdocuments', displayName) + '.' + type.extension
if (getCapabilities().templates) {
await openTemplatePicker(context, type.name, type.mime, filename, content)
} else {
await createDocument(context, type.mime, filename, null, content)
}
},
})
export const registerNewFileMenuEntries = () => {
const fileTypes = [
{ type: Types.getFileType('document'), displayName: 'New document', iconClass: 'icon-filetype-document' },
{ type: Types.getFileType('spreadsheet'), displayName: 'New spreadsheet', iconClass: 'icon-filetype-spreadsheet' },
{ type: Types.getFileType('presentation'), displayName: 'New presentation', iconClass: 'icon-filetype-presentation' },
{ type: Types.getFileType('drawing'), displayName: 'New drawing', iconClass: 'icon-filetype-draw' },
]
fileTypes.forEach(({ type, displayName, iconClass }, index) => {
addNewFileMenuEntry(createFileTypeEntry(type, displayName, iconClass, index))
})
}
const createDocument = async (context, mimetype, filename, templateId = null, content = []) => {
if (!isFilenameValid(filename)) {
return
}
filename = getUniqueName(
filename,
content.map((n) => n.basename),
)
try {
const response = await createEmptyFile(context, mimetype, filename, templateId)
const { data } = response
const file = new File({
source: context.source + '/' + filename,
basename: filename,
id: data.id,
mtime: new Date(data.mtime),
mime: data.mimetype,
owner: null,
permissions: data.permissions,
root: context?.root,
})
emit('files:node:created', file)
if (response && response.status === 'success') {
OCA.Viewer.open({ path: context.dirname + '/' + filename })
} else {
showError(t('core', 'Could not create file') + ': ' + response.data.message)
}
} catch (error) {
console.error(error)
showError(t('core', 'Could not create file'))
}
}
const openTemplatePicker = async (context, type, mimetype, filename, content = []) => {
try {
const { data: response } = await axios.get(generateOcsUrl(`apps/richdocuments/api/v1/templates/${type}`, 2))
const templates = response.ocs.data
if (templates.length === 1) {
await createDocument(context, mimetype, filename, templates[0].id, content)
return
}
await spawnDialog(TemplatePicker, {
templates,
suggestedFilename: filename,
content,
initialTemplateId: templates[0]?.id,
}, (templateId, filename) => {
if (templateId) {
createDocument(context, mimetype, filename, templateId, content)
}
})
} catch (error) {
console.error(error)
showError(t('core', 'Could not load templates'))
}
}

View file

@ -114,6 +114,7 @@ class RegisterTemplateFileCreatorListenerTest extends TestCase {
public function testHandleDoesNotRegisterIfUserCannotEdit() {
$event = $this->createMock(RegisterTemplateCreatorEvent::class);
$event->method('getTemplateManager')->willReturn($this->templateManager);
$this->permissionManager->method('loggedInUser')->willReturn('user');
$this->permissionManager->method('isEnabledForUser')->willReturn(true);
$this->permissionManager->method('userCanEdit')->willReturn(false);
$this->capabilitiesService->method('getCapabilities')->willReturn(['something']);