feat: standardize UI theme, fix dark mode consistency, and implement editorial tags
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m24s

This commit is contained in:
Antigravity
2026-05-10 18:43:13 +00:00
parent f6880bd0e1
commit 330c0c61b6
25 changed files with 640 additions and 503 deletions

View File

@@ -7,7 +7,6 @@ import { LabelBadge } from '@/components/label-badge'
import { GhostTags } from '@/components/ghost-tags'
import { EditorImages } from '@/components/editor-images'
import { TitleSuggestions } from '@/components/title-suggestions'
import { NoteTypeSelector } from '@/components/note-type-selector'
import {
DropdownMenu,
DropdownMenuContent,
@@ -44,32 +43,28 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
const notebookName = notebooks.find(nb => nb.id === note.notebookId)?.name || null
// Snapshot for undo — stored in a ref so the toast callback isn't a stale closure
const undoSnapshotRef = useRef<{ content: string; noteType: string } | null>(null)
const undoSnapshotRef = useRef<{ content: string; isMarkdown: boolean } | null>(null)
const handleConvertToRichtext = async () => {
if (isConverting || !state.content.trim()) return
setIsConverting(true)
// Capture snapshot BEFORE converting
const snapshot = { content: state.content, noteType: state.noteType }
const snapshot = { content: state.content, isMarkdown: state.isMarkdown }
undoSnapshotRef.current = snapshot
try {
let html: string
if (state.noteType === 'markdown') {
// Proper markdown → HTML via marked (no AI needed)
if (state.isMarkdown) {
const { marked } = await import('marked')
html = await marked(state.content, { async: false }) as string
} else {
// Plain text → wrap paragraphs in <p> tags
html = state.content
.split(/\n{2,}/)
.map(para => `<p>${para.trim().replace(/\n/g, '<br />')}</p>`)
.join('')
}
actions.setContent(html)
actions.setNoteType('richtext')
actions.setIsMarkdown(false)
toast.success(t('notes.convertedToRichText') || 'Converted to rich text', {
duration: 8000,
@@ -79,7 +74,7 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
const snap = undoSnapshotRef.current
if (!snap) return
actions.setContent(snap.content)
actions.setNoteType(snap.noteType as any)
if (snap.isMarkdown) actions.setIsMarkdown(true)
undoSnapshotRef.current = null
toast.info(t('ai.undoApplied') || 'Conversion undone')
},
@@ -94,8 +89,7 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
if (mode === 'fullPage') {
return (
<div className="px-12 py-8 flex items-center justify-between sticky top-0 bg-white/95 dark:bg-zinc-950/95 backdrop-blur-sm z-40 border-b border-border dark:border-white/10">
{/* Left: back */}
<div className="px-12 py-8 flex items-center justify-between sticky top-0 bg-white/95 dark:bg-background/95 backdrop-blur-sm z-40 border-b border-border dark:border-white/10">
<button
onClick={onClose}
className="flex items-center gap-2 text-foreground hover:opacity-60 transition-opacity"
@@ -104,9 +98,7 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
<span className="text-sm font-medium">Back to collection</span>
</button>
{/* Right: status + type + AI + Info */}
<div className="flex items-center gap-4">
{/* Save status */}
<span className="hidden sm:flex items-center gap-1.5 text-[11px] text-foreground/40 select-none">
{state.isSaving
? <><Loader2 className="h-3 w-3 animate-spin" /><span>Saving</span></>
@@ -115,15 +107,7 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
: <><Check className="h-3 w-3 text-emerald-500" /><span>Saved</span></>}
</span>
{/* Note type */}
<NoteTypeSelector
value={state.noteType}
onChange={(newType) => { actions.setNoteType(newType); actions.setIsDirty(true) }}
compact
/>
{/* Preview toggle — icon only */}
{(state.noteType === 'text' || state.noteType === 'markdown') && !readOnly && (
{state.isMarkdown && !readOnly && (
<button
title={state.showMarkdownPreview ? 'Revenir à l\'édition' : 'Aperçu'}
aria-label={state.showMarkdownPreview ? 'Revenir à l\'édition' : 'Prévisualiser le rendu'}
@@ -139,8 +123,7 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
</button>
)}
{/* Convert to Rich Text — icon only */}
{(state.noteType === 'text' || state.noteType === 'markdown') && !readOnly && (
{state.isMarkdown && !readOnly && (
<button
title={t('ai.convertToRichtext') || 'Convert to Rich Text'}
aria-label={t('ai.convertToRichtext') || 'Convert to Rich Text'}
@@ -156,7 +139,6 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
</button>
)}
{/* AI — icon only */}
<button
title="AI Assistant"
aria-label="Ouvrir l'assistant IA"
@@ -171,7 +153,6 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
<Sparkles size={16} />
</button>
{/* Save — icon only */}
{!readOnly && (
<button
title={state.isDirty ? 'Enregistrer' : 'Aucune modification'}
@@ -189,7 +170,6 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
</button>
)}
{/* Share button */}
{!readOnly && (
<button
title="Partager la note"
@@ -201,8 +181,6 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
</button>
)}
{/* Three-dot options menu */}
{!readOnly && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -229,7 +207,6 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
</DropdownMenu>
)}
{/* Share Dialog portal */}
{shareOpen && (
<NoteShareDialog
noteId={note.id}
@@ -238,7 +215,6 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
/>
)}
{/* Info panel toggle — rightmost, icon only */}
<button
aria-label="Informations du document"
onClick={() => { actions.setInfoOpen(!state.infoOpen); actions.setAiOpen(false) }}
@@ -256,32 +232,26 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
)
}
// Dialog toolbar
return (
<div className="flex items-center justify-between pt-3 border-t border-border/30">
<div className="flex items-center gap-0.5">
{!readOnly && (
<>
{/* Reminder */}
<Button variant="ghost" size="icon" className={cn('h-8 w-8 rounded-md', state.currentReminder && 'text-primary')}
onClick={() => actions.setShowReminderDialog(true)} title={t('notes.setReminder')}>
<Bell className="h-4 w-4" />
</Button>
{/* Add Image */}
<Button variant="ghost" size="icon" className="h-8 w-8 rounded-md"
onClick={() => fileInputRef.current?.click()} title={t('notes.addImage')}>
<ImageIcon className="h-4 w-4" />
</Button>
{/* Add Link */}
<Button variant="ghost" size="icon" className="h-8 w-8 rounded-md"
onClick={() => actions.setShowLinkDialog(true)} title={t('notes.addLink')}>
<LinkIcon className="h-4 w-4" />
</Button>
<NoteTypeSelector value={state.noteType} onChange={(newType) => { actions.setNoteType(newType); if (newType !== 'markdown') actions.setShowMarkdownPreview(false) }} />
{state.noteType === 'markdown' && (
{state.isMarkdown && (
<Button variant="ghost" size="icon" className="h-8 w-8 rounded-md"
onClick={() => actions.setShowMarkdownPreview(!state.showMarkdownPreview)}
title={state.showMarkdownPreview ? t('general.edit') : t('notes.preview')}>
@@ -289,17 +259,13 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
</Button>
)}
{/* AI Copilot */}
{state.noteType !== 'checklist' && (
<Button variant="ghost" size="sm"
className={cn('h-8 gap-1.5 px-2 text-xs font-medium transition-all duration-200 rounded-md', state.aiOpen && 'bg-primary/10 text-primary')}
onClick={() => actions.setAiOpen(!state.aiOpen)} title="IA Note">
<Sparkles className="h-3.5 w-3.5" />
<span className="hidden sm:inline">IA Note</span>
</Button>
)}
<Button variant="ghost" size="sm"
className={cn('h-8 gap-1.5 px-2 text-xs font-medium transition-all duration-200 rounded-md', state.aiOpen && 'bg-primary/10 text-primary')}
onClick={() => actions.setAiOpen(!state.aiOpen)} title="IA Note">
<Sparkles className="h-3.5 w-3.5" />
<span className="hidden sm:inline">IA Note</span>
</Button>
{/* Size Selector */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8 rounded-md" title={t('notes.changeSize')}>
@@ -319,7 +285,6 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
</DropdownMenuContent>
</DropdownMenu>
{/* Color Picker */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8 rounded-md" title={t('notes.changeColor')}>
@@ -338,7 +303,6 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
</DropdownMenuContent>
</DropdownMenu>
{/* Label Manager */}
<LabelManager existingLabels={state.labels} notebookId={note.notebookId} onUpdate={actions.setLabels} />
</>
)}
@@ -394,4 +358,4 @@ export function NoteEditorToolbar({ mode, onClose }: NoteEditorToolbarProps) {
</div>
</div>
)
}
}