feat(capture): support multiline thoughts and tasks with Shift+Enter in quick dump bar
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 53s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 53s
This commit is contained in:
1 parent
87efb98bc1
commit
e6a791bcca
4 files changed
+61
-16
No files matched your search
@@ -37,11 +37,29 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
||||
const [isListening, setIsListening] = useState(false);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [justSavedLabel, setJustSavedLabel] = useState<string | null>(null);
|
||||
const [isMultiline, setIsMultiline] = useState(false);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const containerRef = useRef<HTMLFormElement>(null);
|
||||
const recognitionRef = useRef<any>(null);
|
||||
|
||||
const adjustHeight = (el?: HTMLTextAreaElement | null) => {
|
||||
const targetEl = el || inputRef.current;
|
||||
if (!targetEl) return;
|
||||
targetEl.style.height = 'auto';
|
||||
if (targetEl.value) {
|
||||
const scrollH = targetEl.scrollHeight;
|
||||
targetEl.style.height = `${Math.min(scrollH, 140)}px`;
|
||||
setIsMultiline(targetEl.value.includes('\n') || scrollH > 28);
|
||||
} else {
|
||||
setIsMultiline(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
adjustHeight();
|
||||
}, [content]);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, [autoFocusTrigger]);
|
||||
@@ -77,6 +95,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
||||
inputRef.current?.focus();
|
||||
} else if (e.key === 'Escape' && document.activeElement === inputRef.current) {
|
||||
setContent('');
|
||||
setIsMultiline(false);
|
||||
setShowOptions(false);
|
||||
inputRef.current?.blur();
|
||||
}
|
||||
@@ -154,12 +173,16 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
||||
|
||||
// Optimistically clear input immediately so the UI feels instant (<10ms) in all browsers
|
||||
setContent('');
|
||||
setIsMultiline(false);
|
||||
setPriority('normal');
|
||||
setDueDate('');
|
||||
setTarget('inbox');
|
||||
setShowOptions(false);
|
||||
setIsSubmitting(true);
|
||||
triggerHaptic(15);
|
||||
if (inputRef.current) {
|
||||
inputRef.current.style.height = 'auto';
|
||||
}
|
||||
|
||||
const savedMsg =
|
||||
effectiveTarget === 'task'
|
||||
@@ -210,25 +233,36 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
||||
return (
|
||||
<div className="w-full">
|
||||
<form ref={containerRef} onSubmit={handleSubmit} className="relative">
|
||||
{/* Quick-Capture Input Bar (Radius controlled by --radius-pill) */}
|
||||
{/* Quick-Capture Input Bar (Radius controlled by --radius-pill / multiline) */}
|
||||
<div
|
||||
className={`flex items-center h-9 pl-1.5 pr-1.5 bg-panel border capture-pill rounded-full transition-colors duration-150 ${
|
||||
className={`flex ${
|
||||
isMultiline ? 'items-end py-1.5' : 'items-center h-9'
|
||||
} pl-1.5 pr-1.5 bg-panel border capture-pill ${
|
||||
isMultiline ? 'capture-bar-multiline' : 'rounded-full'
|
||||
} transition-all duration-150 ${
|
||||
isListening
|
||||
? 'border-accent bg-panel'
|
||||
: 'border-border focus-within:border-accent'
|
||||
}`}
|
||||
>
|
||||
<div className="w-6 h-6 capture-pill rounded-full bg-accent text-on-accent flex items-center justify-center mr-2 flex-shrink-0">
|
||||
<div
|
||||
className={`w-6 h-6 capture-pill rounded-full bg-accent text-on-accent flex items-center justify-center mr-2 flex-shrink-0 ${
|
||||
isMultiline ? 'self-start mt-1' : ''
|
||||
}`}
|
||||
>
|
||||
<Sparkles className="w-3 h-3" />
|
||||
</div>
|
||||
|
||||
<input
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
rows={1}
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setContent(e.target.value);
|
||||
adjustHeight(e.target);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
if (e.key === 'Enter' && (!e.shiftKey || e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
executeCapture();
|
||||
}
|
||||
@@ -237,18 +271,23 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
||||
isListening
|
||||
? 'Ich höre zu…'
|
||||
: target === 'task'
|
||||
? 'Neue Aufgabe direkt anlegen… (Enter)'
|
||||
? 'Neue Aufgabe direkt anlegen… (Enter / Shift+Enter für Mehrzeiler)'
|
||||
: target === 'list_item'
|
||||
? `Eintrag direkt zu "${selectedList?.title || 'Liste'}" hinzufügen…`
|
||||
: target === 'document'
|
||||
? 'Neue Workspace-Notiz direkt anlegen…'
|
||||
: 'Quick Dump: Gedanke einfach reinrotzen & später swipen… (C / Enter)'
|
||||
? 'Neue Workspace-Notiz direkt anlegen… (Enter / Shift+Enter für Mehrzeiler)'
|
||||
: 'Quick Dump: Gedanke reinrotzen… (C / Enter, Shift+Enter für Mehrzeiler)'
|
||||
}
|
||||
className="w-full bg-transparent text-main placeholder-faint text-xs sm:text-sm focus:outline-none"
|
||||
className="w-full bg-transparent text-main placeholder-faint text-xs sm:text-sm focus:outline-none resize-none leading-5 py-0.5 overflow-y-auto"
|
||||
style={{ minHeight: '22px', maxHeight: '140px' }}
|
||||
autoComplete="off"
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-1 ml-2 flex-shrink-0">
|
||||
<div
|
||||
className={`flex items-center gap-1 ml-2 flex-shrink-0 ${
|
||||
isMultiline ? 'self-end mb-0.5' : ''
|
||||
}`}
|
||||
>
|
||||
{/* Target & Options Toggle Pill */}
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -645,7 +645,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
className="panel-surface p-2.5 space-y-2 hover:border-strong transition-colors"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p className="text-xs font-medium text-main break-words flex-1 leading-snug">
|
||||
<p className="text-xs font-medium text-main break-words flex-1 leading-snug whitespace-pre-wrap">
|
||||
{task.content}
|
||||
</p>
|
||||
<button
|
||||
@@ -752,7 +752,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
className="bg-panel border border-accent-border p-2.5 space-y-2 hover:border-accent transition-colors"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p className="text-xs font-medium text-main break-words flex-1 leading-snug">
|
||||
<p className="text-xs font-medium text-main break-words flex-1 leading-snug whitespace-pre-wrap">
|
||||
{task.content}
|
||||
</p>
|
||||
<button
|
||||
|
||||
@@ -400,7 +400,7 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-base sm:text-xl font-medium text-main leading-relaxed break-words">
|
||||
<p className="text-base sm:text-xl font-medium text-main leading-relaxed break-words whitespace-pre-wrap">
|
||||
{activeCard.content}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -84,6 +84,12 @@ body {
|
||||
border-radius: 9999px !important;
|
||||
}
|
||||
|
||||
/* Quick-Capture-Blase bei mehrzeiliger Eingabe (Shift+Enter) */
|
||||
.capture-bar-multiline,
|
||||
.capture-bar-multiline.bg-panel.border {
|
||||
border-radius: 18px !important;
|
||||
}
|
||||
|
||||
/* Aufgaben-Checkboxen */
|
||||
.checkbox-square {
|
||||
border-radius: var(--radius-checkbox);
|
||||
|
||||
Reference in new issue
Block a user