fix: menu for web folders had not the right items
Massimo Melina committed
Jan 27, 2024 at 21:53 UTC
6bd3f2f1ba4e755de407183621c5f5931e9d213c
4 files changed
+49
-46
frontend/src/BrowseFiles.ts
+34
-28
@@ -205,37 +205,43 @@ const Entry = memo(({ entry, midnight, separator }: EntryProps) => {
205
const ariaId = useId()
206
const ariaProps = { id: ariaId, 'aria-label': prefix(name + ' (', isFolder ? "Folder" : entry.web ? "Web page" : isLink ? "Link" : '', ')') }
207
return h('li', { className, label: separator },
208
- h(CustomCode, { name: 'entry', props: { entry }, ifEmpty: () => h(Fragment, {},
209
- showFilter && h(Checkbox, {
210
- disabled: isLink,
211
- 'aria-labelledby': ariaId,
212
- value: selected[uri],
213
- onChange(v){
214
- if (v)
215
- return state.selected[uri] = true
216
- delete state.selected[uri]
217
- },
218
- }),
219
- h('span', { className: 'link-wrapper' }, // container to handle mouse over for both children
220
- isFolder && !entry.web ? h(Fragment, {}, // internal navigation, use Link component
221
- h(Link, { to: uri, ...ariaProps }, ico, entry.n.slice(0,-1)), // don't use name, as we want to include whole path in case of search
208
+ h(CustomCode, {
209
+ name: 'entry',
210
+ props: { entry },
211
+ ifEmpty: () => h(Fragment, {},
212
+ showFilter && h(Checkbox, {
213
+ disabled: isLink,
214
+ 'aria-labelledby': ariaId,
215
+ value: selected[uri],
216
+ onChange(v) {
217
+ if (v)
218
+ return state.selected[uri] = true
219
+ delete state.selected[uri]
220
+ },
221
+ }),
222
+ h('span', { className: 'link-wrapper' }, // container to handle mouse over for both children
223
+ ...isFolder || entry.web ? [ // internal navigation, use Link component
224
+ h(Link, { to: uri, ...ariaProps }, ico, entry.n.slice(0, -1)), // don't use name, as we want to include whole path in case of search
225
// popup button is here to be able to detect link-wrapper:hover
223
- file_menu_on_link && !showingButton && h('button', { className: 'popup-menu-button', onClick: fileMenu }, hIcon('menu'), t`Menu`)
224
- )
225
- : containerName ? h(Fragment, {},
226
+ file_menu_on_link && !showingButton && h('button', {
227
+ className: 'popup-menu-button',
228
+ onClick: fileMenu
229
+ }, hIcon('menu'), t`Menu`)
230
+ ] : containerName ? [
231
h('a', { href: uri, onClick, tabIndex: -1 }, ico),
227
- h(Link, { to: containerDir, className:'container-folder', tabIndex: -1 }, containerName),
232
+ h(Link, { to: containerDir, className: 'container-folder', tabIndex: -1 }, containerName),
233
h('a', { href: uri, onClick, ...ariaProps }, name)
229
- ) : h('a', { href: uri, onClick, ...ariaProps }, ico, name),
230
- ),
231
- h(CustomCode, { name: 'afterEntryName', props: { entry } }),
232
- entry.comment && h('div', { className: 'entry-comment' }, entry.comment),
233
- h('div', { className: 'entry-panel' },
234
- h(EntryDetails, { entry, midnight }),
235
- showingButton && h('button', { className: 'file-menu-button', onClick: fileMenu }, hIcon('menu')),
236
- ),
237
- h('div'),
238
- ) }),
234
+ ] : [h('a', { href: uri, onClick, ...ariaProps }, ico, name)],
235
+ ),
236
+ h(CustomCode, { name: 'afterEntryName', props: { entry } }),
237
+ entry.comment && h('div', { className: 'entry-comment' }, entry.comment),
238
+ h('div', { className: 'entry-panel' },
239
+ h(EntryDetails, { entry, midnight }),
240
+ showingButton && h('button', { className: 'file-menu-button', onClick: fileMenu }, hIcon('menu')),
241
+ ),
242
+ h('div'),
243
+ )
244
+ }),
245
)
246
247
function fileMenu(ev: MouseEvent) {
frontend/src/fileMenu.ts
+1
-1
@@ -54,7 +54,7 @@ export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (FileMe
54
}),
55
state.props?.can_delete && { id: 'rename', label: t`Rename`, icon: 'edit', onClick: () => rename(entry) },
56
state.props?.can_delete && { id: 'cut', label: t`Cut`, icon: 'cut', onClick: () => close(cut([entry])) },
57
- isFolder && { id: 'list', label: t`Get list`, href: uri + '?get=list&folders=*', icon: 'list' },
57
+ isFolder && !entry.web && { id: 'list', label: t`Get list`, href: uri + '?get=list&folders=*', icon: 'list' },
58
]
59
const props = [
60
[t`Name`, entry.name],
src/api.get_file_list.ts
+10
-14
@@ -102,34 +102,30 @@ export const get_file_list: ApiHandler = async ({ uri='/', offset, limit, search
102
}
103
104
async function nodeToDirEntry(ctx: Koa.Context, node: VfsNode): Promise<DirEntry | null> {
105
- let { source, url } = node
105
+ const { source, url } = node
106
const name = getNodeName(node)
107
if (url)
108
return name ? { n: name, url } : null
109
- if (!source) // virtual folder
110
- return name ? { n: name + '/' } : null
111
- if (node.isFolder && await hasDefaultFile(node))
112
- return { n: name + '/', web: true }
109
+ const isFolder = await nodeIsDirectory(node)
110
try {
114
- const st = await stat(source)
115
- const folder = st.isDirectory()
116
- const { ctime, mtime } = st
111
+ const st = source ? await stat(source) : undefined
112
const pl = node.can_list === WHO_NO_ONE ? 'l'
113
: !hasPermission(node, 'can_list', ctx) ? 'L'
114
: ''
115
// no download here, but maybe inside?
121
- const pr = node.can_read === WHO_NO_ONE && !(folder && filesInsideCould()) ? 'r'
116
+ const pr = node.can_read === WHO_NO_ONE && !(isFolder && filesInsideCould()) ? 'r'
117
: !hasPermission(node, 'can_read', ctx) ? 'R'
118
: ''
119
const pd = !can_delete && hasPermission(node, 'can_delete', ctx) ? 'd' : ''
125
- const pa = node.isFolder && Boolean(can_archive) === hasPermission(node, 'can_archive', ctx) ? '' : can_archive ? 'a' : 'A'
120
+ const pa = isFolder && Boolean(can_archive) === hasPermission(node, 'can_archive', ctx) ? '' : can_archive ? 'a' : 'A'
121
return {
127
- n: name + (folder ? '/' : ''),
128
- c: ctime,
129
- m: Math.abs(+mtime-+ctime) < 1000 ? undefined : mtime,
130
- s: folder ? undefined : st.size,
122
+ n: name + (isFolder ? '/' : ''),
123
+ c: st?.ctime,
124
+ m: !st || Math.abs(+st.mtime - +st.ctime) < 1000 ? undefined : st.mtime,
125
+ s: isFolder ? undefined : st?.size,
126
p: (pr + pl + pd + pa) || undefined,
127
comment: await getCommentFor(source),
128
+ web: await hasDefaultFile(node) ? true : undefined,
129
}
130
}
131
catch {
src/serveGuiAndSharedFiles.ts
+4
-3
@@ -77,7 +77,8 @@ export const serveGuiAndSharedFiles: Koa.Middleware = async (ctx, next) => {
77
res()
78
}))
79
}
80
- if (node.default && path.endsWith('/')) // final/ needed on browser to make resource urls correctly with html pages
80
+ const { get } = ctx.query
81
+ if (node.default && path.endsWith('/') && !get) // final/ needed on browser to make resource urls correctly with html pages
82
node = await urlToNode(node.default, ctx, node) ?? node
83
if (!await nodeIsDirectory(node))
84
return !node.source ? sendErrorPage(ctx, HTTP_METHOD_NOT_ALLOWED)
@@ -97,8 +98,8 @@ export const serveGuiAndSharedFiles: Koa.Middleware = async (ctx, next) => {
98
return serveFrontendFiles(ctx, next)
99
}
100
ctx.set({ server: `HFS ${VERSION} ${BUILD_TIMESTAMP}` })
100
- return ctx.query.get === 'zip' ? zipStreamFromFolder(node, ctx)
101
- : ctx.query.get === 'list' ? sendFolderList(node, ctx)
101
+ return get === 'zip' ? zipStreamFromFolder(node, ctx)
102
+ : get === 'list' ? sendFolderList(node, ctx)
103
: serveFrontendFiles(ctx, next)
104
}
105