fix: title textarea auto-resize (no overflow), auto-title 10-word check + toast feedback, AI expand = fixed overlay (no compress)
This commit is contained in:
@@ -515,11 +515,21 @@ export function ContextualAIChat({
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={cn(
|
<>
|
||||||
'border-l border-border/40 bg-background flex flex-col h-full flex-shrink-0 z-10 transition-all duration-300',
|
{/* Backdrop when expanded */}
|
||||||
expanded ? 'w-[560px]' : 'w-[360px]',
|
{expanded && (
|
||||||
className,
|
<div
|
||||||
)}>
|
className="fixed inset-0 z-[199] bg-black/10 dark:bg-black/20 backdrop-blur-[1px]"
|
||||||
|
onClick={() => setExpanded(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<aside className={cn(
|
||||||
|
'border-l border-border/40 bg-background flex flex-col flex-shrink-0 z-10 transition-all duration-300',
|
||||||
|
expanded
|
||||||
|
? 'fixed right-0 top-0 h-screen w-[640px] z-[200] shadow-2xl border-l'
|
||||||
|
: 'h-full w-[360px]',
|
||||||
|
!expanded && className,
|
||||||
|
)}>
|
||||||
|
|
||||||
{/* ── Header ─────────────────────────────────────────── */}
|
{/* ── Header ─────────────────────────────────────────── */}
|
||||||
<div className="px-5 pt-5 pb-4 border-b border-border/40 shrink-0">
|
<div className="px-5 pt-5 pb-4 border-b border-border/40 shrink-0">
|
||||||
@@ -1386,6 +1396,7 @@ export function ContextualAIChat({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</aside>
|
</aside>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -783,18 +783,23 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false }
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Title — editable input styled as h1 */}
|
{/* Title — auto-resizing textarea to prevent overflow */}
|
||||||
<div className="group relative">
|
<div className="group relative">
|
||||||
<input
|
<textarea
|
||||||
dir="auto"
|
dir="auto"
|
||||||
type="text"
|
rows={1}
|
||||||
placeholder={t('notes.titlePlaceholder') || 'Untitled…'}
|
placeholder={t('notes.titlePlaceholder') || 'Untitled…'}
|
||||||
value={title}
|
value={title}
|
||||||
onChange={(e) => { setTitle(e.target.value); setIsDirty(true); setDismissedTitleSuggestions(true) }}
|
onChange={(e) => { setTitle(e.target.value); setIsDirty(true); setDismissedTitleSuggestions(true) }}
|
||||||
|
onInput={(e) => {
|
||||||
|
const el = e.currentTarget
|
||||||
|
el.style.height = 'auto'
|
||||||
|
el.style.height = el.scrollHeight + 'px'
|
||||||
|
}}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full text-4xl md:text-5xl font-memento-serif font-bold border-0 outline-none px-0 bg-transparent text-foreground leading-tight',
|
'w-full text-4xl md:text-5xl font-memento-serif font-bold border-0 outline-none px-0 bg-transparent text-foreground leading-tight',
|
||||||
'placeholder:text-foreground/20',
|
'placeholder:text-foreground/20 resize-none overflow-hidden',
|
||||||
!readOnly && 'pr-12'
|
!readOnly && 'pr-12'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -804,7 +809,11 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false }
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const plain = content.replace(/<[^>]+>/g, ' ').trim()
|
const plain = content.replace(/<[^>]+>/g, ' ').trim()
|
||||||
if (plain.split(/\s+/).filter(Boolean).length < 3) return
|
const wordCount = plain.split(/\s+/).filter(Boolean).length
|
||||||
|
if (wordCount < 10) {
|
||||||
|
toast.error('Ajoutez au moins 10 mots avant de générer un titre.')
|
||||||
|
return
|
||||||
|
}
|
||||||
setIsProcessingAI(true)
|
setIsProcessingAI(true)
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/ai/title-suggestions', {
|
const res = await fetch('/api/ai/title-suggestions', {
|
||||||
@@ -814,13 +823,21 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false }
|
|||||||
})
|
})
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
const s = data.title || data.suggestedTitle || (data.suggestions?.[0]?.title ?? '')
|
const s = data.suggestions?.[0]?.title ?? ''
|
||||||
if (s) { setTitle(s); setIsDirty(true) }
|
if (s) {
|
||||||
|
setTitle(s)
|
||||||
|
setIsDirty(true)
|
||||||
|
toast.success('Titre généré !')
|
||||||
|
} else {
|
||||||
|
toast.error('Impossible de générer un titre.')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.error('Erreur lors de la génération du titre.')
|
||||||
}
|
}
|
||||||
} catch {} finally { setIsProcessingAI(false) }
|
} catch { toast.error('Erreur réseau.') } finally { setIsProcessingAI(false) }
|
||||||
}}
|
}}
|
||||||
disabled={isProcessingAI}
|
disabled={isProcessingAI}
|
||||||
className="absolute right-0 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-60 hover:!opacity-100 transition-opacity rounded-lg p-2 text-foreground/50 hover:bg-black/5"
|
className="absolute right-0 top-2 opacity-0 group-hover:opacity-60 hover:!opacity-100 transition-opacity rounded-lg p-2 text-foreground/50 hover:bg-black/5"
|
||||||
title="Générer un titre automatique avec l'IA"
|
title="Générer un titre automatique avec l'IA"
|
||||||
>
|
>
|
||||||
{isProcessingAI ? <Loader2 className="h-5 w-5 animate-spin" /> : <Sparkles className="h-5 w-5" />}
|
{isProcessingAI ? <Loader2 className="h-5 w-5 animate-spin" /> : <Sparkles className="h-5 w-5" />}
|
||||||
@@ -918,7 +935,11 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false }
|
|||||||
diagramInsertFormat={noteType === 'richtext' ? 'html' : 'markdown'}
|
diagramInsertFormat={noteType === 'richtext' ? 'html' : 'markdown'}
|
||||||
onGenerateTitle={async () => {
|
onGenerateTitle={async () => {
|
||||||
const plain = content.replace(/<[^>]+>/g, ' ').trim()
|
const plain = content.replace(/<[^>]+>/g, ' ').trim()
|
||||||
if (plain.split(/\s+/).filter(Boolean).length < 3) return
|
const wordCount = plain.split(/\s+/).filter(Boolean).length
|
||||||
|
if (wordCount < 10) {
|
||||||
|
toast.error('Ajoutez au moins 10 mots avant de générer un titre.')
|
||||||
|
return
|
||||||
|
}
|
||||||
setIsProcessingAI(true)
|
setIsProcessingAI(true)
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/ai/title-suggestions', {
|
const res = await fetch('/api/ai/title-suggestions', {
|
||||||
@@ -928,10 +949,18 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false }
|
|||||||
})
|
})
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
const s = data.title || data.suggestedTitle || (data.suggestions?.[0]?.title ?? '')
|
const s = data.suggestions?.[0]?.title ?? ''
|
||||||
if (s) { setTitle(s); setIsDirty(true) }
|
if (s) {
|
||||||
|
setTitle(s)
|
||||||
|
setIsDirty(true)
|
||||||
|
toast.success('Titre généré !')
|
||||||
|
} else {
|
||||||
|
toast.error('Impossible de générer un titre.')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.error('Erreur lors de la génération du titre.')
|
||||||
}
|
}
|
||||||
} catch {} finally { setIsProcessingAI(false) }
|
} catch { toast.error('Erreur réseau.') } finally { setIsProcessingAI(false) }
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user