Files
noteflow/src/components/RichTextEditor.tsx
T
Claude 6dd3270f57 Feature: Implémentation complète de NoteFlow avec toutes les fonctionnalités
Cette mise à jour majeure transforme l'application en une plateforme complète
de gestion de notes et de tâches avec de nombreuses nouvelles fonctionnalités.

Nouvelles fonctionnalités:

1. **Éditeur de texte enrichi**
   - Barre d'outils complète avec formatage (gras, italique, souligné)
   - Support des titres (H1, H2, H3)
   - Listes à puces et numérotées
   - Alignement du texte (gauche, centre, droite)
   - Citations et blocs de code
   - Insertion de liens
   - Raccourcis clavier (Ctrl+B, Ctrl+I, Ctrl+U)

2. **Box Tâches globales (sidebar droite)**
   - Liste de tâches accessible depuis toute l'application
   - Ajout/suppression de tâches
   - Marquage des tâches comme complétées
   - Persistance des données

3. **Box Flux RSS (sidebar droite)**
   - Affichage des derniers articles RSS
   - Rafraîchissement manuel des flux
   - Ouverture des articles dans un nouvel onglet
   - Gestion des flux dans l'admin

4. **Support des fichiers joints dans les notes**
   - Upload de tous types de fichiers
   - Affichage de la taille des fichiers
   - Téléchargement et suppression des fichiers
   - Interface utilisateur intuitive

5. **Panel d'administration enrichi**
   - Onglet "Flux RSS" : gestion des flux RSS
   - Onglet "OpenRouter" : configuration de l'API IA
   - Onglet "Paramètres" : configuration de l'intervalle de rafraîchissement RSS
   - Interface organisée avec 4 onglets

6. **Amélioration de l'interface**
   - Layout à 3 colonnes responsive (Notes | Éditeur | Tâches+RSS)
   - Badges pour indiquer le nombre de tâches, images et fichiers par note
   - Design moderne et cohérent
   - Animations et transitions fluides

7. **Mise à jour de l'identité**
   - Nom de l'application : "NoteFlow"
   - Nouveau titre de page
   - Favicon SVG configuré
   - Logo mis à jour dans l'interface

Services ajoutés:
- RssService.ts : gestion des flux RSS et articles
- TodosService.ts : gestion des tâches globales
- SettingsService.ts : gestion des paramètres de l'application

Composants ajoutés:
- RichTextEditor.tsx : éditeur de texte enrichi avec barre d'outils

Modifications techniques:
- Ajout de la propriété "files" au type Note
- Résolution du conflit de nommage avec Settings
- Mise à jour de package.json avec la nouvelle description
- Mise à jour de index.html avec favicon et titre

Cette version représente une évolution majeure de l'application avec une
expérience utilisateur nettement améliorée et de nombreuses fonctionnalités
demandées.
2025-11-12 08:08:28 +00:00

174 lines
5.4 KiB
TypeScript

import { useRef, useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import {
Bold,
Italic,
Underline,
List,
ListOrdered,
Heading1,
Heading2,
Heading3,
AlignLeft,
AlignCenter,
AlignRight,
Code,
Quote,
Link as LinkIcon
} from 'lucide-react';
interface RichTextEditorProps {
content: string;
onChange: (content: string) => void;
placeholder?: string;
}
export function RichTextEditor({ content, onChange, placeholder }: RichTextEditorProps) {
const editorRef = useRef<HTMLDivElement>(null);
const [selectedFormat, setSelectedFormat] = useState<string[]>([]);
useEffect(() => {
if (editorRef.current && editorRef.current.innerHTML !== content) {
editorRef.current.innerHTML = content;
}
}, [content]);
const execCommand = (command: string, value?: string) => {
document.execCommand(command, false, value);
editorRef.current?.focus();
updateContent();
};
const updateContent = () => {
if (editorRef.current) {
onChange(editorRef.current.innerHTML);
}
};
const handleKeyDown = (e: React.KeyboardEvent) => {
// Gestion des raccourcis clavier
if (e.ctrlKey || e.metaKey) {
switch (e.key.toLowerCase()) {
case 'b':
e.preventDefault();
execCommand('bold');
break;
case 'i':
e.preventDefault();
execCommand('italic');
break;
case 'u':
e.preventDefault();
execCommand('underline');
break;
}
}
};
const insertLink = () => {
const url = prompt('Entrez l\'URL:');
if (url) {
execCommand('createLink', url);
}
};
const formatButton = (
icon: React.ReactNode,
command: string,
value?: string,
tooltip?: string
) => (
<Button
variant="ghost"
size="sm"
onClick={() => execCommand(command, value)}
title={tooltip}
className="h-8 w-8 p-0"
>
{icon}
</Button>
);
return (
<div className="space-y-2">
{/* Barre d'outils */}
<Card>
<CardContent className="p-2">
<div className="flex flex-wrap items-center gap-1">
{/* Titres */}
<div className="flex items-center gap-1">
{formatButton(<Heading1 className="h-4 w-4" />, 'formatBlock', '<h1>', 'Titre 1')}
{formatButton(<Heading2 className="h-4 w-4" />, 'formatBlock', '<h2>', 'Titre 2')}
{formatButton(<Heading3 className="h-4 w-4" />, 'formatBlock', '<h3>', 'Titre 3')}
</div>
<Separator orientation="vertical" className="h-6" />
{/* Formatage de texte */}
<div className="flex items-center gap-1">
{formatButton(<Bold className="h-4 w-4" />, 'bold', undefined, 'Gras (Ctrl+B)')}
{formatButton(<Italic className="h-4 w-4" />, 'italic', undefined, 'Italique (Ctrl+I)')}
{formatButton(<Underline className="h-4 w-4" />, 'underline', undefined, 'Souligné (Ctrl+U)')}
</div>
<Separator orientation="vertical" className="h-6" />
{/* Listes */}
<div className="flex items-center gap-1">
{formatButton(<List className="h-4 w-4" />, 'insertUnorderedList', undefined, 'Liste à puces')}
{formatButton(<ListOrdered className="h-4 w-4" />, 'insertOrderedList', undefined, 'Liste numérotée')}
</div>
<Separator orientation="vertical" className="h-6" />
{/* Alignement */}
<div className="flex items-center gap-1">
{formatButton(<AlignLeft className="h-4 w-4" />, 'justifyLeft', undefined, 'Aligner à gauche')}
{formatButton(<AlignCenter className="h-4 w-4" />, 'justifyCenter', undefined, 'Centrer')}
{formatButton(<AlignRight className="h-4 w-4" />, 'justifyRight', undefined, 'Aligner à droite')}
</div>
<Separator orientation="vertical" className="h-6" />
{/* Autres */}
<div className="flex items-center gap-1">
{formatButton(<Quote className="h-4 w-4" />, 'formatBlock', '<blockquote>', 'Citation')}
{formatButton(<Code className="h-4 w-4" />, 'formatBlock', '<pre>', 'Code')}
<Button
variant="ghost"
size="sm"
onClick={insertLink}
title="Insérer un lien"
className="h-8 w-8 p-0"
>
<LinkIcon className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
{/* Zone d'édition */}
<Card>
<CardContent className="p-4">
<div
ref={editorRef}
contentEditable
onInput={updateContent}
onKeyDown={handleKeyDown}
className="min-h-[400px] outline-none prose prose-sm max-w-none dark:prose-invert"
dangerouslySetInnerHTML={{ __html: content }}
data-placeholder={placeholder || 'Commencez à écrire...'}
style={{
fontSize: '16px',
lineHeight: '1.6'
}}
/>
</CardContent>
</Card>
</div>
);
}