@samitouri / QOSami-HFS / commits / f908dace

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