Skip to content

Commit ed6589c

Browse files
committed
fix(files): Adjust getUniqueName for custom suffix and reuse for copy-move-action
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent 508a848 commit ed6589c

6 files changed

Lines changed: 43 additions & 41 deletions

File tree

apps/files/src/actions/moveOrCopyAction.ts

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import FolderMoveSvg from '@mdi/svg/svg/folder-move.svg?raw'
3939

4040
import { MoveCopyAction, canCopy, canMove, getQueue } from './moveOrCopyActionUtils'
4141
import logger from '../logger'
42+
import { getUniqueName } from '../utils/fileUtils'
4243

4344
/**
4445
* Return the action that is possible for the given nodes
@@ -67,30 +68,6 @@ const getActionForNodes = (nodes: Node[]): MoveCopyAction => {
6768
* @return {Promise<void>} A promise that resolves when the copy/move is done
6869
*/
6970
export const handleCopyMoveNodeTo = async (node: Node, destination: Folder, method: MoveCopyAction.COPY | MoveCopyAction.MOVE, overwrite = false) => {
70-
/**
71-
* Create an unique name for a node
72-
* @param node Node that is copied
73-
* @param otherNodes Other nodes in the target directory to check for unique name
74-
* @return Either the node basename, if unique, or the name with a `(copy N)` suffix that is unique
75-
*/
76-
const makeUniqueName = (node: Node, otherNodes: Node[]|FileStat[]) => {
77-
const basename = node.basename.slice(0, node.basename.lastIndexOf('.'))
78-
let index = 0
79-
80-
const currentName = () => {
81-
switch (index) {
82-
case 0: return node.basename
83-
case 1: return `${basename} (copy)${node.extension ?? ''}`
84-
default: return `${basename} ${t('files', '(copy %n)', undefined, index)}${node.extension ?? ''}` // TRANSLATORS: Meaning it is the n'th copy of a file
85-
}
86-
}
87-
88-
while (otherNodes.some((other: Node|FileStat) => currentName() === other.basename)) {
89-
index += 1
90-
}
91-
return currentName()
92-
}
93-
9471
if (!destination) {
9572
return
9673
}
@@ -122,6 +99,13 @@ export const handleCopyMoveNodeTo = async (node: Node, destination: Folder, meth
12299

123100
const queue = getQueue()
124101
return await queue.add(async () => {
102+
const copySuffix = (index: number) => {
103+
if (index === 1) {
104+
return t('files', '(copy)') // TRANSLATORS: Mark a file as a copy of another file
105+
}
106+
return t('files', '(copy %n)', undefined, index) // TRANSLATORS: Meaning it is the n'th copy of a file
107+
}
108+
125109
try {
126110
const client = davGetClient()
127111
const currentPath = join(davRootPath, node.path)
@@ -132,7 +116,7 @@ export const handleCopyMoveNodeTo = async (node: Node, destination: Folder, meth
132116
// If we do not allow overwriting then find an unique name
133117
if (!overwrite) {
134118
const otherNodes = await client.getDirectoryContents(destinationPath) as FileStat[]
135-
target = makeUniqueName(node, otherNodes)
119+
target = getUniqueName(node.basename, otherNodes.map((n) => n.basename), copySuffix)
136120
}
137121
await client.copyFile(currentPath, join(destinationPath, target))
138122
// If the node is copied into current directory the view needs to be updated

apps/files/src/init-templates.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
*
2222
*/
2323
import type { Entry } from '@nextcloud/files'
24+
import type { TemplateFile } from './types'
2425

2526
import { Folder, Node, Permission, addNewFileMenuEntry, removeNewFileMenuEntry } from '@nextcloud/files'
2627
import { generateOcsUrl } from '@nextcloud/router'
@@ -35,7 +36,7 @@ import Vue from 'vue'
3536
import PlusSvg from '@mdi/svg/svg/plus.svg?raw'
3637

3738
import TemplatePickerView from './views/TemplatePicker.vue'
38-
import { getUniqueName } from './newMenu/newFolder'
39+
import { getUniqueName } from './utils/fileUtils.ts'
3940
import { getCurrentUser } from '@nextcloud/auth'
4041

4142
// Set up logger
@@ -58,7 +59,7 @@ TemplatePickerRoot.id = 'template-picker'
5859
document.body.appendChild(TemplatePickerRoot)
5960

6061
// Retrieve and init templates
61-
let templates = loadState('files', 'templates', [])
62+
let templates = loadState<TemplateFile[]>('files', 'templates', [])
6263
let templatesPath = loadState('files', 'templates_path', false)
6364
logger.debug('Templates providers', { templates })
6465
logger.debug('Templates folder', { templatesPath })

apps/files/src/init.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
* along with this program. If not, see <http://www.gnu.org/licenses/>.
2020
*
2121
*/
22-
import MenuIcon from '@mdi/svg/svg/sun-compass.svg?raw'
23-
import { FileAction, addNewFileMenuEntry, registerDavProperty, registerFileAction } from '@nextcloud/files'
22+
import { addNewFileMenuEntry, registerDavProperty, registerFileAction } from '@nextcloud/files'
2423

2524
import { action as deleteAction } from './actions/deleteAction'
2625
import { action as downloadAction } from './actions/downloadAction'

apps/files/src/newMenu/newFolder.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
*/
2222
import type { Entry, Node } from '@nextcloud/files'
2323

24-
import { basename, extname } from 'path'
24+
import { basename } from 'path'
2525
import { emit } from '@nextcloud/event-bus'
2626
import { getCurrentUser } from '@nextcloud/auth'
2727
import { Permission, Folder } from '@nextcloud/files'
@@ -31,6 +31,7 @@ import axios from '@nextcloud/axios'
3131

3232
import FolderPlusSvg from '@mdi/svg/svg/folder-plus.svg?raw'
3333

34+
import { getUniqueName } from '../utils/fileUtils.ts'
3435
import logger from '../logger'
3536

3637
type createFolderResponse = {
@@ -55,17 +56,6 @@ const createNewFolder = async (root: Folder, name: string): Promise<createFolder
5556
}
5657
}
5758

58-
// TODO: move to @nextcloud/files
59-
export const getUniqueName = (name: string, names: string[]): string => {
60-
let newName = name
61-
let i = 1
62-
while (names.includes(newName)) {
63-
const ext = extname(name)
64-
newName = `${basename(name, ext)} (${i++})${ext}`
65-
}
66-
return newName
67-
}
68-
6959
export const entry = {
7060
id: 'newFolder',
7161
displayName: t('files', 'New folder'),

apps/files/src/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,12 @@ export interface UploaderStore {
111111
export interface DragAndDropStore {
112112
dragging: FileId[]
113113
}
114+
115+
export interface TemplateFile {
116+
app: string
117+
label: string
118+
extension: string
119+
iconClass?: string
120+
mimetypes: string[]
121+
ratio?: number
122+
}

apps/files/src/utils/fileUtils.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,25 @@
2121
*/
2222
import { FileType, type Node } from '@nextcloud/files'
2323
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
24+
import { basename, extname } from 'path'
25+
26+
// TODO: move to @nextcloud/files
27+
/**
28+
* Create an unique file name
29+
* @param name The initial name to use
30+
* @param otherNames Other names that are already used
31+
* @param suffix A function that takes an index an returns a suffix to add, defaults to '(index)'
32+
* @return Either the initial name, if unique, or the name with the suffix so that the name is unique
33+
*/
34+
export const getUniqueName = (name: string, otherNames: string[], suffix = (n: number) => `(${n})`): string => {
35+
let newName = name
36+
let i = 1
37+
while (otherNames.includes(newName)) {
38+
const ext = extname(name)
39+
newName = `${basename(name, ext)} ${suffix(i++)}${ext}`
40+
}
41+
return newName
42+
}
2443

2544
export const encodeFilePath = function(path) {
2645
const pathSections = (path.startsWith('/') ? path : `/${path}`).split('/')

0 commit comments

Comments
 (0)