creating a file just creates it — and an empty file is editable
Two halves of one behaviour. ── New file no longer navigates ── It created the file and then took you to /code-editor, which made it the only action in the browser that leaves the browser. Creating a folder does not leave the folder; uploading does not open what you uploaded. Now the file appears in the listing and that is the whole operation. Editing is the same double-click that opens anything else — the viewer's pane already has an editor in it, with a save. One way to open a thing, one place it appears. ── The bug that would have broken that on the first try ── FileViewerHeader gated its Edit toggle on `editable && content`. `content` is `string | null` and an empty file is the EMPTY STRING, which is falsy — so a file with nothing in it rendered no pencil and could not be edited. That is precisely the file you most want to edit, and it is exactly what "New file" produces: the flow above would have dead-ended on its first use. Now `content !== null`, which is what FileViewerBody already used two files away. Checked the other two truthiness gates rather than changing them blindly, and both are right: `isJson` returns false for empty (an empty string is not JSON, and JSON.parse would throw), and Read Aloud stays hidden because speaking an empty file is not a thing. `Open in editor` still goes to /code-editor and is untouched — that route is a different tool, with a tree and tabs, and today's links are the first way to reach it at all. Worth deciding separately. tsgo clean, frontend builds, 817 pass / 7 fail unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -325,11 +325,16 @@ export const useFileBrowserApp = (basePath: string, rootOverride?: string, urlPa
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an empty file and open it in the editor.
|
* Create an empty file. That is all it does.
|
||||||
|
*
|
||||||
|
* It used to navigate straight into `/code-editor`, which made "New file" the one action in the browser
|
||||||
|
* that took you somewhere else. Creating a folder does not leave the folder; uploading does not open
|
||||||
|
* what you uploaded. Now this behaves like both: the file appears in the listing, and editing it is the
|
||||||
|
* same double-click that opens anything else — the viewer's pane has an editor in it.
|
||||||
*
|
*
|
||||||
* The name is taken whole, extension and all — `notes.txt`, `deploy.sh`, `.env`, `Makefile` — because
|
* The name is taken whole, extension and all — `notes.txt`, `deploy.sh`, `.env`, `Makefile` — because
|
||||||
* the extension is what the editor uses to pick a language, and guessing one for the user would be
|
* the extension is what picks the language, and guessing one for the user would be wrong more often
|
||||||
* wrong more often than not. No extension is fine too; that file simply opens as plain text.
|
* than not. No extension is fine; that file opens as plain text.
|
||||||
*
|
*
|
||||||
* Refuses to clobber. `/write` is an overwrite, so creating over an existing name would silently empty
|
* Refuses to clobber. `/write` is an overwrite, so creating over an existing name would silently empty
|
||||||
* it — the one outcome nobody wants from a menu item called "New file". The check is a `read` because
|
* it — the one outcome nobody wants from a menu item called "New file". The check is a `read` because
|
||||||
@@ -348,7 +353,6 @@ export const useFileBrowserApp = (basePath: string, rootOverride?: string, urlPa
|
|||||||
await files.writeFile(filePath, '');
|
await files.writeFile(filePath, '');
|
||||||
await refresh();
|
await refresh();
|
||||||
toast.success(`Created "${name}"`);
|
toast.success(`Created "${name}"`);
|
||||||
navigate(`/code-editor?${EDITOR_FILE_PARAM}=${encodeURIComponent(filePath)}`);
|
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Failed to create file');
|
toast.error('Failed to create file');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,19 @@
|
|||||||
import { Download, Loader2, Music, Film, Image, FileType2, Volume2, ScanText, FileText, AudioLines, FolderArchive, Save, Pencil, Eye } from 'lucide-react';
|
import {
|
||||||
|
Download,
|
||||||
|
Loader2,
|
||||||
|
Music,
|
||||||
|
Film,
|
||||||
|
Image,
|
||||||
|
FileType2,
|
||||||
|
Volume2,
|
||||||
|
ScanText,
|
||||||
|
FileText,
|
||||||
|
AudioLines,
|
||||||
|
FolderArchive,
|
||||||
|
Save,
|
||||||
|
Pencil,
|
||||||
|
Eye,
|
||||||
|
} from 'lucide-react';
|
||||||
import { getIcon } from 'material-file-icons';
|
import { getIcon } from 'material-file-icons';
|
||||||
import { getLang } from './file-types';
|
import { getLang } from './file-types';
|
||||||
import { useFileViewer } from './FileViewerContext';
|
import { useFileViewer } from './FileViewerContext';
|
||||||
@@ -49,7 +64,10 @@ export const FileViewerHeader = () => {
|
|||||||
<span className="text-[10px] font-mono uppercase tracking-wider shrink-0 opacity-60">
|
<span className="text-[10px] font-mono uppercase tracking-wider shrink-0 opacity-60">
|
||||||
{fileType === 'code' ? getLang(fileName) : fileType}
|
{fileType === 'code' ? getLang(fileName) : fileType}
|
||||||
</span>
|
</span>
|
||||||
{editable && content && (
|
{/* `content !== null`, NOT `content` — an empty file is the empty string, which is falsy, so a
|
||||||
|
file with nothing in it had no way to start editing. That is precisely the file you most want
|
||||||
|
to edit, and it is what "New file" produces. The body already got this right. */}
|
||||||
|
{editable && content !== null && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setEditing(!editing)}
|
onClick={() => setEditing(!editing)}
|
||||||
className="p-1 rounded hover:bg-current/10 transition-colors cursor-pointer"
|
className="p-1 rounded hover:bg-current/10 transition-colors cursor-pointer"
|
||||||
@@ -95,7 +113,11 @@ export const FileViewerHeader = () => {
|
|||||||
className="p-1 rounded hover:bg-current/10 disabled:opacity-40 transition-colors cursor-pointer"
|
className="p-1 rounded hover:bg-current/10 disabled:opacity-40 transition-colors cursor-pointer"
|
||||||
title="Extract Audio"
|
title="Extract Audio"
|
||||||
>
|
>
|
||||||
{extractAudioLoading ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <AudioLines className="h-3.5 w-3.5" />}
|
{extractAudioLoading ? (
|
||||||
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<AudioLines className="h-3.5 w-3.5" />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{fileType === 'archive' && (
|
{fileType === 'archive' && (
|
||||||
@@ -105,7 +127,11 @@ export const FileViewerHeader = () => {
|
|||||||
className="p-1 rounded hover:bg-current/10 disabled:opacity-40 transition-colors cursor-pointer"
|
className="p-1 rounded hover:bg-current/10 disabled:opacity-40 transition-colors cursor-pointer"
|
||||||
title="Extract"
|
title="Extract"
|
||||||
>
|
>
|
||||||
{extractLoading ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <FolderArchive className="h-3.5 w-3.5" />}
|
{extractLoading ? (
|
||||||
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<FolderArchive className="h-3.5 w-3.5" />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{handleSaveResult && (
|
{handleSaveResult && (
|
||||||
|
|||||||
Reference in New Issue
Block a user