feat: complete workspace, triage, outliner and sharing overhaul (tasks 1-26)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
This commit is contained in:
1 parent
df2f2bac67
commit
d39d1d1122
17 files changed
+1173
-501
No files matched your search
@@ -13,6 +13,7 @@ import {
|
||||
Minimize2,
|
||||
Sparkles,
|
||||
Plus,
|
||||
Search,
|
||||
} from 'lucide-react';
|
||||
import { Document, DocumentBlock, User } from '../../lib/types';
|
||||
import { api, getToken } from '../../lib/api';
|
||||
@@ -36,7 +37,63 @@ export interface DocumentEditorProps {
|
||||
onSaveStatusChange?: (status: DocSaveStatus, errorMsg?: string) => void;
|
||||
}
|
||||
|
||||
const EMOJI_PALETTE = ['📝', '📄', '💡', '🚀', '📌', '📂', '🧠', '⚡', '🎯', '🛠️', '📊', '🔒'];
|
||||
interface EmojiItem {
|
||||
emoji: string;
|
||||
category: string;
|
||||
keywords: string;
|
||||
}
|
||||
|
||||
const EMOJI_CATALOG: EmojiItem[] = [
|
||||
// Notizen & Dokumente
|
||||
{ emoji: '📝', category: 'Notizen', keywords: 'notiz schreiben memo text note' },
|
||||
{ emoji: '📄', category: 'Notizen', keywords: 'dokument seite page doc' },
|
||||
{ emoji: '📋', category: 'Notizen', keywords: 'clipboard klemmbrett liste' },
|
||||
{ emoji: '📂', category: 'Notizen', keywords: 'ordner mappe folder datei' },
|
||||
{ emoji: '📁', category: 'Notizen', keywords: 'ordner folder directory' },
|
||||
{ emoji: '📌', category: 'Notizen', keywords: 'pin anheften bookmark wichtig' },
|
||||
{ emoji: '📍', category: 'Notizen', keywords: 'standort ort pin location' },
|
||||
{ emoji: '🏷️', category: 'Notizen', keywords: 'tag etikett label markierung' },
|
||||
{ emoji: '📚', category: 'Notizen', keywords: 'buch bibliothek lesen studium' },
|
||||
{ emoji: '📖', category: 'Notizen', keywords: 'buch open book wissen' },
|
||||
|
||||
// Ideen & Fokus
|
||||
{ emoji: '🧠', category: 'Ideen', keywords: 'gehirn brain denken wissen thought mind' },
|
||||
{ emoji: '💡', category: 'Ideen', keywords: 'idee inspiration lampe glühbirne idea' },
|
||||
{ emoji: '🎯', category: 'Ideen', keywords: 'ziel target fokus mission okr' },
|
||||
{ emoji: '🚀', category: 'Ideen', keywords: 'rakete start launch projekt rocket' },
|
||||
{ emoji: '⚡', category: 'Ideen', keywords: 'blitz energie schnell quick power' },
|
||||
{ emoji: '🔥', category: 'Ideen', keywords: 'feuer flame priorität hot wichtig' },
|
||||
{ emoji: '⭐', category: 'Ideen', keywords: 'stern favoriten star top' },
|
||||
{ emoji: '✨', category: 'Ideen', keywords: 'funkeln magie neu neuigkeit sparkles' },
|
||||
{ emoji: '🧩', category: 'Ideen', keywords: 'puzzle teil lösung problem' },
|
||||
{ emoji: '🔬', category: 'Ideen', keywords: 'mikroskop forschung analyse test' },
|
||||
|
||||
// Tech & Dev
|
||||
{ emoji: '💻', category: 'Tech', keywords: 'laptop computer dev code programming' },
|
||||
{ emoji: '🖥️', category: 'Tech', keywords: 'desktop monitor bildschirm workstation' },
|
||||
{ emoji: '🛠️', category: 'Tech', keywords: 'werkzeug tools optionen settings einstellungen' },
|
||||
{ emoji: '⚙️', category: 'Tech', keywords: 'zahnrad config settings system' },
|
||||
{ emoji: '📊', category: 'Tech', keywords: 'diagramm chart daten statistik dashboard' },
|
||||
{ emoji: '📈', category: 'Tech', keywords: 'wachstum trend kurve business' },
|
||||
{ emoji: '🔒', category: 'Tech', keywords: 'schloss sicherheit geheim privat secure' },
|
||||
{ emoji: '🔑', category: 'Tech', keywords: 'schlüssel key passwort access' },
|
||||
{ emoji: '🌐', category: 'Tech', keywords: 'web internet network welt globus' },
|
||||
{ emoji: '📦', category: 'Tech', keywords: 'paket box release lieferung package' },
|
||||
|
||||
// Alltag & Organisation
|
||||
{ emoji: '💬', category: 'Alltag', keywords: 'chat nachricht kommentar call sprechen' },
|
||||
{ emoji: '✉️', category: 'Alltag', keywords: 'mail email brief post nachricht' },
|
||||
{ emoji: '📅', category: 'Alltag', keywords: 'kalender datum termin plan event' },
|
||||
{ emoji: '⏰', category: 'Alltag', keywords: 'wecker uhr zeit deadline alarm' },
|
||||
{ emoji: '🛒', category: 'Alltag', keywords: 'warenkorb einkauf shopping liste' },
|
||||
{ emoji: '☕', category: 'Alltag', keywords: 'kaffee pause bistro morning' },
|
||||
{ emoji: '🏠', category: 'Alltag', keywords: 'haus heim home wohnung' },
|
||||
{ emoji: '🏢', category: 'Alltag', keywords: 'büro firma arbeit office work' },
|
||||
{ emoji: '✈️', category: 'Alltag', keywords: 'flugzeug reise urlaub trip travel' },
|
||||
{ emoji: '🎨', category: 'Alltag', keywords: 'kunst farbe design creative' },
|
||||
{ emoji: '🏆', category: 'Alltag', keywords: 'pokal erfolg win trophy award' },
|
||||
{ emoji: '✅', category: 'Alltag', keywords: 'haken check done erledigt' },
|
||||
];
|
||||
|
||||
function ensureNonEmptyBlocks(docId: string, blocks?: DocumentBlock[]): DocumentBlock[] {
|
||||
if (!blocks || blocks.length === 0) {
|
||||
@@ -162,8 +219,17 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
||||
const [loading, setLoading] = useState<boolean>(!initialDoc);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState<boolean>(false);
|
||||
const [emojiSearch, setEmojiSearch] = useState<string>('');
|
||||
const [zoomedBlockId, setZoomedBlockId] = useState<string | null>(null);
|
||||
|
||||
const filteredEmojis = useMemo(() => {
|
||||
const q = emojiSearch.trim().toLowerCase();
|
||||
if (!q) return EMOJI_CATALOG;
|
||||
return EMOJI_CATALOG.filter(
|
||||
(item) => item.keywords.includes(q) || item.emoji.includes(q) || item.category.toLowerCase().includes(q)
|
||||
);
|
||||
}, [emojiSearch]);
|
||||
|
||||
// AutoSave state (REST persistence)
|
||||
const [, setSaveStatus] = useState<DocSaveStatus>('saved');
|
||||
const [, setSaveErrorMessage] = useState<string | null>(null);
|
||||
@@ -172,7 +238,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
||||
const lastSavedSnapshotRef = useRef<string>('');
|
||||
const isInitialLoadRef = useRef<boolean>(true);
|
||||
|
||||
const titleInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const titleInputRef = useRef<HTMLTextAreaElement | HTMLInputElement | null>(null);
|
||||
const docRef = useRef<Document | null>(doc);
|
||||
docRef.current = doc;
|
||||
|
||||
@@ -416,7 +482,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
||||
onDocMetadataChange(updatedDoc);
|
||||
}, [blocks, extractedTags, title]);
|
||||
|
||||
const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
if (!canEdit) return;
|
||||
setTitle(e.target.value);
|
||||
};
|
||||
@@ -896,31 +962,81 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
||||
{doc?.icon || '📝'}
|
||||
</button>
|
||||
{showEmojiPicker && canEdit && (
|
||||
<div className="absolute left-0 mt-1 p-2 panel-surface shadow-panel border border-border rounded-card grid grid-cols-6 gap-1 z-30 w-52">
|
||||
{EMOJI_PALETTE.map((emoji) => (
|
||||
<div
|
||||
className="absolute left-0 mt-1 p-2 panel-surface shadow-lg border border-border rounded-card z-30 w-72 space-y-2"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="relative">
|
||||
<Search className="w-3.5 h-3.5 text-faint absolute left-2 top-1/2 -translate-y-1/2" />
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
value={emojiSearch}
|
||||
onChange={(e) => setEmojiSearch(e.target.value)}
|
||||
placeholder="Icon suchen oder Emoji eingeben…"
|
||||
className="input-field w-full pl-7 pr-2 py-1 text-xs"
|
||||
/>
|
||||
</div>
|
||||
{emojiSearch.trim() && (
|
||||
<button
|
||||
key={emoji}
|
||||
type="button"
|
||||
onClick={() => handleSelectIcon(emoji)}
|
||||
className="p-1.5 rounded-btn hover:bg-panel-elevated text-base flex items-center justify-center cursor-pointer"
|
||||
onClick={() => {
|
||||
handleSelectIcon(emojiSearch.trim());
|
||||
setShowEmojiPicker(false);
|
||||
setEmojiSearch('');
|
||||
}}
|
||||
className="w-full text-left px-2 py-1 bg-accent/10 hover:bg-accent/20 text-accent rounded text-xs font-medium flex items-center justify-between cursor-pointer"
|
||||
>
|
||||
{emoji}
|
||||
<span>Eingabe als Icon nutzen</span>
|
||||
<span className="text-base">{emojiSearch.trim()}</span>
|
||||
</button>
|
||||
))}
|
||||
)}
|
||||
<div className="max-h-48 overflow-y-auto grid grid-cols-6 gap-1 pr-1">
|
||||
{filteredEmojis.map((item) => (
|
||||
<button
|
||||
key={item.emoji}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
handleSelectIcon(item.emoji);
|
||||
setShowEmojiPicker(false);
|
||||
setEmojiSearch('');
|
||||
}}
|
||||
className="p-1.5 rounded-btn hover:bg-panel-elevated text-lg flex items-center justify-center cursor-pointer transition-colors"
|
||||
title={item.keywords}
|
||||
>
|
||||
{item.emoji}
|
||||
</button>
|
||||
))}
|
||||
{filteredEmojis.length === 0 && (
|
||||
<div className="col-span-6 text-center text-xs text-faint py-3">
|
||||
Kein passendes Standard-Icon
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<input
|
||||
ref={titleInputRef}
|
||||
type="text"
|
||||
<textarea
|
||||
ref={(el) => {
|
||||
titleInputRef.current = el;
|
||||
if (el) {
|
||||
el.style.height = 'auto';
|
||||
el.style.height = `${el.scrollHeight}px`;
|
||||
}
|
||||
}}
|
||||
rows={1}
|
||||
value={title}
|
||||
readOnly={!canEdit}
|
||||
autoFocus={autoFocusTitle}
|
||||
onChange={handleTitleChange}
|
||||
onChange={(e) => {
|
||||
handleTitleChange(e);
|
||||
e.target.style.height = 'auto';
|
||||
e.target.style.height = `${e.target.scrollHeight}px`;
|
||||
}}
|
||||
onBlur={handleTitleBlur}
|
||||
placeholder="Titel der Notiz…"
|
||||
className="flex-1 min-w-0 text-xl sm:text-2xl font-bold text-main bg-transparent border-0 focus:outline-none focus:ring-0 tracking-tight"
|
||||
className="flex-1 min-w-0 text-xl sm:text-2xl font-bold text-main bg-transparent border-0 focus:outline-none focus:ring-0 tracking-tight resize-none leading-snug whitespace-pre-wrap break-words [overflow-wrap:anywhere]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in new issue
Block a user