From f7320c0b529139696d165f8df69a6d0cb267c43f Mon Sep 17 00:00:00 2001 From: Andrew Backhouse Date: Fri, 5 Jun 2026 16:58:52 +0200 Subject: [PATCH] fix(saveas): allow 2nd (and 3rd etc) saveas to work via doc reinit Signed-off-by: Andrew Backhouse --- src/mixins/saveAs.js | 2 +- src/view/FilesAppIntegration.js | 6 ++-- src/view/Office.vue | 61 ++++++++++++++++++++++++++------- 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/mixins/saveAs.js b/src/mixins/saveAs.js index edb530c6f8..74f04c29e3 100644 --- a/src/mixins/saveAs.js +++ b/src/mixins/saveAs.js @@ -23,7 +23,7 @@ export default { spawnDialog( SaveAs, { - path: this.filename, + path: this.activeFilename, format, description: t('richdocuments', 'Save a copy of the file under a new name and continue editing the new file'), }, diff --git a/src/view/FilesAppIntegration.js b/src/view/FilesAppIntegration.js index bc7bce5764..781e3b3685 100644 --- a/src/view/FilesAppIntegration.js +++ b/src/view/FilesAppIntegration.js @@ -568,11 +568,13 @@ export default { if (nodes[0]) { console.debug('[FilesAppIntegration] Emitting files:node:created for', basename) emit('files:node:created', nodes[0]) - } else { - console.warn('[FilesAppIntegration] New file not found:', basename) + return nodes[0] } + console.warn('[FilesAppIntegration] New file not found:', basename) + return null } catch (e) { console.error('Failed to fetch new file metadata from webdav', e) + return null } }, diff --git a/src/view/Office.vue b/src/view/Office.vue index 563c1a4a25..14976cb6ed 100644 --- a/src/view/Office.vue +++ b/src/view/Office.vue @@ -200,6 +200,12 @@ export default { // Track the last requested save-as filename for export operations lastSaveAsFilename: null, + // Active document identity. The filename/fileid props only carry the + // initially opened document; these are updated when a save-as moves + // the editor session to a new file. + activeFileid: this.fileid, + activeFilename: this.filename, + // Store original favicon for restoration originalFavicon: null, @@ -240,7 +246,7 @@ export default { return this.loadingMsg } - return t('richdocuments', 'Loading {filename} …', { filename: basename(this.filename) }, 1, { escape: false }) + return t('richdocuments', 'Loading {filename} …', { filename: basename(this.activeFilename) }, 1, { escape: false }) }, debug() { return !!window.TESTING @@ -284,14 +290,14 @@ export default { return } - if (this.fileid) { + if (this.activeFileid) { const fileList = OCA?.Files?.App?.getCurrentFileList?.() FilesAppIntegration.init({ - fileName: basename(this.filename), - fileId: this.fileid, - filePath: dirname(this.filename), + fileName: basename(this.activeFilename), + fileId: this.activeFileid, + filePath: dirname(this.activeFilename), fileList, - fileModel: fileList?.getModelForFile(basename(this.filename)), + fileModel: fileList?.getModelForFile(basename(this.activeFilename)), sendPostMessage: (msgId, values) => { this.postMessage.sendWOPIPostMessage(FRAME_DOCUMENT, msgId, values) }, @@ -322,8 +328,8 @@ export default { }, methods: { async load() { - const fileid = this.fileid ?? basename(dirname(this.source)) - const version = this.fileid ? '0' : basename(this.source) + const fileid = this.activeFileid ?? basename(dirname(this.source)) + const version = this.activeFileid ? '0' : basename(this.source) enableScrollLock() @@ -390,6 +396,32 @@ export default { this.load() this.$refs.documentFrame.contentWindow.location.replace(this.iframeSrc) }, + async switchToSavedAsFile(newBasename) { + const node = await FilesAppIntegration.createNodeForNewFile(newBasename) + if (!node) { + // New file could not be resolved, avoid reloading into a bad state + return + } + this.activeFilename = node.path + this.activeFileid = node.fileid + + // FilesAppIntegration keeps its own copy of the file identity + const fileList = OCA?.Files?.App?.getCurrentFileList?.() + FilesAppIntegration.init({ + fileName: node.basename, + fileId: node.fileid, + filePath: node.dirname, + fileList, + fileModel: fileList?.getModelForFile(node.basename), + sendPostMessage: (msgId, values) => { + this.postMessage.sendWOPIPostMessage(FRAME_DOCUMENT, msgId, values) + }, + }) + FilesAppIntegration.changeFilesRoute(node.fileid) + + this.loading = LOADING_STATE.LOADING + await this.load() + }, postMessageHandler({ parsed }) { const { msgId, args, deprecated } = parsed console.debug('[viewer] Received post message', msgId, args, deprecated) @@ -459,16 +491,21 @@ export default { if (!newFileName && args.result === 'exportas' && this.lastSaveAsFilename) { newFileName = this.lastSaveAsFilename } + this.lastSaveAsFilename = null - if (newFileName && newFileName !== this.filename) { - // When exporting (e.g., DOCX -> PDF), a new file is created - // Fetch its metadata and emit files:node:created to show it in the files list + if (newFileName && args.result === 'exportas') { + // Exporting (e.g. DOCX -> PDF) creates a new file but the + // editor keeps editing the original, so only surface the + // new file in the files list. FilesAppIntegration.createNodeForNewFile(newFileName) + } else if (newFileName && newFileName !== basename(this.activeFilename)) { + // A real save-as: Collabora has switched to a new file, so + // the editor session has to be re-initialised for it. + this.switchToSavedAsFile(newFileName) } else { // When saving the current file, update its modification time FilesAppIntegration.updateFileInfo(undefined, Date.now()) } - this.lastSaveAsFilename = null } break case 'UI_InsertGraphic':