fix: auto-title button always visible on hover + wired to AI panel Wand button, title text-4xl/5xl no overflow, content max-w-3xl
This commit is contained in:
@@ -97,6 +97,8 @@ interface ContextualAIChatProps {
|
|||||||
className?: string
|
className?: string
|
||||||
/** How to embed generated diagram images (markdown vs rich text HTML) */
|
/** How to embed generated diagram images (markdown vs rich text HTML) */
|
||||||
diagramInsertFormat?: 'markdown' | 'html'
|
diagramInsertFormat?: 'markdown' | 'html'
|
||||||
|
/** Called to trigger AI title generation for the note */
|
||||||
|
onGenerateTitle?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Component ─────────────────────────────────────────────────────────────────
|
// ── Component ─────────────────────────────────────────────────────────────────
|
||||||
@@ -113,6 +115,7 @@ export function ContextualAIChat({
|
|||||||
notebooks = [],
|
notebooks = [],
|
||||||
className,
|
className,
|
||||||
diagramInsertFormat = 'markdown',
|
diagramInsertFormat = 'markdown',
|
||||||
|
onGenerateTitle,
|
||||||
}: ContextualAIChatProps) {
|
}: ContextualAIChatProps) {
|
||||||
const { t, language } = useLanguage()
|
const { t, language } = useLanguage()
|
||||||
const webSearchAvailable = useWebSearchAvailable()
|
const webSearchAvailable = useWebSearchAvailable()
|
||||||
@@ -513,8 +516,7 @@ export function ContextualAIChat({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={cn(
|
<aside className={cn(
|
||||||
'border-l border-border/40 bg-card flex flex-col self-stretch flex-shrink-0 z-10 transition-all duration-300',
|
'border-l border-border/40 bg-background flex flex-col h-full w-full flex-shrink-0 z-10 transition-all duration-300',
|
||||||
expanded ? 'w-[560px]' : 'w-[360px]',
|
|
||||||
className,
|
className,
|
||||||
)}>
|
)}>
|
||||||
|
|
||||||
@@ -541,16 +543,16 @@ export function ContextualAIChat({
|
|||||||
<RotateCcw className="h-3.5 w-3.5" />
|
<RotateCcw className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
{onGenerateTitle && (
|
||||||
variant="ghost" size="icon"
|
<Button
|
||||||
className="h-7 w-7 text-muted-foreground hover:text-foreground"
|
variant="ghost" size="icon"
|
||||||
onClick={() => setExpanded(e => !e)}
|
className="h-7 w-7 text-muted-foreground hover:text-amber-500 hover:bg-amber-50 dark:hover:bg-amber-950/30"
|
||||||
title={expanded ? t('ai.shrinkPanel') : t('ai.expandPanel')}
|
onClick={onGenerateTitle}
|
||||||
>
|
title="Générer un titre automatique"
|
||||||
{expanded
|
>
|
||||||
? <Minimize2 className="h-3.5 w-3.5" />
|
<Wand2 className="h-3.5 w-3.5" />
|
||||||
: <Maximize2 className="h-3.5 w-3.5" />}
|
</Button>
|
||||||
</Button>
|
)}
|
||||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-7 w-7 text-muted-foreground hover:text-foreground">
|
<Button variant="ghost" size="icon" onClick={onClose} className="h-7 w-7 text-muted-foreground hover:text-foreground">
|
||||||
<X className="h-3.5 w-3.5" />
|
<X className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -793,13 +793,13 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false }
|
|||||||
onChange={(e) => { setTitle(e.target.value); setIsDirty(true); setDismissedTitleSuggestions(true) }}
|
onChange={(e) => { setTitle(e.target.value); setIsDirty(true); setDismissedTitleSuggestions(true) }}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full text-5xl md:text-6xl 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',
|
||||||
!readOnly && 'pr-14'
|
!readOnly && 'pr-12'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{/* AI title generation — shown when title is empty */}
|
{/* AI title generation — always visible on hover */}
|
||||||
{!title && !readOnly && (
|
{!readOnly && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
@@ -821,7 +821,7 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = 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-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"
|
||||||
title="Generate AI title"
|
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" />}
|
||||||
</button>
|
</button>
|
||||||
@@ -849,8 +849,8 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false }
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Content area — max-w-2xl, like prototype */}
|
{/* Content area — max-w-3xl for wider reading column */}
|
||||||
<div className="max-w-2xl mx-auto pb-32">
|
<div className="max-w-3xl mx-auto w-full pb-32">
|
||||||
{noteType === 'richtext' ? (
|
{noteType === 'richtext' ? (
|
||||||
<div className="fullpage-editor">
|
<div className="fullpage-editor">
|
||||||
<RichTextEditor
|
<RichTextEditor
|
||||||
@@ -882,7 +882,7 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false }
|
|||||||
onFocus={() => setShowMarkdownPreview(false)}
|
onFocus={() => setShowMarkdownPreview(false)}
|
||||||
onChange={(e) => { setContent(e.target.value); setIsDirty(true) }}
|
onChange={(e) => { setContent(e.target.value); setIsDirty(true) }}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
className="w-full min-h-[280px] border-0 outline-none px-0 bg-transparent text-lg leading-relaxed font-light resize-none overflow-hidden placeholder:text-foreground/20 text-foreground/80"
|
className="w-full min-h-[280px] border-0 outline-none px-0 bg-transparent text-xl leading-relaxed resize-none overflow-hidden placeholder:text-foreground/30 text-foreground"
|
||||||
/>
|
/>
|
||||||
{!readOnly && (
|
{!readOnly && (
|
||||||
<MarkdownSlashCommands
|
<MarkdownSlashCommands
|
||||||
@@ -916,10 +916,28 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false }
|
|||||||
lastActionApplied={previousContentForCopilot !== null}
|
lastActionApplied={previousContentForCopilot !== null}
|
||||||
notebooks={notebooks.map(nb => ({ id: nb.id, name: nb.name }))}
|
notebooks={notebooks.map(nb => ({ id: nb.id, name: nb.name }))}
|
||||||
diagramInsertFormat={noteType === 'richtext' ? 'html' : 'markdown'}
|
diagramInsertFormat={noteType === 'richtext' ? 'html' : 'markdown'}
|
||||||
|
onGenerateTitle={async () => {
|
||||||
|
const plain = content.replace(/<[^>]+>/g, ' ').trim()
|
||||||
|
if (plain.split(/\s+/).filter(Boolean).length < 3) return
|
||||||
|
setIsProcessingAI(true)
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/ai/title-suggestions', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ content: plain }),
|
||||||
|
})
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json()
|
||||||
|
const s = data.title || data.suggestedTitle || (data.suggestions?.[0]?.title ?? '')
|
||||||
|
if (s) { setTitle(s); setIsDirty(true) }
|
||||||
|
}
|
||||||
|
} catch {} finally { setIsProcessingAI(false) }
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
{/* ── Side panel: Document Info ── */}
|
{/* ── Side panel: Document Info ── */}
|
||||||
{infoOpen && (
|
{infoOpen && (
|
||||||
<div className="w-[400px] h-full self-stretch border-l border-black/10 dark:border-white/10 bg-background flex flex-col z-50 shrink-0">
|
<div className="w-[400px] h-full self-stretch border-l border-black/10 dark:border-white/10 bg-background flex flex-col z-50 shrink-0">
|
||||||
|
|||||||
Reference in New Issue
Block a user