rename #251
Massimo Melina committed
Jun 19, 2023 at 18:33 UTC
f908dace21b2313a3119894027b4409e0b517d88
6 files changed
+59
-6
frontend/src/fileMenu.ts
+27
-1
@@ -7,6 +7,8 @@ import { DirEntry, state } from './state'
7
import { deleteFiles } from './menu'
8
import { Link } from 'react-router-dom'
9
import { fileShow, getShowType } from './show'
10
+import { alertDialog, promptDialog } from './dialog'
11
+import { apiCall } from '@hfs/shared/api'
12
13
interface FileMenuEntry {
14
label: ReactNode
@@ -40,7 +42,8 @@ export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (FileMe
42
}
43
return x
44
}),
43
- isFolder && { label: t`Get list`, href: uri + '?get=list&folders=*', icon: 'list' }
45
+ state.can_delete && { label: t`Rename`, icon: 'edit', onClick: () => rename(entry) },
46
+ isFolder && { label: t`Get list`, href: uri + '?get=list&folders=*', icon: 'list' },
47
]
48
const props = [
49
[t`Name`, entry.name],
@@ -86,3 +89,26 @@ export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (FileMe
89
}
90
})
91
}
92
+
93
+async function rename(entry: DirEntry) {
94
+ const dest = await promptDialog(t`Name`, { def: entry.name, title: t`Rename` })
95
+ if (!dest) return
96
+ const uri = location.pathname + entry.uri
97
+ try {
98
+ await apiCall('rename', { uri, dest })
99
+ // update state instead of re-getting the list
100
+ const { n } = entry
101
+ const newN = n.replace(/(.*?)[^/]+(\/?)$/, (_,before,after) => before + dest + after)
102
+ const newEntry = new DirEntry(newN)
103
+ const i = _.findIndex(state.list, { n })
104
+ state.list[i] = newEntry
105
+ const j = _.findIndex(state.filteredList, { n })
106
+ if (j >= 0)
107
+ state.filteredList![j] = newEntry
108
+
109
+ alertDialog(t`Operation successful`).then() // don't wait, so it appears after the file-menu closes
110
+ }
111
+ catch(e: any) {
112
+ await alertDialog(e)
113
+ }
114
+}
\ No newline at end of file
frontend/src/icons.ts
+1
@@ -32,6 +32,7 @@ const SYS_ICONS: Record<string, string[]> = {
32
menu: ['☰'],
33
list: ['☰','menu'],
34
play: ['▶'],
35
+ edit: ['✏️'],
36
}
37
38
document.fonts.ready.then(async ()=> {
src/frontEndApis.ts
+25
-2
@@ -16,8 +16,8 @@ import {
16
HTTP_UNAUTHORIZED
17
} from './const'
18
import { hasPermission, urlToNode } from './vfs'
19
-import { mkdir, rm } from 'fs/promises'
20
-import { join } from 'path'
19
+import { mkdir, rename, rm } from 'fs/promises'
20
+import { dirname, join } from 'path'
21
22
export const customHeader = defineConfig('custom_header', '')
23
@@ -72,6 +72,29 @@ export const frontEndApis: ApiHandlers = {
72
}
73
},
74
75
+ async rename({ uri, dest }, ctx) {
76
+ apiAssertTypes({ string: { uri, dest } })
77
+ if (dest.includes('/') || dirTraversal(dest))
78
+ throw new ApiError(HTTP_FORBIDDEN)
79
+ const node = await urlToNode(uri, ctx)
80
+ if (!node)
81
+ throw new ApiError(HTTP_NOT_FOUND)
82
+ if (!hasPermission(node, 'can_delete', ctx))
83
+ throw new ApiError(HTTP_UNAUTHORIZED)
84
+ try {
85
+ if (node.name)
86
+ node.name = dest
87
+ else if (node.source)
88
+ await rename(node.source, join(dirname(node.source), dest))
89
+ else
90
+ throw new ApiError(HTTP_SERVER_ERROR)
91
+ return {}
92
+ }
93
+ catch (e: any) {
94
+ throw new ApiError(e.code || HTTP_SERVER_ERROR, e)
95
+ }
96
+ }
97
+
98
}
99
100
export function notifyClient(ctx: Koa.Context, name: string, data: any) {
src/langs/hfs-lang-en.json
+3
-1
@@ -116,6 +116,8 @@
116
"Size": "Size",
117
"Timestamp": "Timestamp",
118
"Show": "Show",
119
- "Loading failed": "Loading failed"
119
+ "Loading failed": "Loading failed",
120
+ "Rename": "Rename",
121
+ "Operation successful": "Operation successful"
122
}
123
}
src/langs/hfs-lang-it.json
+3
-1
@@ -110,6 +110,8 @@
110
"Size": "Dimensione",
111
"Timestamp": "Data/ora",
112
"Show": "Show",
113
- "Loading failed": "Caricamento fallito"
113
+ "Loading failed": "Caricamento fallito",
114
+ "Rename": "Rinomina",
115
+ "Operation successful": "Operazione riuscita"
116
}
117
}
todo.md
-1
@@ -16,7 +16,6 @@
16
- configure router with upnp. If it fails, suggest a guide. https://github.com/indutny/node-nat-upnp
17
- offer ddns registration/update
18
- admin/fs: sort items
19
-- rename file (with delete permission)
19
- admin/config: hide advanced settings
20
- admin/fs: support insert/delete key
21
- admin/monitor: show some info on what folder is browsing