Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions src/components/ExportModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { useEffect } from 'react';
import { createPortal } from 'react-dom';
import { FileText, FileJson, X } from 'lucide-react';
import type { Prompt } from '../types';

interface ExportModalProps {
prompt: Prompt;
onClose: () => void;
onExportMD: (p: Prompt) => void;
onExportJSON: (p: Prompt) => void;
}

export function ExportModal({
prompt,
onClose,
onExportMD,
onExportJSON,
}: ExportModalProps) {
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [onClose]);
Comment on lines +19 to +25

const handleExportMD = () => {
onExportMD(prompt);
onClose();
};

const handleExportJSON = () => {
onExportJSON(prompt);
onClose();
};

if (typeof document === 'undefined') return null;

return createPortal(
<div className="modal-overlay" onClick={onClose}>
<div
className="modal-content export-modal"
onClick={(e) => e.stopPropagation()}
role="dialog"
aria-modal="true"
aria-labelledby="export-modal-title"
>
<div className="modal-header">
<h3 id="export-modal-title">Export "{prompt.title}"</h3>
<button className="btn-icon" onClick={onClose} title="Close">
<X size={18} />
</button>
</div>
<div className="modal-actions">
<button
type="button"
className="btn-primary"
onClick={handleExportMD}
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '0.5rem' }}
>
<FileText size={16} /> Download as Markdown (.md)
</button>
<button
type="button"
className="btn-secondary"
onClick={handleExportJSON}
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '0.5rem' }}
>
<FileJson size={16} /> Download as JSON (.json)
</button>
</div>
</div>
</div>,
document.body,
);
}
31 changes: 16 additions & 15 deletions src/components/PromptList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from 'lucide-react';
import { VariableInjector } from './VariableInjector';
import { VersionHistory } from './VersionHistory';
import { ExportModal } from './ExportModal';

interface ListChildData<T = unknown> {
index: number;
Expand Down Expand Up @@ -54,8 +55,7 @@ interface PromptCardActionsProps {
prompt: Prompt;
onEdit: (p: Prompt) => void;
onCopy: (body: string) => void;
onExportMD: (p: Prompt) => void;
onExportJSON: (p: Prompt) => void;
onExport: (p: Prompt) => void;
onViewHistory: (id: string) => void;
onDelete: (id: string) => void;
}
Expand All @@ -64,8 +64,7 @@ function PromptCardActions({
prompt,
onEdit,
onCopy,
onExportMD,
onExportJSON,
onExport,
onViewHistory,
onDelete,
}: PromptCardActionsProps) {
Expand All @@ -82,19 +81,12 @@ function PromptCardActions({
<Copy size={16} />
</button>
<button
onClick={() => onExportMD(prompt)}
onClick={() => onExport(prompt)}
className="btn-icon"
title="Export as Markdown"
title="Export"
>
<Download size={16} />
</button>
<button
onClick={() => onExportJSON(prompt)}
className="btn-icon"
title="Export as JSON"
>
<Download size={16} style={{ color: 'var(--primary-color)' }} />
</button>
<button
onClick={() => onViewHistory(prompt.id)}
className="btn-icon"
Expand Down Expand Up @@ -204,6 +196,7 @@ export function PromptList({
}: PromptListProps) {
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
const [historyPromptId, setHistoryPromptId] = useState<string | null>(null);
const [exportPrompt, setExportPrompt] = useState<Prompt | null>(null);

// O(1) workspace lookup optimization using Object.fromEntries
const workspaceMap = useMemo(
Expand Down Expand Up @@ -316,8 +309,7 @@ export function PromptList({
prompt={prompt}
onEdit={onEdit}
onCopy={handleCopy}
onExportMD={handleExportMD}
onExportJSON={handleExportJSON}
onExport={setExportPrompt}
onViewHistory={setHistoryPromptId}
onDelete={onDelete}
/>
Expand Down Expand Up @@ -395,6 +387,15 @@ export function PromptList({
/>
)}

{exportPrompt && (
<ExportModal
prompt={exportPrompt}
onClose={() => setExportPrompt(null)}
onExportMD={handleExportMD}
onExportJSON={handleExportJSON}
/>
)}

<div className={`prompt-list ${viewMode}`} id="prompt-list">
{prompts.map((_: Prompt, idx: number) => (
<PromptRow
Expand Down
20 changes: 14 additions & 6 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -316,12 +316,6 @@ body {
font-size: 1.1rem;
line-height: 1.4;
margin-bottom: 0.5rem;
height: 3rem; /* Fixed height for title to align cards */
display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}

.prompt-actions {
Expand Down Expand Up @@ -1072,12 +1066,26 @@ button {
margin-bottom: 1.5rem;
}

.prompt-list.grid .prompt-header h4 {
height: 3rem;
display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
Comment on lines +1069 to +1076

.prompt-list.list {
display: flex;
flex-direction: column;
gap: 1.5rem;
}

.prompt-list.list .prompt-card {
box-shadow: var(--shadow-sm);
border-left: 3px solid var(--primary-color);
}

.prompt-header {
display: flex;
justify-content: space-between;
Expand Down
Loading