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
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m24s
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user