@samitouri / QOSami-HFS / commits / 92227d48

fix: admin/shared: adding a folder from disk on Windows displayed the new entry as it was a file instead of a folder, until you reloaded

Massimo Melina committed Mar 22, 2026 at 14:01 UTC 92227d48d5810184b4cadc8afdcc63d45c4371ec
4 files changed +9 -6
admin/src/FilePicker.ts
+1 -1
@@ -56,7 +56,7 @@ export default function FilePicker({ onSelect, multiple=true, files=true, folder
56 const [listHeight, setListHeight] = useState(0)
57 const filteredList = useMemo(() => _.sortBy(list.filter(it => filterMatch(it.n)), ['k', 'n']), [list, filterMatch])
58 const root = isWindows.current ? '' : '/'
59 - const pathDelimiter = isWindows.current ? '\\' : '/'
59 + const pathDelimiter = isWindows.current ? '\\' : '/' // should we use the new getHFS().pathSeparator instead?
60 const cwdDelimiter = enforceFinal(pathDelimiter, cwd)
61 const isRoot = cwd.length < 2
62 return h(Fragment, {},
admin/src/addFiles.ts
+2 -2
@@ -7,7 +7,7 @@ import { reindexVfs, VfsNodeAdmin } from './VfsPage'
7 import { addToChildrenOf } from './VfsTree'
8 import { prepareVfsUndo, state } from './state'
9 import FilePicker from './FilePicker'
10 -import { basename, extname, focusSelector } from '@hfs/shared'
10 +import { basename, extname, focusSelector, getHFS } from '@hfs/shared'
11
12 let lastFolder: undefined | string
13 export default function addFiles() {
@@ -36,7 +36,7 @@ export default function addFiles() {
36
37 function addNodes(parent: VfsNodeAdmin, nodes: VfsNodeAdmin[]) {
38 for (const n of nodes) {
39 - if (n.source?.endsWith('/') || !n.source && !n.url)
39 + if (n.source?.endsWith(getHFS().pathSeparator) || !n.source && !n.url)
40 n.type = 'folder'
41 n.id ||= parent.id + n.name + (n.type === 'folder' ? '/' : '')
42 n.parent = parent
dev-plugins.md
+4 -2
@@ -485,10 +485,11 @@ In frontend you will have access to the `HFS` object of the global scope, which
485 Returns falsy if entry is not supported.
486 - `copyTextToClipboard(text: string)` self-explanatory.
487 - `urlParams: object` you'll find each parameter in the URL mapped in this object as string.
488 +- `pathSeparator: string` you'll find \ or / depending on what OS HFS is running on
489 - `fileShowComponents: { Video, Audio }` exposes standard components used by file-show. Can be useful if you need extend them, inside `fileShow` event.
490 - `isShowSupported(entry: DirEntry): boolean` true if the entry is supported by Show.
491 - `textSortCompare(a: string, b: string): number` the function HFS will use for text sorting.
491 - Returns a negative if `a` must go before `b`, a positive if `b` must go before `a`, or zero they have same order.
492 + Returns a negative if `a` must go before `b`, a positive if `b` must go before `a`, or zero if they have same order.
493 It's exposed for you to use, or to overwrite if you need.
494 - `elementToEntry(el: HTMLElement): DirEntry | undefined` given a DOM element, returns the DirEntry associated to it, if any.
495 - `isVideoComponent(Component): boolean` tell if the component is used by show for video files.
@@ -1183,4 +1184,5 @@ If you want to override a text regardless of the language, use the special langu
1184 - 12.97 (v0.57.28)
1185 - HFS.customizeText
1186 - 13 (v3.1.0)
1186 - - backend events: dirEntry, request
\ No newline at end of file
1187 + - backend events: dirEntry, request
1188 + - HFS.pathSeparator
\ No newline at end of file
src/serveGuiFiles.ts
+2 -1
@@ -9,7 +9,7 @@ import { serveFile } from './serveFile'
9 import { getPluginConfigFields, getPluginInfo, mapPlugins, pluginsConfig } from './plugins'
10 import { authApis } from './api.auth'
11 import { ApiError } from './apiMiddleware'
12 -import { join, extname } from 'path'
12 +import { join, extname, sep } from 'path'
13 import {
14 CFG, debounceAsync, formatBytes, FRONTEND_OPTIONS, isPrimitive, newObj, objSameKeys, onlyTruthy, parseFile,
15 enforceStarting, statWithTimeout, shortenAgent
@@ -103,6 +103,7 @@ async function treatIndex(ctx: Koa.Context, filesUri: string, body: string) {
103 VERSION,
104 API_VERSION,
105 SPECIAL_URI, PLUGINS_PUB_URI, FRONTEND_URI,
106 + pathSeparator: sep,
107 session: session instanceof ApiError ? null : session,
108 plugins,
109 loadScripts: Object.fromEntries(mapPlugins((p, id) => [id, p.frontend_js?.map(f => f.includes('//') ? f : pub + id + '/' + f)])),