auto-git:

[change] src/App.tsx
This commit is contained in:
2026-01-31 18:49:06 +01:00
parent 2297cab6a7
commit 988e2ed55a

View File

@@ -1226,56 +1226,529 @@ export default function App() {
/> />
</div> </div>
<div className="prompt-list"> <div className="prompt-list">
<div className="prompt-list__inner"> <div
{loadingTexts ? ( className="prompt-list__inner"
<div className="empty">Loading texts</div> onDragOver={(event) => {
) : texts.length === 0 ? ( event.preventDefault();
}}
onDrop={handleRootDrop}
>
{loadingTexts || loadingFolders ? (
<div className="empty">Loading</div>
) : texts.length === 0 && folders.length === 0 ? (
<div className="empty">No texts yet.</div> <div className="empty">No texts yet.</div>
) : ( ) : (
texts.map((text) => ( <>
<div {(foldersByParent.get(null) ?? [])
key={text.id} .filter((folder) => !hasSearch || visibleFolderIds?.has(folder.id))
className={`prompt-item${ .map((folder) => {
text.id === selectedTextId ? " is-active" : "" const expanded = isFolderExpanded(folder.id);
}`} const childFolders = foldersByParent.get(folder.id) ?? [];
onClick={() => setSelectedTextId(text.id)} const childTexts = textsByFolder.get(folder.id) ?? [];
role="button" return (
tabIndex={0} <div key={folder.id} className="folder-node">
onKeyDown={(event) => { <div
if (event.key === "Enter" || event.key === " ") { className={`folder-item${expanded ? " is-open" : ""}`}
event.preventDefault(); draggable
setSelectedTextId(text.id); onDragStart={(event) => handleDragStartFolder(event, folder)}
} onDragEnd={handleDragEnd}
}} onDragOver={(event) => event.preventDefault()}
> onDrop={(event) => handleFolderDrop(event, folder)}
<div className="prompt-item__content"> onClick={() => toggleFolderExpanded(folder.id)}
<div className="prompt-item__title">{text.title}</div> >
<div className="prompt-item__meta"> <button
Updated {formatDate(text.updated_at)} className="folder-item__toggle"
</div> type="button"
</div> aria-label={expanded ? "Collapse folder" : "Expand folder"}
<button onClick={(event) => {
className="prompt-item__delete" event.stopPropagation();
onClick={(event) => { toggleFolderExpanded(folder.id);
event.stopPropagation(); }}
setConfirmState({ >
title: "Delete text", {expanded ? "▾" : "▸"}
message: `Delete \"${text.title}\"? This removes all versions and drafts.`, </button>
actionLabel: "Delete text", <div className="folder-item__title">{folder.name}</div>
onConfirm: () => handleDeleteText(text.id) </div>
}); {expanded ? (
}} <div className="folder-children">
aria-label="Delete text" {childFolders
title="Delete text" .filter((child) => !hasSearch || visibleFolderIds?.has(child.id))
.map((child) => {
const childExpanded = isFolderExpanded(child.id);
const nestedFolders = foldersByParent.get(child.id) ?? [];
const nestedTexts = textsByFolder.get(child.id) ?? [];
return (
<div key={child.id} className="folder-node">
<div
className={`folder-item${childExpanded ? " is-open" : ""}`}
draggable
onDragStart={(event) => handleDragStartFolder(event, child)}
onDragEnd={handleDragEnd}
onDragOver={(event) => event.preventDefault()}
onDrop={(event) => handleFolderDrop(event, child)}
onClick={() => toggleFolderExpanded(child.id)}
>
<button
className="folder-item__toggle"
type="button"
aria-label={childExpanded ? "Collapse folder" : "Expand folder"}
onClick={(event) => {
event.stopPropagation();
toggleFolderExpanded(child.id);
}}
>
{childExpanded ? "▾" : "▸"}
</button>
<div className="folder-item__title">{child.name}</div>
</div>
{childExpanded ? (
<div className="folder-children">
{nestedFolders
.filter(
(nested) =>
!hasSearch || visibleFolderIds?.has(nested.id)
)
.map((nested) => (
<div key={nested.id} className="folder-node">
<div
className={`folder-item${
isFolderExpanded(nested.id) ? " is-open" : ""
}`}
draggable
onDragStart={(event) =>
handleDragStartFolder(event, nested)
}
onDragEnd={handleDragEnd}
onDragOver={(event) => event.preventDefault()}
onDrop={(event) => handleFolderDrop(event, nested)}
onClick={() => toggleFolderExpanded(nested.id)}
>
<button
className="folder-item__toggle"
type="button"
aria-label={
isFolderExpanded(nested.id)
? "Collapse folder"
: "Expand folder"
}
onClick={(event) => {
event.stopPropagation();
toggleFolderExpanded(nested.id);
}}
>
{isFolderExpanded(nested.id) ? "▾" : "▸"}
</button>
<div className="folder-item__title">
{nested.name}
</div>
</div>
{isFolderExpanded(nested.id) ? (
<div className="folder-children">
{(foldersByParent.get(nested.id) ?? [])
.filter(
(nestedChild) =>
!hasSearch ||
visibleFolderIds?.has(nestedChild.id)
)
.map((nestedChild) => (
<div key={nestedChild.id} className="folder-node">
<div
className={`folder-item${
isFolderExpanded(nestedChild.id)
? " is-open"
: ""
}`}
draggable
onDragStart={(event) =>
handleDragStartFolder(event, nestedChild)
}
onDragEnd={handleDragEnd}
onDragOver={(event) => event.preventDefault()}
onDrop={(event) =>
handleFolderDrop(event, nestedChild)
}
onClick={() => toggleFolderExpanded(nestedChild.id)}
>
<button
className="folder-item__toggle"
type="button"
aria-label={
isFolderExpanded(nestedChild.id)
? "Collapse folder"
: "Expand folder"
}
onClick={(event) => {
event.stopPropagation();
toggleFolderExpanded(nestedChild.id);
}}
>
{isFolderExpanded(nestedChild.id) ? "▾" : "▸"}
</button>
<div className="folder-item__title">
{nestedChild.name}
</div>
</div>
{isFolderExpanded(nestedChild.id) ? (
<div className="folder-children">
{(foldersByParent.get(nestedChild.id) ?? [])
.filter(
(deepChild) =>
!hasSearch ||
visibleFolderIds?.has(deepChild.id)
)
.map((deepChild) => (
<div
key={deepChild.id}
className="folder-node"
>
<div
className={`folder-item${
isFolderExpanded(deepChild.id)
? " is-open"
: ""
}`}
draggable
onDragStart={(event) =>
handleDragStartFolder(event, deepChild)
}
onDragEnd={handleDragEnd}
onDragOver={(event) => event.preventDefault()}
onDrop={(event) =>
handleFolderDrop(event, deepChild)
}
onClick={() =>
toggleFolderExpanded(deepChild.id)
}
>
<button
className="folder-item__toggle"
type="button"
aria-label={
isFolderExpanded(deepChild.id)
? "Collapse folder"
: "Expand folder"
}
onClick={(event) => {
event.stopPropagation();
toggleFolderExpanded(deepChild.id);
}}
>
{isFolderExpanded(deepChild.id) ? "▾" : "▸"}
</button>
<div className="folder-item__title">
{deepChild.name}
</div>
</div>
{isFolderExpanded(deepChild.id) ? (
<div className="folder-children">
{(textsByFolder.get(deepChild.id) ?? []).map(
(text) => (
<div
key={text.id}
className={`prompt-item${
text.id === selectedTextId
? " is-active"
: ""
}`}
draggable
onDragStart={(event) =>
handleDragStartText(event, text)
}
onDragEnd={handleDragEnd}
onDragOver={(event) =>
event.preventDefault()
}
onDrop={(event) =>
handleTextDrop(
event,
text.id,
text.folder_id ?? null
)
}
onClick={() =>
setSelectedTextId(text.id)
}
onContextMenu={(event) =>
handleTextContextMenu(event, text.id)
}
>
<div className="prompt-item__content">
<div className="prompt-item__title">
{text.title}
</div>
<div className="prompt-item__meta">
Updated {formatDate(text.updated_at)}
</div>
</div>
<button
className="prompt-item__delete"
onClick={(event) => {
event.stopPropagation();
setConfirmState({
title: "Delete text",
message: `Delete \"${text.title}\"? This removes all versions and drafts.`,
actionLabel: "Delete text",
onConfirm: () =>
handleDeleteText(text.id)
});
}}
aria-label="Delete text"
title="Delete text"
>
×
</button>
</div>
)
)}
</div>
) : null}
</div>
))}
{(textsByFolder.get(nestedChild.id) ?? []).map(
(text) => (
<div
key={text.id}
className={`prompt-item${
text.id === selectedTextId ? " is-active" : ""
}`}
draggable
onDragStart={(event) =>
handleDragStartText(event, text)
}
onDragEnd={handleDragEnd}
onDragOver={(event) => event.preventDefault()}
onDrop={(event) =>
handleTextDrop(
event,
text.id,
text.folder_id ?? null
)
}
onClick={() => setSelectedTextId(text.id)}
onContextMenu={(event) =>
handleTextContextMenu(event, text.id)
}
>
<div className="prompt-item__content">
<div className="prompt-item__title">
{text.title}
</div>
<div className="prompt-item__meta">
Updated {formatDate(text.updated_at)}
</div>
</div>
<button
className="prompt-item__delete"
onClick={(event) => {
event.stopPropagation();
setConfirmState({
title: "Delete text",
message: `Delete \"${text.title}\"? This removes all versions and drafts.`,
actionLabel: "Delete text",
onConfirm: () =>
handleDeleteText(text.id)
});
}}
aria-label="Delete text"
title="Delete text"
>
×
</button>
</div>
)
)}
</div>
) : null}
</div>
))}
{(textsByFolder.get(nested.id) ?? []).map((text) => (
<div
key={text.id}
className={`prompt-item${
text.id === selectedTextId ? " is-active" : ""
}`}
draggable
onDragStart={(event) => handleDragStartText(event, text)}
onDragEnd={handleDragEnd}
onDragOver={(event) => event.preventDefault()}
onDrop={(event) =>
handleTextDrop(
event,
text.id,
text.folder_id ?? null
)
}
onClick={() => setSelectedTextId(text.id)}
onContextMenu={(event) =>
handleTextContextMenu(event, text.id)
}
>
<div className="prompt-item__content">
<div className="prompt-item__title">{text.title}</div>
<div className="prompt-item__meta">
Updated {formatDate(text.updated_at)}
</div>
</div>
<button
className="prompt-item__delete"
onClick={(event) => {
event.stopPropagation();
setConfirmState({
title: "Delete text",
message: `Delete \"${text.title}\"? This removes all versions and drafts.`,
actionLabel: "Delete text",
onConfirm: () => handleDeleteText(text.id)
});
}}
aria-label="Delete text"
title="Delete text"
>
×
</button>
</div>
))}
</div>
) : null}
</div>
))}
{nestedTexts.map((text) => (
<div
key={text.id}
className={`prompt-item${
text.id === selectedTextId ? " is-active" : ""
}`}
draggable
onDragStart={(event) => handleDragStartText(event, text)}
onDragEnd={handleDragEnd}
onDragOver={(event) => event.preventDefault()}
onDrop={(event) =>
handleTextDrop(
event,
text.id,
text.folder_id ?? null
)
}
onClick={() => setSelectedTextId(text.id)}
onContextMenu={(event) =>
handleTextContextMenu(event, text.id)
}
>
<div className="prompt-item__content">
<div className="prompt-item__title">{text.title}</div>
<div className="prompt-item__meta">
Updated {formatDate(text.updated_at)}
</div>
</div>
<button
className="prompt-item__delete"
onClick={(event) => {
event.stopPropagation();
setConfirmState({
title: "Delete text",
message: `Delete \"${text.title}\"? This removes all versions and drafts.`,
actionLabel: "Delete text",
onConfirm: () => handleDeleteText(text.id)
});
}}
aria-label="Delete text"
title="Delete text"
>
×
</button>
</div>
))}
</div>
) : null}
</div>
);
})}
{childTexts.map((text) => (
<div
key={text.id}
className={`prompt-item${
text.id === selectedTextId ? " is-active" : ""
}`}
draggable
onDragStart={(event) => handleDragStartText(event, text)}
onDragEnd={handleDragEnd}
onDragOver={(event) => event.preventDefault()}
onDrop={(event) =>
handleTextDrop(event, text.id, text.folder_id ?? null)
}
onClick={() => setSelectedTextId(text.id)}
onContextMenu={(event) => handleTextContextMenu(event, text.id)}
>
<div className="prompt-item__content">
<div className="prompt-item__title">{text.title}</div>
<div className="prompt-item__meta">
Updated {formatDate(text.updated_at)}
</div>
</div>
<button
className="prompt-item__delete"
onClick={(event) => {
event.stopPropagation();
setConfirmState({
title: "Delete text",
message: `Delete \"${text.title}\"? This removes all versions and drafts.`,
actionLabel: "Delete text",
onConfirm: () => handleDeleteText(text.id)
});
}}
aria-label="Delete text"
title="Delete text"
>
×
</button>
</div>
))}
</div>
) : null}
</div>
);
})}
{(textsByFolder.get(null) ?? []).map((text) => (
<div
key={text.id}
className={`prompt-item${text.id === selectedTextId ? " is-active" : ""}`}
draggable
onDragStart={(event) => handleDragStartText(event, text)}
onDragEnd={handleDragEnd}
onDragOver={(event) => event.preventDefault()}
onDrop={(event) => handleTextDrop(event, text.id, text.folder_id ?? null)}
onClick={() => setSelectedTextId(text.id)}
onContextMenu={(event) => handleTextContextMenu(event, text.id)}
> >
× <div className="prompt-item__content">
</button> <div className="prompt-item__title">{text.title}</div>
</div> <div className="prompt-item__meta">Updated {formatDate(text.updated_at)}</div>
)) </div>
<button
className="prompt-item__delete"
onClick={(event) => {
event.stopPropagation();
setConfirmState({
title: "Delete text",
message: `Delete \"${text.title}\"? This removes all versions and drafts.`,
actionLabel: "Delete text",
onConfirm: () => handleDeleteText(text.id)
});
}}
aria-label="Delete text"
title="Delete text"
>
×
</button>
</div>
))}
</>
)} )}
</div> </div>
</div> </div>
<div className="sidebar__footer"> <div className="sidebar__footer">
<button className="button" onClick={handleNewFolder}>
New Folder
</button>
<button className="button button--primary" onClick={handleNewText}> <button className="button button--primary" onClick={handleNewText}>
New Text New Text
</button> </button>