First draft
This commit is contained in:
1 parent
aba52f2b3c
commit
84bc6884b0
37 files changed
+6697
-27
No files matched your search
@@ -0,0 +1,539 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Plus,
|
||||
Pin,
|
||||
Trash2,
|
||||
Download,
|
||||
CheckSquare,
|
||||
Heading1,
|
||||
Heading2,
|
||||
List,
|
||||
Code,
|
||||
Sparkles,
|
||||
AlignLeft,
|
||||
Check,
|
||||
Search,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
} from 'lucide-react';
|
||||
import { BlockType, Document, DocumentBlock } from '../../lib/types';
|
||||
import { api, triggerHaptic } from '../../lib/api';
|
||||
|
||||
interface WorkspaceViewProps {
|
||||
initialDocId?: string | null;
|
||||
onStatsChange: () => void;
|
||||
}
|
||||
|
||||
const ICONS = ['🚀', '💡', '📄', '🏡', '⚡', '🎯', '🍕', '🛠️', '🧠', '🔥', '🏔️', '📦'];
|
||||
|
||||
export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
initialDocId,
|
||||
onStatsChange,
|
||||
}) => {
|
||||
const [docs, setDocs] = useState<Document[]>([]);
|
||||
const [selectedDoc, setSelectedDoc] = useState<Document | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [saveState, setSaveState] = useState<'saved' | 'saving'>('saved');
|
||||
const [showIconPicker, setShowIconPicker] = useState(false);
|
||||
|
||||
const loadDocs = async (selectId?: string | null) => {
|
||||
const list = await api.getDocuments();
|
||||
setDocs(list);
|
||||
if (selectId) {
|
||||
const found = list.find((d) => d.id === selectId);
|
||||
if (found) {
|
||||
setSelectedDoc(found);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!selectedDoc && list.length > 0) {
|
||||
setSelectedDoc(list[0]);
|
||||
} else if (selectedDoc) {
|
||||
const updated = list.find((d) => d.id === selectedDoc.id);
|
||||
if (updated) setSelectedDoc(updated);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadDocs(initialDocId);
|
||||
}, [initialDocId]);
|
||||
|
||||
const persistDocument = async (updatedDoc: Document) => {
|
||||
setSelectedDoc(updatedDoc);
|
||||
setDocs((prev) => prev.map((d) => (d.id === updatedDoc.id ? updatedDoc : d)));
|
||||
setSaveState('saving');
|
||||
try {
|
||||
await api.updateDocument(updatedDoc.id, {
|
||||
title: updatedDoc.title,
|
||||
icon: updatedDoc.icon,
|
||||
blocks: updatedDoc.blocks,
|
||||
tags: updatedDoc.tags,
|
||||
isPinned: updatedDoc.isPinned,
|
||||
});
|
||||
} finally {
|
||||
setSaveState('saved');
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateDoc = async () => {
|
||||
triggerHaptic(15);
|
||||
const created = await api.createDocument({
|
||||
title: 'Neues Projekt / Notiz',
|
||||
icon: '💡',
|
||||
blocks: [
|
||||
{
|
||||
id: `blk_${Date.now()}_1`,
|
||||
type: 'callout',
|
||||
content: 'Kernidee oder Ziel dieses Projekts kurz zusammenfassen…',
|
||||
},
|
||||
{
|
||||
id: `blk_${Date.now()}_2`,
|
||||
type: 'h2',
|
||||
content: 'Nächste Schritte',
|
||||
},
|
||||
{
|
||||
id: `blk_${Date.now()}_3`,
|
||||
type: 'todo',
|
||||
content: 'Ersten Punkt abhaken',
|
||||
checked: false,
|
||||
},
|
||||
],
|
||||
tags: ['projekt'],
|
||||
});
|
||||
await loadDocs(created.id);
|
||||
onStatsChange();
|
||||
};
|
||||
|
||||
const handleDeleteDoc = async (docId: string) => {
|
||||
triggerHaptic(20);
|
||||
await api.deleteDocument(docId);
|
||||
const remaining = docs.filter((d) => d.id !== docId);
|
||||
setDocs(remaining);
|
||||
setSelectedDoc(remaining[0] || null);
|
||||
onStatsChange();
|
||||
};
|
||||
|
||||
const addBlock = (type: BlockType, afterIndex?: number) => {
|
||||
if (!selectedDoc) return;
|
||||
const newBlock: DocumentBlock = {
|
||||
id: `blk_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
||||
type,
|
||||
content: '',
|
||||
checked: false,
|
||||
lang: type === 'code' ? 'bash' : undefined,
|
||||
};
|
||||
|
||||
const nextBlocks = [...selectedDoc.blocks];
|
||||
if (afterIndex !== undefined && afterIndex >= 0) {
|
||||
nextBlocks.splice(afterIndex + 1, 0, newBlock);
|
||||
} else {
|
||||
nextBlocks.push(newBlock);
|
||||
}
|
||||
|
||||
persistDocument({ ...selectedDoc, blocks: nextBlocks });
|
||||
};
|
||||
|
||||
const updateBlock = (blockId: string, patch: Partial<DocumentBlock>) => {
|
||||
if (!selectedDoc) return;
|
||||
const nextBlocks = selectedDoc.blocks.map((b) =>
|
||||
b.id === blockId ? { ...b, ...patch } : b
|
||||
);
|
||||
persistDocument({ ...selectedDoc, blocks: nextBlocks });
|
||||
};
|
||||
|
||||
const removeBlock = (blockId: string) => {
|
||||
if (!selectedDoc || selectedDoc.blocks.length <= 1) return;
|
||||
const nextBlocks = selectedDoc.blocks.filter((b) => b.id !== blockId);
|
||||
persistDocument({ ...selectedDoc, blocks: nextBlocks });
|
||||
};
|
||||
|
||||
const moveBlock = (index: number, direction: -1 | 1) => {
|
||||
if (!selectedDoc) return;
|
||||
const targetIdx = index + direction;
|
||||
if (targetIdx < 0 || targetIdx >= selectedDoc.blocks.length) return;
|
||||
const nextBlocks = [...selectedDoc.blocks];
|
||||
const [moved] = nextBlocks.splice(index, 1);
|
||||
nextBlocks.splice(targetIdx, 0, moved);
|
||||
persistDocument({ ...selectedDoc, blocks: nextBlocks });
|
||||
};
|
||||
|
||||
const handleExportMarkdown = () => {
|
||||
if (!selectedDoc) return;
|
||||
const lines: string[] = [`# ${selectedDoc.icon} ${selectedDoc.title}`, ''];
|
||||
for (const b of selectedDoc.blocks) {
|
||||
switch (b.type) {
|
||||
case 'h1':
|
||||
lines.push(`# ${b.content}`, '');
|
||||
break;
|
||||
case 'h2':
|
||||
lines.push(`## ${b.content}`, '');
|
||||
break;
|
||||
case 'h3':
|
||||
lines.push(`### ${b.content}`, '');
|
||||
break;
|
||||
case 'todo':
|
||||
lines.push(`- [${b.checked ? 'x' : ' '}] ${b.content}`);
|
||||
break;
|
||||
case 'bullet':
|
||||
lines.push(`- ${b.content}`);
|
||||
break;
|
||||
case 'callout':
|
||||
lines.push(`> 💡 ${b.content}`, '');
|
||||
break;
|
||||
case 'code':
|
||||
lines.push('```' + (b.lang || ''), b.content, '```', '');
|
||||
break;
|
||||
default:
|
||||
lines.push(b.content, '');
|
||||
}
|
||||
}
|
||||
|
||||
const blob = new Blob([lines.join('\n')], { type: 'text/markdown;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${selectedDoc.title.toLowerCase().replace(/[^a-z0-9äöüß]+/g, '-')}.md`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const filteredDocs = docs.filter(
|
||||
(d) =>
|
||||
d.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
d.content.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 max-w-6xl mx-auto">
|
||||
{/* Left Document List Sidebar */}
|
||||
<div className="lg:col-span-4 space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="w-4 h-4 text-[#9aa0a6] absolute left-3.5 top-1/2 -translate-y-1/2" />
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Notizen durchsuchen…"
|
||||
className="w-full h-10 pl-9 pr-4 rounded-full bg-[#1e1f20] text-xs sm:text-sm text-[#e3e3e3] placeholder-[#9aa0a6] gemini-border focus:outline-none focus:border-[#8ab4f8]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleCreateDoc}
|
||||
className="h-10 px-4 rounded-full bg-[#8ab4f8] text-[#0b0f19] font-semibold text-xs sm:text-sm flex items-center gap-1.5 hover:bg-[#a1c2fa] transition-colors flex-shrink-0"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
<span>Neu</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 max-h-[540px] overflow-y-auto pr-1">
|
||||
{filteredDocs.map((doc) => {
|
||||
const active = selectedDoc?.id === doc.id;
|
||||
return (
|
||||
<button
|
||||
key={doc.id}
|
||||
onClick={() => setSelectedDoc(doc)}
|
||||
className={`w-full text-left p-4 rounded-3xl transition-all flex items-start justify-between gap-3 ${
|
||||
active
|
||||
? 'bg-[#1e1f20] border border-[#8ab4f8]/50 shadow-gemini-glow'
|
||||
: 'bg-[#131314] hover:bg-[#1e1f20] gemini-border'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-3 min-w-0">
|
||||
<span className="text-2xl flex-shrink-0">{doc.icon}</span>
|
||||
<div className="min-w-0">
|
||||
<h4 className="font-semibold text-sm text-[#e3e3e3] truncate">{doc.title}</h4>
|
||||
<p className="text-xs text-[#9aa0a6] truncate mt-0.5">
|
||||
{doc.blocks?.[0]?.content || 'Leeres Dokument'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{doc.isPinned && <Pin className="w-3.5 h-3.5 text-[#8ab4f8] flex-shrink-0 mt-1" />}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Notion-Flair Block Editor */}
|
||||
<div className="lg:col-span-8">
|
||||
{selectedDoc ? (
|
||||
<div className="p-6 sm:p-8 rounded-3xl bg-[#1e1f20] gemini-border shadow-card-depth">
|
||||
{/* Document Top Action Bar */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 pb-4 mb-6 border-b border-white/[0.06]">
|
||||
<div className="flex items-center gap-2 text-xs text-[#9aa0a6]">
|
||||
<span className="px-3 py-1 rounded-full bg-[#131314] text-[#81c995] flex items-center gap-1">
|
||||
<Check className="w-3 h-3" />
|
||||
{saveState === 'saving' ? 'Speichert…' : 'Automatisch gespeichert'}
|
||||
</span>
|
||||
<span>• {selectedDoc.blocks.length} Blöcke</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() =>
|
||||
persistDocument({ ...selectedDoc, isPinned: !selectedDoc.isPinned })
|
||||
}
|
||||
className={`h-8 px-3 rounded-full text-xs flex items-center gap-1.5 transition-colors ${
|
||||
selectedDoc.isPinned
|
||||
? 'bg-[#8ab4f8]/20 text-[#8ab4f8] border border-[#8ab4f8]/40'
|
||||
: 'bg-[#282a2c] text-[#9aa0a6] hover:text-[#e3e3e3]'
|
||||
}`}
|
||||
>
|
||||
<Pin className="w-3.5 h-3.5" />
|
||||
<span>{selectedDoc.isPinned ? 'Angepinnt' : 'Anpinnen'}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleExportMarkdown}
|
||||
className="h-8 px-3 rounded-full bg-[#282a2c] hover:bg-[#323639] text-xs text-[#e3e3e3] flex items-center gap-1.5 transition-colors"
|
||||
>
|
||||
<Download className="w-3.5 h-3.5 text-[#8ab4f8]" />
|
||||
<span>Markdown (.md)</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleDeleteDoc(selectedDoc.id)}
|
||||
className="w-8 h-8 rounded-full bg-[#282a2c] hover:bg-[#f28b82]/20 text-[#9aa0a6] hover:text-[#f28b82] flex items-center justify-center transition-colors"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Document Icon & Title */}
|
||||
<div className="flex items-center gap-3 mb-6 relative">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowIconPicker((prev) => !prev)}
|
||||
className="w-12 h-12 rounded-2xl bg-[#131314] hover:bg-[#282a2c] gemini-border text-2xl flex items-center justify-center flex-shrink-0"
|
||||
title="Icon ändern"
|
||||
>
|
||||
{selectedDoc.icon}
|
||||
</button>
|
||||
|
||||
{showIconPicker && (
|
||||
<div className="absolute top-14 left-0 z-20 p-2.5 rounded-3xl bg-[#131314] gemini-border shadow-card-depth flex flex-wrap gap-1.5 w-64">
|
||||
{ICONS.map((ic) => (
|
||||
<button
|
||||
key={ic}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
persistDocument({ ...selectedDoc, icon: ic });
|
||||
setShowIconPicker(false);
|
||||
}}
|
||||
className="w-9 h-9 rounded-full hover:bg-[#282a2c] text-lg flex items-center justify-center"
|
||||
>
|
||||
{ic}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<input
|
||||
type="text"
|
||||
value={selectedDoc.title}
|
||||
onChange={(e) => persistDocument({ ...selectedDoc, title: e.target.value })}
|
||||
placeholder="Dokument-Titel…"
|
||||
className="w-full bg-transparent text-2xl sm:text-3xl font-bold text-[#e3e3e3] focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Blocks Stack */}
|
||||
<div className="space-y-3">
|
||||
{selectedDoc.blocks.map((block, idx) => (
|
||||
<div key={block.id} className="group flex items-start gap-2">
|
||||
{/* Block Renderer */}
|
||||
<div className="flex-1 min-w-0">
|
||||
{block.type === 'h1' && (
|
||||
<input
|
||||
type="text"
|
||||
value={block.content}
|
||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||
placeholder="Überschrift 1…"
|
||||
className="w-full bg-transparent text-2xl font-bold text-[#e3e3e3] focus:outline-none py-1"
|
||||
/>
|
||||
)}
|
||||
|
||||
{block.type === 'h2' && (
|
||||
<input
|
||||
type="text"
|
||||
value={block.content}
|
||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||
placeholder="Abschnitts-Überschrift…"
|
||||
className="w-full bg-transparent text-lg font-semibold text-[#8ab4f8] focus:outline-none pt-2 pb-1"
|
||||
/>
|
||||
)}
|
||||
|
||||
{block.type === 'paragraph' && (
|
||||
<textarea
|
||||
rows={Math.max(1, Math.ceil(block.content.length / 75))}
|
||||
value={block.content}
|
||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||
placeholder="Text schreiben…"
|
||||
className="w-full bg-transparent text-sm sm:text-base text-[#e3e3e3] leading-relaxed focus:outline-none resize-none"
|
||||
/>
|
||||
)}
|
||||
|
||||
{block.type === 'todo' && (
|
||||
<div className="flex items-center gap-3 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!!block.checked}
|
||||
onChange={(e) => updateBlock(block.id, { checked: e.target.checked })}
|
||||
className="w-4 h-4 rounded accent-[#81c995] cursor-pointer"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={block.content}
|
||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||
placeholder="To-Do Punkt…"
|
||||
className={`w-full bg-transparent text-sm sm:text-base focus:outline-none ${
|
||||
block.checked ? 'line-through text-[#5f6368]' : 'text-[#e3e3e3]'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{block.type === 'bullet' && (
|
||||
<div className="flex items-center gap-3 py-0.5">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[#8ab4f8] flex-shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
value={block.content}
|
||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||
placeholder="Listenpunkt…"
|
||||
className="w-full bg-transparent text-sm sm:text-base text-[#e3e3e3] focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{block.type === 'callout' && (
|
||||
<div className="p-4 rounded-2xl bg-[#131314] border border-[#8ab4f8]/30 flex items-start gap-3">
|
||||
<Sparkles className="w-4 h-4 text-[#8ab4f8] flex-shrink-0 mt-1" />
|
||||
<textarea
|
||||
rows={2}
|
||||
value={block.content}
|
||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||
placeholder="Wichtiger Hinweis oder Zusammenfassung…"
|
||||
className="w-full bg-transparent text-sm text-[#e3e3e3] focus:outline-none resize-none"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{block.type === 'code' && (
|
||||
<div className="p-4 rounded-2xl bg-[#0b0f19] gemini-border font-mono text-xs sm:text-sm text-[#81c995]">
|
||||
<textarea
|
||||
rows={Math.max(2, block.content.split('\n').length)}
|
||||
value={block.content}
|
||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||
placeholder="// Code oder Shell-Kommando…"
|
||||
className="w-full bg-transparent focus:outline-none resize-none"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Block Reorder & Delete Controls */}
|
||||
<div className="opacity-0 group-hover:opacity-100 flex items-center gap-1 transition-opacity pt-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => moveBlock(idx, -1)}
|
||||
className="p-1 rounded-full hover:bg-[#282a2c] text-[#9aa0a6]"
|
||||
title="Nach oben"
|
||||
>
|
||||
<ArrowUp className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => moveBlock(idx, 1)}
|
||||
className="p-1 rounded-full hover:bg-[#282a2c] text-[#9aa0a6]"
|
||||
title="Nach unten"
|
||||
>
|
||||
<ArrowDown className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeBlock(block.id)}
|
||||
className="p-1 rounded-full hover:bg-[#f28b82]/20 text-[#9aa0a6] hover:text-[#f28b82]"
|
||||
title="Block entfernen"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Add Block Pill Toolbar */}
|
||||
<div className="mt-8 pt-4 border-t border-white/[0.06] flex flex-wrap items-center gap-2">
|
||||
<span className="text-xs text-[#9aa0a6] mr-1">+ Block einfügen:</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => addBlock('paragraph')}
|
||||
className="h-8 px-3 rounded-full bg-[#131314] hover:bg-[#282a2c] gemini-border text-xs text-[#e3e3e3] flex items-center gap-1.5"
|
||||
>
|
||||
<AlignLeft className="w-3.5 h-3.5 text-[#8ab4f8]" /> Text
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => addBlock('h2')}
|
||||
className="h-8 px-3 rounded-full bg-[#131314] hover:bg-[#282a2c] gemini-border text-xs text-[#e3e3e3] flex items-center gap-1.5"
|
||||
>
|
||||
<Heading2 className="w-3.5 h-3.5 text-[#8ab4f8]" /> Überschrift
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => addBlock('todo')}
|
||||
className="h-8 px-3 rounded-full bg-[#131314] hover:bg-[#282a2c] gemini-border text-xs text-[#e3e3e3] flex items-center gap-1.5"
|
||||
>
|
||||
<CheckSquare className="w-3.5 h-3.5 text-[#81c995]" /> Checkliste
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => addBlock('bullet')}
|
||||
className="h-8 px-3 rounded-full bg-[#131314] hover:bg-[#282a2c] gemini-border text-xs text-[#e3e3e3] flex items-center gap-1.5"
|
||||
>
|
||||
<List className="w-3.5 h-3.5 text-[#8ab4f8]" /> Aufzählung
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => addBlock('callout')}
|
||||
className="h-8 px-3 rounded-full bg-[#131314] hover:bg-[#282a2c] gemini-border text-xs text-[#e3e3e3] flex items-center gap-1.5"
|
||||
>
|
||||
<Sparkles className="w-3.5 h-3.5 text-[#c58af9]" /> Callout
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => addBlock('code')}
|
||||
className="h-8 px-3 rounded-full bg-[#131314] hover:bg-[#282a2c] gemini-border text-xs text-[#e3e3e3] flex items-center gap-1.5"
|
||||
>
|
||||
<Code className="w-3.5 h-3.5 text-[#81c995]" /> Codeblock
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => addBlock('h1')}
|
||||
className="h-8 px-3 rounded-full bg-[#131314] hover:bg-[#282a2c] gemini-border text-xs text-[#e3e3e3] flex items-center gap-1.5"
|
||||
>
|
||||
<Heading1 className="w-3.5 h-3.5 text-[#8ab4f8]" /> H1
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-12 rounded-3xl bg-[#1e1f20] gemini-border text-center text-[#9aa0a6]">
|
||||
Wähle links ein Dokument aus oder erstelle ein neues Projekt.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user