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)])),