aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src/store/renaming.ts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/src/store/renaming.ts')
-rw-r--r--apps/files/src/store/renaming.ts317
1 files changed, 153 insertions, 164 deletions
diff --git a/apps/files/src/store/renaming.ts b/apps/files/src/store/renaming.ts
index 2ec73837f82..fc61be3bd3b 100644
--- a/apps/files/src/store/renaming.ts
+++ b/apps/files/src/store/renaming.ts
@@ -3,184 +3,173 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Node } from '@nextcloud/files'
-import type { RenamingStore } from '../types'
import axios, { isAxiosError } from '@nextcloud/axios'
import { emit, subscribe } from '@nextcloud/event-bus'
-import { NodeStatus } from '@nextcloud/files'
-import { DialogBuilder } from '@nextcloud/dialogs'
+import { FileType, NodeStatus } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
+import { spawnDialog } from '@nextcloud/vue/functions/dialog'
import { basename, dirname, extname } from 'path'
import { defineStore } from 'pinia'
import logger from '../logger'
-import Vue from 'vue'
-import IconCancel from '@mdi/svg/svg/cancel.svg?raw'
-import IconCheck from '@mdi/svg/svg/check.svg?raw'
-
-let isDialogVisible = false
-
-const showWarningDialog = (oldExtension: string, newExtension: string): Promise<boolean> => {
- if (isDialogVisible) {
- return Promise.resolve(false)
- }
-
- isDialogVisible = true
-
- let message
-
- if (!oldExtension && newExtension) {
- message = t(
- 'files',
- 'Adding the file extension "{new}" may render the file unreadable.',
- { new: newExtension },
- )
- } else if (!newExtension) {
- message = t(
- 'files',
- 'Removing the file extension "{old}" may render the file unreadable.',
- { old: oldExtension },
- )
- } else {
- message = t(
- 'files',
- 'Changing the file extension from "{old}" to "{new}" may render the file unreadable.',
- { old: oldExtension, new: newExtension },
- )
- }
-
- return new Promise((resolve) => {
- const dialog = new DialogBuilder()
- .setName(t('files', 'Change file extension'))
- .setText(message)
- .setButtons([
- {
- label: t('files', 'Keep {oldextension}', { oldextension: oldExtension }),
- icon: IconCancel,
- type: 'secondary',
- callback: () => {
- isDialogVisible = false
- resolve(false)
- },
+import Vue, { defineAsyncComponent, ref } from 'vue'
+import { useUserConfigStore } from './userconfig'
+import { fetchNode } from '../services/WebdavClient'
+
+export const useRenamingStore = defineStore('renaming', () => {
+ /**
+ * The currently renamed node
+ */
+ const renamingNode = ref<Node>()
+ /**
+ * The new name of the currently renamed node
+ */
+ const newNodeName = ref('')
+
+ /**
+ * Internal flag to only allow calling `rename` once.
+ */
+ const isRenaming = ref(false)
+
+ /**
+ * Execute the renaming.
+ * This will rename the node set as `renamingNode` to the configured new name `newName`.
+ *
+ * @return true if success, false if skipped (e.g. new and old name are the same)
+ * @throws Error if renaming fails, details are set in the error message
+ */
+ async function rename(): Promise<boolean> {
+ if (renamingNode.value === undefined) {
+ throw new Error('No node is currently being renamed')
+ }
+
+ // Only rename once so we use this as some kind of mutex
+ if (isRenaming.value) {
+ return false
+ }
+ isRenaming.value = true
+
+ let node = renamingNode.value
+ Vue.set(node, 'status', NodeStatus.LOADING)
+
+ const userConfig = useUserConfigStore()
+
+ let newName = newNodeName.value.trim()
+ const oldName = node.basename
+ const oldExtension = extname(oldName)
+ const newExtension = extname(newName)
+ // Check for extension change for files
+ if (node.type === FileType.File
+ && oldExtension !== newExtension
+ && userConfig.userConfig.show_dialog_file_extension
+ && !(await showFileExtensionDialog(oldExtension, newExtension))
+ ) {
+ // user selected to use the old extension
+ newName = basename(newName, newExtension) + oldExtension
+ }
+
+ const oldEncodedSource = node.encodedSource
+ try {
+ if (oldName === newName) {
+ return false
+ }
+
+ // rename the node
+ node.rename(newName)
+ logger.debug('Moving file to', { destination: node.encodedSource, oldEncodedSource })
+ // create MOVE request
+ await axios({
+ method: 'MOVE',
+ url: oldEncodedSource,
+ headers: {
+ Destination: node.encodedSource,
+ Overwrite: 'F',
},
- {
- label: newExtension.length ? t('files', 'Use {newextension}', { newextension: newExtension }) : t('files', 'Remove extension'),
- icon: IconCheck,
- type: 'primary',
- callback: () => {
- isDialogVisible = false
- resolve(true)
- },
- },
- ])
- .build()
-
- dialog.show().then(() => {
- dialog.hide()
- })
- })
-}
-
-export const useRenamingStore = function(...args) {
- const store = defineStore('renaming', {
- state: () => ({
- renamingNode: undefined,
- newName: '',
- } as RenamingStore),
-
- actions: {
- /**
- * Execute the renaming.
- * This will rename the node set as `renamingNode` to the configured new name `newName`.
- * @return true if success, false if skipped (e.g. new and old name are the same)
- * @throws Error if renaming fails, details are set in the error message
- */
- async rename(): Promise<boolean> {
- if (this.renamingNode === undefined) {
- throw new Error('No node is currently being renamed')
- }
-
- const newName = this.newName.trim?.() || ''
- const oldName = this.renamingNode.basename
- const oldEncodedSource = this.renamingNode.encodedSource
-
- // Check for extension change
- const oldExtension = extname(oldName)
- const newExtension = extname(newName)
- if (oldExtension !== newExtension) {
- const proceed = await showWarningDialog(oldExtension, newExtension)
- if (!proceed) {
- return false
- }
+ })
+
+ // Update mime type if extension changed
+ // as other related informations might have changed
+ // on the backend but it is really hard to know on the front
+ if (oldExtension !== newExtension) {
+ node = await fetchNode(node.path)
+ }
+
+ // Success 🎉
+ emit('files:node:updated', node)
+ emit('files:node:renamed', node)
+ emit('files:node:moved', {
+ node,
+ oldSource: `${dirname(node.source)}/${oldName}`,
+ })
+
+ // Reset the state not changed
+ if (renamingNode.value === node) {
+ $reset()
+ }
+
+ return true
+ } catch (error) {
+ logger.error('Error while renaming file', { error })
+ // Rename back as it failed
+ node.rename(oldName)
+ if (isAxiosError(error)) {
+ // TODO: 409 means current folder does not exist, redirect ?
+ if (error?.response?.status === 404) {
+ throw new Error(t('files', 'Could not rename "{oldName}", it does not exist any more', { oldName }))
+ } else if (error?.response?.status === 412) {
+ throw new Error(t(
+ 'files',
+ 'The name "{newName}" is already used in the folder "{dir}". Please choose a different name.',
+ {
+ newName,
+ dir: basename(renamingNode.value!.dirname),
+ },
+ ))
}
+ }
+ // Unknown error
+ throw new Error(t('files', 'Could not rename "{oldName}"', { oldName }))
+ } finally {
+ Vue.set(node, 'status', undefined)
+ isRenaming.value = false
+ }
+ }
- if (oldName === newName) {
- return false
- }
+ /**
+ * Reset the store state
+ */
+ function $reset(): void {
+ newNodeName.value = ''
+ renamingNode.value = undefined
+ }
- const node = this.renamingNode
- Vue.set(node, 'status', NodeStatus.LOADING)
-
- try {
- // rename the node
- this.renamingNode.rename(newName)
- logger.debug('Moving file to', { destination: this.renamingNode.encodedSource, oldEncodedSource })
- // create MOVE request
- await axios({
- method: 'MOVE',
- url: oldEncodedSource,
- headers: {
- Destination: this.renamingNode.encodedSource,
- Overwrite: 'F',
- },
- })
-
- // Success 🎉
- emit('files:node:updated', this.renamingNode as Node)
- emit('files:node:renamed', this.renamingNode as Node)
- emit('files:node:moved', {
- node: this.renamingNode as Node,
- oldSource: `${dirname(this.renamingNode.source)}/${oldName}`,
- })
- this.$reset()
- return true
- } catch (error) {
- logger.error('Error while renaming file', { error })
- // Rename back as it failed
- this.renamingNode.rename(oldName)
- if (isAxiosError(error)) {
- // TODO: 409 means current folder does not exist, redirect ?
- if (error?.response?.status === 404) {
- throw new Error(t('files', 'Could not rename "{oldName}", it does not exist any more', { oldName }))
- } else if (error?.response?.status === 412) {
- throw new Error(t(
- 'files',
- 'The name "{newName}" is already used in the folder "{dir}". Please choose a different name.',
- {
- newName,
- dir: basename(this.renamingNode.dirname),
- },
- ))
- }
- }
- // Unknown error
- throw new Error(t('files', 'Could not rename "{oldName}"', { oldName }))
- } finally {
- Vue.set(node, 'status', undefined)
- }
- },
- },
+ // Make sure we only register the listeners once
+ subscribe('files:node:rename', (node: Node) => {
+ renamingNode.value = node
+ newNodeName.value = node.basename
})
- const renamingStore = store(...args)
+ return {
+ $reset,
- // Make sure we only register the listeners once
- if (!renamingStore._initialized) {
- subscribe('files:node:rename', function(node: Node) {
- renamingStore.renamingNode = node
- renamingStore.newName = node.basename
- })
- renamingStore._initialized = true
+ newNodeName,
+ rename,
+ renamingNode,
}
+})
- return renamingStore
+/**
+ * Show a dialog asking user for confirmation about changing the file extension.
+ *
+ * @param oldExtension the old file name extension
+ * @param newExtension the new file name extension
+ */
+async function showFileExtensionDialog(oldExtension: string, newExtension: string): Promise<boolean> {
+ const { promise, resolve } = Promise.withResolvers<boolean>()
+ spawnDialog(
+ defineAsyncComponent(() => import('../views/DialogConfirmFileExtension.vue')),
+ { oldExtension, newExtension },
+ (useNewExtension: unknown) => resolve(Boolean(useNewExtension)),
+ )
+ return await promise
}