feat: add notes feature and keyboard shortcuts

- Introduced a new Note model in the Prisma schema to support note-taking functionality.
- Updated the HeaderNavigation component to include a link to the new Notes page.
- Implemented keyboard shortcuts for note actions, enhancing user experience and productivity.
- Added dependencies for markdown rendering and formatting tools to support note content.
This commit is contained in:
Julien Froidefond
2025-10-09 13:38:09 +02:00
parent 1fe59f26e4
commit 6c86ce44f1
15 changed files with 4354 additions and 96 deletions

View File

@@ -1,6 +1,12 @@
'use client';
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import {
createContext,
useContext,
useState,
useEffect,
ReactNode,
} from 'react';
import { usePathname } from 'next/navigation';
export interface KeyboardShortcut {
@@ -20,130 +26,264 @@ const PAGE_SHORTCUTS: PageShortcuts = {
{
keys: ['Cmd', '?'],
description: 'Afficher les raccourcis clavier',
category: 'Navigation'
category: 'Navigation',
},
{
keys: ['Shift', 'Q'],
description: 'Basculer le thème',
category: 'Apparence'
category: 'Apparence',
},
{
keys: ['Shift', 'W'],
description: 'Faire tourner les thèmes dark',
category: 'Apparence'
category: 'Apparence',
},
{
keys: ['Shift', 'B'],
description: 'Changer le background',
category: 'Apparence'
category: 'Apparence',
},
{
keys: ['Esc'],
description: 'Fermer les modales/annuler',
category: 'Navigation'
}
category: 'Navigation',
},
],
// Dashboard
'/': [
{
keys: ['Shift', 'K'],
description: 'Vers Kanban',
category: 'Navigation'
}
category: 'Navigation',
},
],
// Kanban
'/kanban': [
{
keys: ['Shift', 'N'],
description: 'Créer une nouvelle tâche',
category: 'Actions'
category: 'Actions',
},
{
keys: ['Space'],
description: 'Basculer la vue compacte',
category: 'Vue'
category: 'Vue',
},
{
keys: ['Shift', 'F'],
description: 'Ouvrir les filtres',
category: 'Filtres'
category: 'Filtres',
},
{
keys: ['Shift', 'S'],
description: 'Basculer les swimlanes',
category: 'Vue'
category: 'Vue',
},
{
keys: ['Shift', 'O'],
description: 'Basculer les objectifs',
category: 'Vue'
category: 'Vue',
},
{
keys: ['Shift', 'D'],
description: 'Filtrer par date de fin',
category: 'Filtres'
category: 'Filtres',
},
{
keys: ['Shift', 'Z'],
description: 'Basculer la taille de police',
category: 'Vue'
category: 'Vue',
},
{
keys: ['Tab'],
description: 'Navigation entre colonnes',
category: 'Navigation'
category: 'Navigation',
},
{
keys: ['Enter'],
description: 'Valider une tâche',
category: 'Actions'
category: 'Actions',
},
{
keys: ['Esc'],
description: 'Annuler la création de tâche',
category: 'Actions'
}
category: 'Actions',
},
],
// Daily
'/daily': [
{
keys: ['←', '→'],
description: 'Navigation jour précédent/suivant',
category: 'Navigation'
category: 'Navigation',
},
{
keys: ['Shift', 'H'],
description: 'Aller à aujourd\'hui',
category: 'Navigation'
description: "Aller à aujourd'hui",
category: 'Navigation',
},
{
keys: ['Enter'],
description: 'Valider un élément',
category: 'Actions'
}
category: 'Actions',
},
],
// Manager
'/weekly-manager': [
{
keys: ['Cmd', 'Ctrl', 'N'],
description: 'Créer un objectif',
category: 'Actions'
category: 'Actions',
},
{
keys: ['←', '→'],
description: 'Navigation semaine précédente/suivante',
category: 'Navigation'
category: 'Navigation',
},
{
keys: ['Cmd', 'Ctrl', 'T'],
description: 'Aller à cette semaine',
category: 'Navigation'
}
]
category: 'Navigation',
},
],
// Notes
'/notes': [
{
keys: ['Cmd', 'N'],
description: 'Créer une nouvelle note',
category: 'Actions',
},
{
keys: ['Cmd', 'S'],
description: 'Sauvegarder la note',
category: 'Actions',
},
{
keys: ['Cmd', 'E'],
description: 'Basculer mode édition/aperçu',
category: 'Navigation',
},
{
keys: ['Cmd', 'Shift', 'P'],
description: 'Basculer aperçu',
category: 'Navigation',
},
{
keys: ['Cmd', 'B'],
description: 'Texte en gras',
category: 'Édition',
},
{
keys: ['Cmd', 'I'],
description: 'Texte en italique',
category: 'Édition',
},
{
keys: ['Cmd', 'K'],
description: 'Créer un lien',
category: 'Édition',
},
{
keys: ['Cmd', 'Shift', 'K'],
description: 'Code inline',
category: 'Édition',
},
{
keys: ['Cmd', 'Shift', 'C'],
description: 'Bloc de code',
category: 'Édition',
},
{
keys: ['Cmd', 'Shift', 'X'],
description: 'Texte barré',
category: 'Édition',
},
{
keys: ['Cmd', 'Shift', 'H'],
description: 'Titre niveau 1',
category: 'Édition',
},
{
keys: ['Cmd', 'Shift', '2'],
description: 'Titre niveau 2',
category: 'Édition',
},
{
keys: ['Cmd', 'Shift', '3'],
description: 'Titre niveau 3',
category: 'Édition',
},
{
keys: ['Cmd', 'Shift', 'L'],
description: 'Liste à puces',
category: 'Édition',
},
{
keys: ['Cmd', 'Shift', 'O'],
description: 'Liste numérotée',
category: 'Édition',
},
{
keys: ['Cmd', 'Shift', 'Q'],
description: 'Citation',
category: 'Édition',
},
{
keys: ['Cmd', 'Z'],
description: 'Annuler',
category: 'Édition',
},
{
keys: ['Cmd', 'Shift', 'Z'],
description: 'Rétablir',
category: 'Édition',
},
{
keys: ['Cmd', 'A'],
description: 'Tout sélectionner',
category: 'Édition',
},
{
keys: ['Cmd', 'C'],
description: 'Copier',
category: 'Édition',
},
{
keys: ['Cmd', 'V'],
description: 'Coller',
category: 'Édition',
},
{
keys: ['Cmd', 'X'],
description: 'Couper',
category: 'Édition',
},
{
keys: ['Cmd', 'F'],
description: 'Rechercher',
category: 'Édition',
},
{
keys: ['Cmd', 'G'],
description: 'Rechercher suivant',
category: 'Édition',
},
{
keys: ['Cmd', 'Shift', 'G'],
description: 'Rechercher précédent',
category: 'Édition',
},
{
keys: ['Cmd', 'Shift', 'S'],
description: 'Basculer la barre latérale',
category: 'Navigation',
},
],
};
interface KeyboardShortcutsContextType {
@@ -154,36 +294,40 @@ interface KeyboardShortcutsContextType {
toggleModal: () => void;
}
const KeyboardShortcutsContext = createContext<KeyboardShortcutsContextType | undefined>(undefined);
const KeyboardShortcutsContext = createContext<
KeyboardShortcutsContextType | undefined
>(undefined);
interface KeyboardShortcutsProviderProps {
children: ReactNode;
}
export function KeyboardShortcutsProvider({ children }: KeyboardShortcutsProviderProps) {
export function KeyboardShortcutsProvider({
children,
}: KeyboardShortcutsProviderProps) {
const [isOpen, setIsOpen] = useState(false);
const pathname = usePathname();
// Obtenir les raccourcis pour la page actuelle
const getCurrentPageShortcuts = (): KeyboardShortcut[] => {
const shortcuts: KeyboardShortcut[] = [];
// Ajouter les raccourcis globaux
if (PAGE_SHORTCUTS['*']) {
shortcuts.push(...PAGE_SHORTCUTS['*']);
}
// Ajouter les raccourcis spécifiques à la page
const pageShortcuts = PAGE_SHORTCUTS[pathname];
if (pageShortcuts) {
shortcuts.push(...pageShortcuts);
}
return shortcuts;
};
const shortcuts = getCurrentPageShortcuts();
// Gérer le raccourci Cmd+? pour ouvrir la popup
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
@@ -192,28 +336,28 @@ export function KeyboardShortcutsProvider({ children }: KeyboardShortcutsProvide
event.preventDefault();
setIsOpen(true);
}
// Esc pour fermer
if (event.key === 'Escape' && isOpen) {
setIsOpen(false);
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen]);
const contextValue: KeyboardShortcutsContextType = {
isOpen,
shortcuts,
openModal: () => setIsOpen(true),
closeModal: () => setIsOpen(false),
toggleModal: () => setIsOpen(prev => !prev)
toggleModal: () => setIsOpen((prev) => !prev),
};
return (
<KeyboardShortcutsContext.Provider value={contextValue}>
{children}
@@ -224,7 +368,9 @@ export function KeyboardShortcutsProvider({ children }: KeyboardShortcutsProvide
export function useKeyboardShortcutsModal() {
const context = useContext(KeyboardShortcutsContext);
if (context === undefined) {
throw new Error('useKeyboardShortcutsModal must be used within a KeyboardShortcutsProvider');
throw new Error(
'useKeyboardShortcutsModal must be used within a KeyboardShortcutsProvider'
);
}
return context;
}