ux: textarea auto-grow, preview toggle in toolbar, prose-lg on richtext, no more bottom preview btn
This commit is contained in:
@@ -258,6 +258,14 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false }
|
|||||||
return () => document.removeEventListener('paste', handlePaste, { capture: true } as any)
|
return () => document.removeEventListener('paste', handlePaste, { capture: true } as any)
|
||||||
}, [t])
|
}, [t])
|
||||||
|
|
||||||
|
// Auto-grow textarea as content grows
|
||||||
|
useEffect(() => {
|
||||||
|
const el = textareaRef.current
|
||||||
|
if (!el) return
|
||||||
|
el.style.height = 'auto'
|
||||||
|
el.style.height = Math.max(el.scrollHeight, 280) + 'px'
|
||||||
|
}, [content])
|
||||||
|
|
||||||
const handleRemoveImage = (index: number) => {
|
const handleRemoveImage = (index: number) => {
|
||||||
const removedUrl = images[index]
|
const removedUrl = images[index]
|
||||||
setImages(images.filter((_, i) => i !== index))
|
setImages(images.filter((_, i) => i !== index))
|
||||||
@@ -700,6 +708,23 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false }
|
|||||||
compact
|
compact
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Preview toggle — only for text/markdown, in toolbar where it's visible */}
|
||||||
|
{(noteType === 'text' || noteType === 'markdown') && !readOnly && (
|
||||||
|
<button
|
||||||
|
onClick={() => setShowMarkdownPreview(v => !v)}
|
||||||
|
title={showMarkdownPreview ? 'Revenir à l\'édition' : 'Prévisualiser le rendu'}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-2 px-3 py-1.5 rounded-full border transition-all duration-300 text-xs font-medium',
|
||||||
|
showMarkdownPreview
|
||||||
|
? 'bg-foreground text-background border-foreground'
|
||||||
|
: 'border-black/20 dark:border-white/20 text-foreground hover:bg-black/5 dark:hover:bg-white/5'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Eye size={16} />
|
||||||
|
<span>{showMarkdownPreview ? 'Éditer' : 'Aperçu'}</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* AI — rounded-full, exact prototype style */}
|
{/* AI — rounded-full, exact prototype style */}
|
||||||
<button
|
<button
|
||||||
onClick={() => { setAiOpen(v => !v); setInfoOpen(false) }}
|
onClick={() => { setAiOpen(v => !v); setInfoOpen(false) }}
|
||||||
@@ -811,23 +836,25 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false }
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Content area — max-w-2xl, like prototype */}
|
{/* Content area — max-w-2xl, like prototype */}
|
||||||
<div className="max-w-2xl mx-auto space-y-8 pb-32">
|
<div className="max-w-2xl mx-auto pb-32">
|
||||||
{noteType === 'richtext' ? (
|
{noteType === 'richtext' ? (
|
||||||
<RichTextEditor
|
<div className="prose prose-lg dark:prose-invert max-w-none [&_.notion-editor]:min-h-[280px]">
|
||||||
content={content}
|
<RichTextEditor
|
||||||
onChange={(v) => { setContent(v); setIsDirty(true) }}
|
content={content}
|
||||||
className="min-h-[60vh] text-lg font-light leading-relaxed text-foreground/80"
|
onChange={(v) => { setContent(v); setIsDirty(true) }}
|
||||||
onImageUpload={uploadImageFile}
|
className="min-h-[280px] text-lg font-light leading-relaxed"
|
||||||
/>
|
onImageUpload={uploadImageFile}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
) : (noteType === 'markdown' || noteType === 'text') && showMarkdownPreview ? (
|
) : (noteType === 'markdown' || noteType === 'text') && showMarkdownPreview ? (
|
||||||
<div
|
<div
|
||||||
className="min-h-[300px] cursor-text prose prose-lg dark:prose-invert max-w-none leading-relaxed"
|
className="min-h-[280px] cursor-text prose prose-lg dark:prose-invert max-w-none leading-relaxed"
|
||||||
onClick={() => !readOnly && setShowMarkdownPreview(false)}
|
onClick={() => !readOnly && setShowMarkdownPreview(false)}
|
||||||
>
|
>
|
||||||
<MarkdownContent content={content} />
|
<MarkdownContent content={content} />
|
||||||
{!readOnly && (
|
{!readOnly && (
|
||||||
<p className="text-[11px] text-foreground/30 mt-6 select-none not-prose">
|
<p className="text-[11px] text-foreground/30 mt-8 select-none not-prose italic">
|
||||||
Click to edit
|
Cliquez pour éditer
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -836,22 +863,13 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false }
|
|||||||
<textarea
|
<textarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
dir="auto"
|
dir="auto"
|
||||||
placeholder={t('notes.takeNote') || "Type '/' for commands…"}
|
placeholder={t('notes.takeNote') || "Commencez à écrire… tapez '/' pour les commandes"}
|
||||||
value={content}
|
value={content}
|
||||||
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-[60vh] border-0 outline-none px-0 bg-transparent text-lg leading-relaxed font-light resize-none placeholder:text-foreground/20 text-foreground/80"
|
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"
|
||||||
/>
|
/>
|
||||||
{content && !readOnly && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowMarkdownPreview(true)}
|
|
||||||
className="mt-2 text-[11px] text-foreground/40 hover:text-foreground flex items-center gap-1.5 transition-colors"
|
|
||||||
>
|
|
||||||
<Eye className="h-3 w-3" /> Preview
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{!readOnly && (
|
{!readOnly && (
|
||||||
<MarkdownSlashCommands
|
<MarkdownSlashCommands
|
||||||
textareaRef={textareaRef as React.RefObject<HTMLTextAreaElement>}
|
textareaRef={textareaRef as React.RefObject<HTMLTextAreaElement>}
|
||||||
|
|||||||
Reference in New Issue
Block a user