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