Skip to content

Commit ef24254

Browse files
committed
✨ feat(button): Support deleting a history entry
1 parent 445a0c0 commit ef24254

2 files changed

Lines changed: 53 additions & 3 deletions

File tree

src/components/HistoryArea.tsx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,10 @@ const HistoryItem = React.memo<{
218218
const index = historyStore.$get().inputHistory.findIndex((e) => e === entry);
219219
return () => onJumpToInputHistory?.(index);
220220
})()}
221+
onDelete={(() => {
222+
const idx = historyStore.$get().history.indexOf(entry);
223+
return () => historyStore.removeInputBlockAt(idx);
224+
})()}
221225
/>
222226
),
223227
"{ type: 'output', variant: 'info', value: _ }": (value) => (
@@ -291,7 +295,8 @@ const ButtonGroup = React.memo<{
291295
input: string;
292296
inputAreaRef?: React.RefObject<InputAreaRef | null>;
293297
onJump?: () => void;
294-
}>(function ButtonGroup({ input, inputAreaRef, onJump }) {
298+
onDelete?: () => void;
299+
}>(function ButtonGroup({ input, inputAreaRef, onDelete, onJump }) {
295300
const [copied, setCopied] = useState(false);
296301
const [showMenu, setShowMenu] = useState(false);
297302
const isTouchDevice = useIsTouchDevice();
@@ -344,6 +349,16 @@ const ButtonGroup = React.memo<{
344349
{/* Show secondary buttons on hover for non-touch devices */}
345350
{!isTouchDevice && (
346351
<div className="mr-1.5 hidden space-x-1.5 group-hover:flex">
352+
<button
353+
type="button"
354+
title="Delete this entry"
355+
onClick={(e) => {
356+
e.stopPropagation();
357+
onDelete?.();
358+
}}
359+
className="rounded-md border border-gray-700/50 bg-black/70 p-1 text-gray-400 hover:bg-[#2a1e30] hover:text-[#ff6e6e]">
360+
<Icon icon="material-symbols:close" className="size-4" />
361+
</button>
347362
<button
348363
type="button"
349364
title="Copy to clipboard"
@@ -397,6 +412,17 @@ const ButtonGroup = React.memo<{
397412
{/* Touch-device dropdown menu */}
398413
{showMenu && (
399414
<div className="absolute top-8 right-0 z-10 min-w-32 rounded-md border border-gray-700/50 bg-black/90 py-0.5 shadow-lg">
415+
<button
416+
type="button"
417+
className="flex w-full items-center px-2.5 py-1.5 text-left text-xs text-gray-300 hover:bg-white/10"
418+
onClick={(e) => {
419+
e.stopPropagation();
420+
onDelete?.();
421+
setShowMenu(false);
422+
}}>
423+
<Icon icon="material-symbols:close" className="mr-2 size-3.5" />
424+
Delete
425+
</button>
400426
<button
401427
type="button"
402428
className="flex w-full items-center px-2.5 py-1.5 text-left text-xs text-gray-300 hover:bg-white/10"
@@ -464,7 +490,8 @@ const InputMessage = React.memo<{
464490
inputAreaRef?: React.RefObject<InputAreaRef | null>;
465491
historyAreaRef?: React.RefObject<HTMLDivElement | null>;
466492
onJump?: () => void;
467-
}>(function InputMessage({ historyAreaRef, inputAreaRef, onJump, value }) {
493+
onDelete?: () => void;
494+
}>(function InputMessage({ historyAreaRef, inputAreaRef, onDelete, onJump, value }) {
468495
const [isTooCloseToTop, setIsTooCloseToTop] = useState(false);
469496
const messageRef = useRef<HTMLDivElement>(null);
470497

@@ -524,7 +551,12 @@ const InputMessage = React.memo<{
524551
{/* Only render ButtonGroup if not too close to top,
525552
to avoid overlapping with the GitHub icon (which is always at the top right) */}
526553
{!isTooCloseToTop && (
527-
<ButtonGroup input={value} inputAreaRef={inputAreaRef} onJump={onJump} />
554+
<ButtonGroup
555+
input={value}
556+
inputAreaRef={inputAreaRef}
557+
onJump={onJump}
558+
onDelete={onDelete}
559+
/>
528560
)}
529561
</div>
530562
</div>

src/stores/history.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,24 @@ const historyStore = create({
3838
const message = error instanceof Error ? `${error.name}: ${error.message}` : show(error);
3939
this.history.push({ type: "error", value: message });
4040
},
41+
42+
/**
43+
* Remove an input entry and all subsequent outputs/errors up to (but not including) the next
44+
* input or recovered-mark. If the index is invalid or not an input, this is a no-op.
45+
*/
46+
removeInputBlockAt(historyIndex: number) {
47+
const history = this.history;
48+
if (historyIndex < 0 || historyIndex >= history.length) return;
49+
const entry = history[historyIndex];
50+
if (entry?.type !== "input") return;
51+
let end = historyIndex + 1;
52+
while (end < history.length) {
53+
const e = history[end] as HistoryEntry | { type: "recovered-mark" };
54+
if (e.type === "input" || e.type === "recovered-mark") break;
55+
end++;
56+
}
57+
this.history.splice(historyIndex, end - historyIndex);
58+
},
4159
});
4260

4361
export default historyStore;

0 commit comments

Comments
 (0)