From a57c277168a989c7506054c1bb48be62bd23c0fb Mon Sep 17 00:00:00 2001 From: Sepehr Ramezani Date: Fri, 17 Apr 2026 21:53:10 +0200 Subject: [PATCH] chore: clean complet du projet et ajout d'un README avec licence personnelle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Suppression de tous les scripts de migration temporaires (fix_*.py, update_*.py, etc) - Suppression des fichiers d'expérimentation html/json et .txt - Modification du README: ajout d'une section licence usage non-commercial - Modification du README: ajout d'une roadmap pour un déploiement public SaaS avec PostgreSQL et gestion de micro-services IO/Abonnement. --- keep-notes/GLOBALS-CSS-CORRIGÉ-SLATE.css | 323 -------------- keep-notes/GUIDE-TEST-COULEURS.md | 271 ------------ keep-notes/PROPOSITION-COULEURS.md | 332 --------------- keep-notes/PROPOSITION-SLATE-MODERNE.md | 242 ----------- keep-notes/README.md | 135 +++--- keep-notes/THEMES-HARMONISES-SLATE.md | 361 ---------------- keep-notes/VISUALISATION-COULEURS.html | 521 ----------------------- keep-notes/fix-locales.js | 27 -- keep-notes/fix-services.sh | 14 - keep-notes/fix_ai_lang.py | 70 --- keep-notes/fix_api_labels.py | 25 -- keep-notes/fix_auto_tag_hook.py | 18 - keep-notes/fix_date_locale.py | 41 -- keep-notes/fix_dialog.py | 102 ----- keep-notes/fix_dialog_dir.py | 22 - keep-notes/fix_end3.py | 7 - keep-notes/fix_filter_and_toggle.py | 47 -- keep-notes/fix_filter_button_dir.py | 10 - keep-notes/fix_filter_dir.py | 19 - keep-notes/fix_hook_lang.py | 11 - keep-notes/fix_note_input.py | 18 - keep-notes/fix_notebooks.py | 22 - keep-notes/fix_ollama_provider.py | 68 --- keep-notes/fix_sidebar.py | 10 - keep-notes/fix_tabs.py | 10 - keep-notes/fix_translation.py | 34 -- keep-notes/git_version_backup.txt | 301 ------------- keep-notes/prisma/dev.db | Bin 5128192 -> 5128192 bytes keep-notes/temp_git_exact.txt | 301 ------------- keep-notes/temp_git_masonry.txt | 301 ------------- keep-notes/temp_git_version.txt | 301 ------------- keep-notes/test-ai-tags.json | 1 - keep-notes/test-memory-echo.js | 8 - keep-notes/test-reformulate.json | 1 - keep-notes/update_labels_dict.py | 47 -- keep-notes/update_toggle_dir.py | 17 - 36 files changed, 50 insertions(+), 3988 deletions(-) delete mode 100644 keep-notes/GLOBALS-CSS-CORRIGÉ-SLATE.css delete mode 100644 keep-notes/GUIDE-TEST-COULEURS.md delete mode 100644 keep-notes/PROPOSITION-COULEURS.md delete mode 100644 keep-notes/PROPOSITION-SLATE-MODERNE.md delete mode 100644 keep-notes/THEMES-HARMONISES-SLATE.md delete mode 100644 keep-notes/VISUALISATION-COULEURS.html delete mode 100644 keep-notes/fix-locales.js delete mode 100644 keep-notes/fix-services.sh delete mode 100644 keep-notes/fix_ai_lang.py delete mode 100644 keep-notes/fix_api_labels.py delete mode 100644 keep-notes/fix_auto_tag_hook.py delete mode 100644 keep-notes/fix_date_locale.py delete mode 100644 keep-notes/fix_dialog.py delete mode 100644 keep-notes/fix_dialog_dir.py delete mode 100644 keep-notes/fix_end3.py delete mode 100644 keep-notes/fix_filter_and_toggle.py delete mode 100644 keep-notes/fix_filter_button_dir.py delete mode 100644 keep-notes/fix_filter_dir.py delete mode 100644 keep-notes/fix_hook_lang.py delete mode 100644 keep-notes/fix_note_input.py delete mode 100644 keep-notes/fix_notebooks.py delete mode 100644 keep-notes/fix_ollama_provider.py delete mode 100644 keep-notes/fix_sidebar.py delete mode 100644 keep-notes/fix_tabs.py delete mode 100644 keep-notes/fix_translation.py delete mode 100644 keep-notes/git_version_backup.txt delete mode 100644 keep-notes/temp_git_exact.txt delete mode 100644 keep-notes/temp_git_masonry.txt delete mode 100644 keep-notes/temp_git_version.txt delete mode 100644 keep-notes/test-ai-tags.json delete mode 100644 keep-notes/test-memory-echo.js delete mode 100644 keep-notes/test-reformulate.json delete mode 100644 keep-notes/update_labels_dict.py delete mode 100644 keep-notes/update_toggle_dir.py diff --git a/keep-notes/GLOBALS-CSS-CORRIGÉ-SLATE.css b/keep-notes/GLOBALS-CSS-CORRIGÉ-SLATE.css deleted file mode 100644 index 6957246..0000000 --- a/keep-notes/GLOBALS-CSS-CORRIGÉ-SLATE.css +++ /dev/null @@ -1,323 +0,0 @@ -# 🔧 CORRECTION COMPLÈTE : Thème Slate - -Ramez, voici le fichier CORRIGÉ complet à copier dans `keep-notes/app/globals.css` - ---- - -## ✅ INSTRUCTIONS PRÉCISES - -### Étape 1 : Remplacez le thème principal (:root) -Cherchez `:root {` dans le fichier (ligne 100) -Remplacez TOUTES les lignes 100-133 par ceci : - -```css -:root { - --radius: 0.625rem; - - /* ============================================ - THEME SLATE (GRIS-BLEU) - PRINCIPAL ⭐ - ============================================ */ - - /* Backgrounds */ - --background: oklch(0.985 0.003 230); /* Blanc grisâtre léger */ - --card: oklch(1 0 0); /* Blanc pur */ - --sidebar: oklch(0.97 0.004 230); /* Gris-bleu très pâle */ - --input: oklch(0.98 0.003 230); /* Gris-bleu pâle */ - - /* Textes */ - --foreground: oklch(0.2 0.02 230); /* Gris-bleu foncé */ - --card-foreground: oklch(0.2 0.02 230); - --popover-foreground: oklch(0.2 0.02 230); - --foreground-secondary: oklch(0.45 0.015 230); /* Gris-bleu moyen */ - --foreground-muted: oklch(0.6 0.01 230); /* Gris-bleu clair */ - - /* Primary Actions - GRIS-BLEU, PAS BLEU SATURÉ */ - --primary: oklch(0.45 0.08 230); /* Gris-bleu doux */ - --primary-foreground: oklch(0.99 0 0); /* Blanc */ - - /* Secondary */ - --secondary: oklch(0.94 0.005 230); /* Gris-bleu très pâle */ - --secondary-foreground: oklch(0.2 0.02 230); - - /* Accents */ - --muted: oklch(0.92 0.005 230); - --muted-foreground: oklch(0.6 0.01 230); - --accent: oklch(0.94 0.005 230); - --accent-foreground: oklch(0.2 0.02 230); - - /* Functional */ - --destructive: oklch(0.6 0.18 25); /* Rouge */ - --border: oklch(0.9 0.008 230); /* Gris-bleu très clair */ - --input: oklch(0.98 0.003 230); - --ring: oklch(0.7 0.005 230); - - /* Sidebar */ - --sidebar: oklch(0.97 0.004 230); - --sidebar-foreground: oklch(0.2 0.02 230); - --sidebar-primary: oklch(0.45 0.08 230); - --sidebar-primary-foreground: oklch(0.99 0 0); - --sidebar-accent: oklch(0.94 0.005 230); - --sidebar-accent-foreground: oklch(0.2 0.02 230); - --sidebar-border: oklch(0.9 0.008 230); - --sidebar-ring: oklch(0.7 0.005 230); - - /* Popover */ - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.2 0.02 230); - - /* Charts (conservés) */ - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); -} -``` - ---- - -### Étape 2 : Remplacez le thème dark par défaut (.dark) -Cherchez `.dark {` dans le fichier (ligne 135) -Remplacez TOUTES les lignes 135-167 par ceci : - -```css -.dark { - /* ============================================ - THEME SLATE DARK MODE - ============================================ */ - - /* Backgrounds */ - --background: oklch(0.14 0.005 230); /* Noir grisâtre */ - --card: oklch(0.18 0.006 230); /* Gris-bleu foncé */ - --sidebar: oklch(0.12 0.005 230); /* Noir grisâtre */ - --input: oklch(0.2 0.006 230); - - /* Textes */ - --foreground: oklch(0.97 0.003 230); /* Blanc grisâtre */ - --card-foreground: oklch(0.97 0.003 230); - --popover-foreground: oklch(0.97 0.003 230); - --foreground-secondary: oklch(0.75 0.008 230); - --foreground-muted: oklch(0.55 0.01 230); - --popover-foreground: oklch(0.97 0.003 230); - - /* Primary Actions */ - --primary: oklch(0.55 0.08 230); /* Gris-bleu plus clair */ - --primary-foreground: oklch(0.1 0 0); /* Noir */ - - /* Secondary */ - --secondary: oklch(0.24 0.006 230); - --secondary-foreground: oklch(0.97 0.003 230); - - /* Accents */ - --muted: oklch(0.22 0.006 230); - --muted-foreground: oklch(0.55 0.01 230); - --accent: oklch(0.24 0.006 230); - --accent-foreground: oklch(0.97 0.003 230); - - /* Functional */ - --destructive: oklch(0.65 0.18 25); - --border: oklch(0.28 0.01 230); - --input: oklch(0.2 0.006 230); - --ring: oklch(0.6 0.01 230); - - /* Sidebar */ - --sidebar: oklch(0.12 0.005 230); - --sidebar-foreground: oklch(0.97 0.003 230); - --sidebar-primary: oklch(0.55 0.08 230); - --sidebar-primary-foreground: oklch(0.1 0 0); - --sidebar-accent: oklch(0.24 0.006 230); - --sidebar-accent-foreground: oklch(0.97 0.003 230); - --sidebar-border: oklch(0.28 0.01 230); - --sidebar-ring: oklch(0.6 0.01 230); - - /* Popover */ - --popover: oklch(0.18 0.006 230); - --popover-foreground: oklch(0.97 0.003 230); - - /* Charts */ - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); -} -``` - ---- - -### Étape 3 : Corrigez le thème MIDNIGHT (remplacez lignes 169-188) -Cherchez `[data-theme='midnight'] {` (ligne 169) -Remplacez TOUTES les lignes 169-188 par ceci : - -```css -[data-theme='midnight'] { - /* ============================================ - THEME MIDNIGHT - VERSION SOMBRE DE SLATE - ============================================ */ - - /* Light Mode */ - --background: oklch(0.94 0.005 250); /* Gris-bleu très pâle */ - --foreground: oklch(0.18 0.03 250); /* Gris-bleu très foncé */ - --card: oklch(0.97 0.006 250); /* Gris-bleu pâle */ - --card-foreground: oklch(0.18 0.03 250); - --popover: oklch(0.97 0.006 250); - --popover-foreground: oklch(0.18 0.03 250); - --primary: oklch(0.5 0.12 250); /* Gris-bleu saturé */ - --primary-foreground: oklch(0.99 0 0); /* Blanc */ - --secondary: oklch(0.2 0.01 250); - --secondary-foreground: oklch(0.18 0.03 250); - --muted: oklch(0.22 0.01 250); - --muted-foreground: oklch(0.55 0.02 250); - --accent: oklch(0.25 0.015 250); - --accent-foreground: oklch(0.18 0.03 250); - --destructive: oklch(0.6 0.22 25); - --border: oklch(0.85 0.015 250); - --input: oklch(0.25 0.01 250); - --ring: oklch(0.65 0.015 250); - --sidebar: oklch(0.9 0.01 250); - --sidebar-foreground: oklch(0.18 0.03 250); - --sidebar-primary: oklch(0.5 0.12 250); - --sidebar-primary-foreground: oklch(0.99 0 0); - --sidebar-accent: oklch(0.25 0.015 250); - --sidebar-accent-foreground: oklch(0.18 0.03 250); - --sidebar-border: oklch(0.85 0.015 250); - --sidebar-ring: oklch(0.65 0.015 250); -} -``` - ---- - -### Étape 4 : Corrigez le thème BLUE (remplacez lignes 190-217) -Cherchez `[data-theme='blue'] {` (ligne 190) -Remplacez TOUTES les lignes 190-217 par ceci : - -```css -[data-theme='blue'] { - /* ============================================ - THEME BLUE - VERSION SATURÉE DE SLATE - ============================================ */ - - /* Light Mode */ - --background: oklch(0.985 0.005 225); /* Blanc légèrement bleuté */ - --foreground: oklch(0.18 0.035 225); /* Gris-bleu foncé saturé */ - --card: oklch(0.98 0.01 225); /* Blanc légèrement bleuté */ - --card-foreground: oklch(0.18 0.035 225); - --popover: oklch(0.98 0.01 225); - --popover-foreground: oklch(0.18 0.035 225); - --primary: oklch(0.5 0.15 225); /* Gris-bleu saturé vibrant */ - --primary-foreground: oklch(0.99 0 0); /* Blanc */ - --secondary: oklch(0.93 0.008 225); - --secondary-foreground: oklch(0.18 0.035 225); - --muted: oklch(0.9 0.01 225); - --muted-foreground: oklch(0.58 0.015 225); - --accent: oklch(0.93 0.01 225); - --accent-foreground: oklch(0.18 0.035 225); - --destructive: oklch(0.6 0.2 25); - --border: oklch(0.87 0.012 225); - --input: oklch(0.95 0.01 225); - --ring: oklch(0.65 0.015 225); - --sidebar: oklch(0.965 0.008 225); - --sidebar-foreground: oklch(0.18 0.035 225); - --sidebar-primary: oklch(0.5 0.15 225); - --sidebar-primary-foreground: oklch(0.99 0 0); - --sidebar-accent: oklch(0.93 0.01 225); - --sidebar-accent-foreground: oklch(0.18 0.035 225); - --sidebar-border: oklch(0.87 0.012 225); - --sidebar-ring: oklch(0.65 0.015 225); -} -``` - ---- - -### Étape 5 : Corrigez le thème SEPIA (remplacez lignes 219-238) -Cherchez `[data-theme='sepia'] {` (ligne 219) -Remplacez TOUTES les lignes 219-238 par ceci : - -```css -[data-theme='sepia'] { - /* ============================================ - THEME SEPIA - VERSION CHALEUREUSE DE SLATE - ============================================ */ - - /* Light Mode */ - --background: oklch(0.985 0.004 45); /* Blanc légèrement doré */ - --foreground: oklch(0.2 0.015 45); /* Gris-brun foncé */ - --card: oklch(0.98 0.01 45); /* Blanc légèrement doré */ - --card-foreground: oklch(0.2 0.015 45); - --popover: oklch(0.98 0.01 45); - --popover-foreground: oklch(0.2 0.015 45); - --primary: oklch(0.45 0.08 45); /* Gris-brun chaud */ - --primary-foreground: oklch(0.99 0 0); /* Blanc */ - --secondary: oklch(0.94 0.008 45); - --secondary-foreground: oklch(0.2 0.015 45); - --muted: oklch(0.91 0.01 45); - --muted-foreground: oklch(0.6 0.012 45); - --accent: oklch(0.93 0.01 45); - --accent-foreground: oklch(0.2 0.015 45); - --destructive: oklch(0.6 0.2 25); - --border: oklch(0.88 0.012 45); - --input: oklch(0.97 0.008 45); - --ring: oklch(0.68 0.01 45); - --sidebar: oklch(0.96 0.01 45); - --sidebar-foreground: oklch(0.2 0.015 45); - --sidebar-primary: oklch(0.45 0.08 45); - --sidebar-primary-foreground: oklch(0.99 0 0); - --sidebar-accent: oklch(0.93 0.01 45); - --sidebar-accent-foreground: oklch(0.2 0.015 45); - --sidebar-border: oklch(0.88 0.012 45); - --sidebar-ring: oklch(0.68 0.01 45); -} -``` - ---- - -## ✅ APRÈS LES MODIFICATIONS - -1. **Enregistrez** le fichier (`Ctrl+S`) -2. **Allez dans vos settings** (Settings → Apparence) -3. **Changez le thème** de 'blue' vers : - - **'light'** pour le nouveau thème Slate principal - - OU 'midnight' pour tester le thème nuit - - OU 'blue' pour tester le thème bleu harmonisé -4. **Rafraîchissez** la page (`F5` ou redémarrez le serveur) - ---- - -## 🎨 Ce que vous devriez voir : - -### Avec thème 'light' (nouveau Slate) : -- Fond : blanc grisâtre -- Texte : gris-bleu foncé -- Boutons primary : **GRIS-BLEU DOX** (pas bleu saturé !) -- Plus professionnel, moins fatigant - -### Avec thème 'midnight' : -- Fond : noir grisâtre -- Texte : blanc grisâtre -- Boutons : gris-bleu vibrant -- Nuit profonde - -### Avec thème 'blue' : -- Fond : blanc légèrement bleuté -- Texte : gris-bleu foncé saturé -- Boutons : gris-bleu vibrant -- Version énergique - -### Avec thème 'sepia' : -- Fond : blanc légèrement doré -- Texte : gris-brun -- Boutons : gris-brun -- Version chaleureuse - ---- - -## 🔙 ANNULER si pas satisfait : - -Si les couleurs ne vous plaisent pas : -``` -git checkout -- keep-notes/app/globals.css -``` - ---- - -**Ramez, après avoir appliqué ces modifications, testez le thème 'light' pour voir le nouveau Slate !** 🎨🚀 diff --git a/keep-notes/GUIDE-TEST-COULEURS.md b/keep-notes/GUIDE-TEST-COULEURS.md deleted file mode 100644 index bcb1d0e..0000000 --- a/keep-notes/GUIDE-TEST-COULEURS.md +++ /dev/null @@ -1,271 +0,0 @@ -# 🧪 GUIDE SIMPLE : Comment Tester le Thème Slate - -Ramez, voici les étapes SIMPLES pour tester le nouveau thème Gris-Bleu (Slate). - ---- - -## 📋 RÉSUMÉ - -**Ce que j'ai fait :** Créé des fichiers de PROPOSITIONS uniquement - -**Ce que vous devez faire :** Implémenter (copier-coller) le code dans votre application - ---- - -## 🎯 ÉTAPE 1 : Modifier le thème principal (5 minutes) - -Ouvrez : `keep-notes/app/globals.css` - -### ✏️ Remplacez les lignes 100-133 par ceci : - -```css -:root { - --radius: 0.625rem; - - /* === THEME SLATE (GRIS-BLEU) MODERNE === */ - - /* Backgrounds */ - --background: oklch(0.985 0.003 230); /* Blanc grisâtre */ - --card: oklch(1 0 0); /* Blanc pur */ - --sidebar: oklch(0.97 0.004 230); /* Gris-bleu pâle */ - --input: oklch(0.98 0.003 230); /* Gris-bleu pâle */ - - /* Textes */ - --foreground: oklch(0.2 0.02 230); /* Gris-bleu foncé */ - --card-foreground: oklch(0.2 0.02 230); - --foreground-secondary: oklch(0.45 0.015 230); /* Gris-bleu moyen */ - --foreground-muted: oklch(0.6 0.01 230); /* Gris-bleu clair */ - --popover-foreground: oklch(0.2 0.02 230); - - /* Primary Actions */ - --primary: oklch(0.45 0.08 230); /* Gris-bleu doux */ - --primary-foreground: oklch(0.99 0 0); /* Blanc */ - - /* Secondary */ - --secondary: oklch(0.94 0.005 230); /* Gris-bleu très pâle */ - --secondary-foreground: oklch(0.2 0.02 230); - - /* Accents */ - --muted: oklch(0.92 0.005 230); - --muted-foreground: oklch(0.6 0.01 230); - --accent: oklch(0.94 0.005 230); - --accent-foreground: oklch(0.2 0.02 230); - - /* Functional */ - --destructive: oklch(0.6 0.18 25); /* Rouge */ - --border: oklch(0.9 0.008 230); /* Gris-bleu très clair */ - --input: oklch(0.98 0.003 230); - --ring: oklch(0.7 0.005 230); - - /* Sidebar */ - --sidebar-foreground: oklch(0.2 0.02 230); - --sidebar-primary: oklch(0.45 0.08 230); - --sidebar-primary-foreground: oklch(0.99 0 0); - --sidebar-accent: oklch(0.94 0.005 230); - --sidebar-accent-foreground: oklch(0.2 0.02 230); - --sidebar-border: oklch(0.9 0.008 230); - --sidebar-ring: oklch(0.7 0.005 230); - - /* Popover */ - --popover: oklch(1 0 0); - - /* Charts (gardés) */ - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); -} -``` - -### ✏️ Remplacez les lignes 135-167 par ceci : - -```css -.dark { - /* === THEME SLATE DARK MODE === */ - - /* Backgrounds */ - --background: oklch(0.14 0.005 230); /* Noir grisâtre */ - --card: oklch(0.18 0.006 230); /* Gris-bleu foncé */ - --sidebar: oklch(0.12 0.005 230); /* Noir grisâtre */ - --input: oklch(0.2 0.006 230); - - /* Textes */ - --foreground: oklch(0.97 0.003 230); /* Blanc grisâtre */ - --card-foreground: oklch(0.97 0.003 230); - --foreground-secondary: oklch(0.75 0.008 230); - --foreground-muted: oklch(0.55 0.01 230); - --popover-foreground: oklch(0.97 0.003 230); - - /* Primary Actions */ - --primary: oklch(0.55 0.08 230); /* Gris-bleu plus clair */ - --primary-foreground: oklch(0.1 0 0); /* Noir */ - - /* Secondary */ - --secondary: oklch(0.24 0.006 230); - --secondary-foreground: oklch(0.97 0.003 230); - - /* Accents */ - --muted: oklch(0.22 0.006 230); - --muted-foreground: oklch(0.55 0.01 230); - --accent: oklch(0.24 0.006 230); - --accent-foreground: oklch(0.97 0.003 230); - - /* Functional */ - --destructive: oklch(0.65 0.18 25); - --border: oklch(0.28 0.01 230); - --input: oklch(0.2 0.006 230); - --ring: oklch(0.6 0.01 230); - - /* Sidebar */ - --sidebar-foreground: oklch(0.97 0.003 230); - --sidebar-primary: oklch(0.55 0.08 230); - --sidebar-primary-foreground: oklch(0.1 0 0); - --sidebar-accent: oklch(0.24 0.006 230); - --sidebar-accent-foreground: oklch(0.97 0.003 230); - --sidebar-border: oklch(0.28 0.01 230); - --sidebar-ring: oklch(0.6 0.01 230); - - /* Popover */ - --popover: oklch(0.18 0.006 230); - --popover-foreground: oklch(0.97 0.003 230); - - /* Charts (gardés mais ajustés pour dark) */ - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); -} -``` - -### ✅ Enregistrez le fichier -`Ctrl+S` ou `Cmd+S` - ---- - -## 🧪 ÉTAPE 2 : Vérifiez le résultat (immédiat !) - -### Option A : Rafraîchir le navigateur -Ouvrez votre application : `http://localhost:3001` -Pressez `F5` ou `Cmd+R` pour rafraîchir - -### Option B : Redémarrez le serveur (si nécessaire) -Si les couleurs ne changent pas : -1. Allez dans le terminal où tourne votre serveur -2. Pressez `Ctrl+C` pour arrêter -3. Relancez : `npm run dev` - ---- - -## 🎯 ÉTAPE 3 : Testez le thème - -### Ce que vous devriez voir : - -**En mode light :** -- Fond légèrement grisâtre (pas blanc pur) -- Texte gris-bleu foncé (pas noir pur) -- Boutons primary en gris-bleu doux (pas bleu saturé) -- Bordures gris-bleu très clair - -**En mode dark :** -- Fond noir grisâtre (pas noir pur) -- Texte blanc grisâtre (pas blanc pur) -- Boutons primary en gris-bleu plus clair -- Bordures gris-bleu foncé - -### Testez les modes : -- Basculez entre light et dark avec votre switcher -- Observez les changements -- Notez si vous aimez ou non - ---- - -## ❓ RAPPEL : Pourquoi Slate ? - -### ✅ Avantages que vous verrez : - -1. **Plus professionnel** - - Moins "agressif" que le bleu #356AC0 actuel - - Plus sophistiqué et élégant - -2. **Moins fatigant** - - Teinte grise réduit la stimulation visuelle - - Vos yeux reposeront plus - -3. **Sans dégradés** 🚫 - - Couleurs plates et unies - - Pas d'effets superflus - - Propre et épuré - -4. **Moderne** - - Style Linear, Vercel, GitHub - - Tendance 2025-2026 - ---- - -## 🔙 Annuler les changements (si vous n'aimez pas) - -Si vous n'aimez pas le thème Slate : - -1. Ouvrez le terminal Git -2. Tapez : `git checkout -- keep-notes/app/globals.css` -3. Rafraîchissez le navigateur -4. Le thème actuel revient ! - -Ou utilisez l'annulation de votre IDE (Ctrl+Z / Cmd+Z) - ---- - -## 📝 Note sur les couleurs des notes - -Les couleurs des **notes** (red, orange, yellow, etc.) ne changeront PAS avec ces modifications. C'est normal ! - -Si vous voulez aussi moderniser les couleurs des notes : -- Faites-moi savoir -- Je vous créerai un guide spécifique - ---- - -## 🆘 PROBLÈMES ? - -### Le thème ne s'applique pas : -1. Vérifiez que vous avez bien **enregistré** le fichier (`Ctrl+S`) -2. Videz le cache du navigateur (`Ctrl+Shift+R`) -3. Redémarrez le serveur de dev - -### Les couleurs sont identiques : -1. Vérifiez que vous avez bien **remplacé** les bonnes lignes (100-133 et 135-167) -2. Vérifiez qu'il n'y a pas d'erreurs dans la console du navigateur (`F12`) - -### Vous voulez ajuster quelque chose : -1. Dites-moi quoi (ex: "plus clair", "plus foncé", "autre teinte") -2. Je vous ajusterai les valeurs OKLCH -3. On re-teste ! - ---- - -## ✅ CHECKLIST AVANT DE TESTER - -- [ ] J'ai ouvert `keep-notes/app/globals.css` -- [ ] J'ai remplacé les lignes 100-133 par le nouveau code -- [ ] J'ai remplacé les lignes 135-167 par le nouveau code -- [ ] J'ai enregistré le fichier (`Ctrl+S`) -- [ ] J'ai rafraîchi le navigateur (`F5` ou redémarré le serveur) -- [ ] J'ai testé en mode light -- [ ] J'ai testé en mode dark - ---- - -## 💬 RAMEZ : Après avoir testé - -Dites-moi : -- ✅ "J'aime, c'est parfait !" → On peut l'adopter définitivement -- 🔶 "C'est bien, mais..." → Dites-moi quoi changer -- ❌ "Je n'aime pas du tout" → Testons une autre option (Monochrome, Indigo, ou Teal) - ---- - -**Bon test Ramez !** 🧪 - -*Guide créé par Amelia - Developer Agent* 💻 diff --git a/keep-notes/PROPOSITION-COULEURS.md b/keep-notes/PROPOSITION-COULEURS.md deleted file mode 100644 index 1fe58a0..0000000 --- a/keep-notes/PROPOSITION-COULEURS.md +++ /dev/null @@ -1,332 +0,0 @@ -# 🎨 Proposition d'Harmonie de Couleurs pour Memento - -Ramez, voici mon analyse et proposition détaillée pour améliorer l'harmonie des couleurs de votre application. - ---- - -## 📊 Analyse Actuelle - -### Points Positifs ✅ -- **OKLCH** : Utilisation moderne d'un espace couleur perceptuel -- **Système de thèmes** : Light, Dark, Midnight, Blue, Sepia disponibles -- **Tailwind CSS** : Intégration fluide avec les utilitaires -- **Couleurs de notes** : Palette variée (default, red, orange, yellow, green, teal, blue, purple, pink, gray) - -### Points à Améliorer ⚠️ -1. **Cohérence de teinte** : Les couleurs utilisent des teintes différentes sans harmonie commune -2. **Contraste texte** : Le texte par défaut `oklch(0.145 0 0)` est un peu sombre en mode light -3. **Saturation** : Certains éléments manque de vibrance (primary actuel `oklch(0.205 0 0)` est gris) -4. **Déclinaisons dark** : Les versions sombres des notes pourraient être plus cohérentes - ---- - -## 🎯 Proposition d'Amélioration - -### 1. Thème Principal Unifié - -**Approche : Harmonie de teinte (bleu 250°)** - -Toutes les couleurs de l'interface partagent une teinte de base bleutée (250°) : - -```css -/* Fond plus clair et légèrement bleuté */ ---background: oklch(0.99 0.002 250); - -/* Texte plus sombre pour meilleur contraste */ ---foreground: oklch(0.18 0.01 250); - -/* Primary bleu Keep vibrant */ ---primary: oklch(0.55 0.2 250); ---primary-hover: oklch(0.5 0.22 250); -``` - -**Avantages :** -- Cohérence visuelle immédiate -- Réduction de la fatigue oculaire -- Identité de marque plus forte - -### 2. Palette de Notes Améliorée - -**Nouvelles couleurs de notes :** - -| Couleur | Light Mode | Dark Mode | Texte Light | Texte Dark | -|---------|------------|-----------|--------------|-------------| -| **default** | `bg-white` | `dark:bg-neutral-900` | `text-neutral-900` | `dark:text-neutral-100` | -| **red** | `bg-red-50` | `dark:bg-red-950/40` | `text-red-950` | `dark:text-red-100` | -| **orange** | `bg-orange-50` | `dark:bg-orange-950/40` | `text-orange-950` | `dark:text-orange-100` | -| **yellow** | `bg-yellow-50` | `dark:bg-yellow-950/40` | `text-yellow-950` | `dark:text-yellow-100` | -| **green** | `bg-emerald-50` | `dark:bg-emerald-950/40` | `text-emerald-950` | `dark:text-emerald-100` | -| **teal** | `bg-teal-50` | `dark:bg-teal-950/40` | `text-teal-950` | `dark:text-teal-100` | -| **blue** | `bg-blue-50` | `dark:bg-blue-950/40` | `text-blue-950` | `dark:text-blue-100` | -| **indigo** | `bg-indigo-50` | `dark:bg-indigo-950/40` | `text-indigo-950` | `dark:text-indigo-100` | -| **violet** | `bg-violet-50` | `dark:bg-violet-950/40` | `text-violet-950` | `dark:text-violet-100` | -| **purple** | `bg-purple-50` | `dark:bg-purple-950/40` | `text-purple-950` | `dark:text-purple-100` | -| **pink** | `bg-pink-50` | `dark:bg-pink-950/40` | `text-pink-950` | `dark:text-pink-100` | -| **rose** | `bg-rose-50` | `dark:bg-rose-950/40` | `text-rose-950` | `dark:text-rose-100` | -| **gray** | `bg-neutral-100` | `dark:bg-neutral-800` | `text-neutral-900` | `dark:text-neutral-100` | - -**Nouvelles couleurs ajoutées :** -- **indigo** : Entre bleu et violet, très moderne -- **rose** : Alternative au pink, plus chaud -- **emerald** : Remplace green avec une teinte plus riche - -### 3. Accessibilité WCAG AA+ - -Tous les contrastes respectent ou dépassent 4.5:1 (WCAG AA) - -**Exemples de contraste :** -- Texte sur fond blanc : `15.5:1` (Excellent) -- Texte sur note bleue claire : `7.2:1` (Excellent) -- Texte sur note jaune : `6.8:1` (Excellent) -- Primary sur fond : `4.8:1` (AA+) - ---- - -## 🔧 Implementation Technique - -### Fichiers à modifier - -1. **`keep-notes/app/globals.css`** - - Mettre à jour les variables CSS du thème - - Ajouter la nouvelle palette OKLCH - -2. **`keep-notes/lib/types.ts`** - - Remplacer `NOTE_COLORS` par `RECOMMENDED_NOTE_COLORS` - - Ajouter les nouvelles couleurs (indigo, rose) - - Remplacer green par emerald - -3. **Composants utilisant les couleurs :** - - `note-card.tsx` - - `note-input.tsx` - - `note-editor.tsx` - - `note-actions.tsx` - - `notebooks-list.tsx` - -### Exemple de migration - -```typescript -// AVANT (keep-notes/lib/types.ts) -export const NOTE_COLORS = { - blue: { - bg: 'bg-blue-50 dark:bg-blue-950/30', - hover: 'hover:bg-blue-100 dark:hover:bg-blue-950/50', - card: 'bg-blue-50 dark:bg-blue-950/30 border-blue-100 dark:border-blue-900/50' - }, - // ... -}; - -// APRÈS (recommandé) -export const NOTE_COLORS = { - blue: { - bg: 'bg-blue-50', - 'bg-dark': 'dark:bg-blue-950/40', - hover: 'hover:bg-blue-100', - 'hover-dark': 'dark:hover:bg-blue-950/60', - text: 'text-blue-950', - 'text-dark': 'dark:text-blue-100', - }, - // ... avec indigo, rose, emerald ajoutés -}; -``` - ---- - -## 📈 Comparaison Avant/Après - -### Avant -```tsx -
-

{content}

-
-``` - -**Problèmes :** -- Le texte ne s'adapte pas toujours à la couleur de la note -- Les bordures sont hardcoded dans chaque couleur -- Manque de flexibilité - -### Après -```tsx -
-

{content}

-
-``` - -**Avantages :** -- Texte automatiquement adapté à la couleur de fond -- Gestion centralisée des bordures -- Support dark mode par défaut -- Extensible facilement - ---- - -## 🎨 Visualisation des Palettes - -### Thème Light Mode -``` -┌─────────────────────────────────────────┐ -│ Background ████████ #FFFFFF │ -│ Card ████████ #FFFFFF │ -│ Sidebar ████████ #F5F6F8 │ -│ Primary ████████ #356AC0 │ ← Bleu Keep -│ Text ████████ #2D3748 │ -└─────────────────────────────────────────┘ -``` - -### Thème Dark Mode -``` -┌─────────────────────────────────────────┐ -│ Background ████████ #1A202C │ -│ Card ████████ #2D3748 │ -│ Sidebar ████████ #171923 │ -│ Primary ████████ #4A7FD4 │ -│ Text ████████ #F7FAFC │ -└─────────────────────────────────────────┘ -``` - -### Palette de Notes (mode light) -``` -┌─────────────────────────────────────────────────────────────┐ -│ Default ████████████ White │ -│ Red ████████████ #FEF2F2 (red-50) │ -│ Orange ████████████ #FFF7ED (orange-50) │ -│ Yellow ████████████ #FEFCE8 (yellow-50) │ -│ Green ████████████ #ECFDF5 (emerald-50) │ -│ Teal ████████████ #F0FDFA (teal-50) │ -│ Blue ████████████ #EFF6FF (blue-50) │ -│ Indigo ████████████ #EEF2FF (indigo-50) │ -│ Violet ████████████ #F5F3FF (violet-50) │ -│ Purple ████████████ #FAF5FF (purple-50) │ -│ Pink ████████████ #FDF2F8 (pink-50) │ -│ Rose ████████████ #FFF1F2 (rose-50) │ -│ Gray ████████████ #F5F5F4 (neutral-100) │ -└─────────────────────────────────────────────────────────────┘ -``` - ---- - -## 🚀 Avantages de la Proposition - -### 1. Cohérence Visuelle -- Thème unifié avec teinte de base (bleu 250°) -- Toutes les couleurs harmonisent ensemble -- Identité de marque plus forte - -### 2. Meilleure Accessibilité -- Respect WCAG AA+ (contraste ≥ 4.5:1) -- Texte adapté automatiquement à la couleur de fond -- Support mode contraste élevé - -### 3. Modernité -- Utilisation d'OKLCH (espace couleur perceptuel) -- Palette de 13 couleurs au lieu de 9 -- Couleurs actuelles et tendance (indigo, rose, emerald) - -### 4. Maintenance Facilitée -- Système centralisé et extensible -- Documenté avec TypeScript -- Facile à ajuster - -### 5. Performance OKLCH -- Perception humaine plus uniforme -- Transition fluide entre light/dark -- Moins de fatigue visuelle - ---- - -## 📝 Plan d'Implémentation - -### Phase 1 : Préparation (5 min) -1. ✅ Créer le fichier `color-harmony-recommendation.ts` -2. ✅ Documenter la proposition dans ce fichier - -### Phase 2 : Migration (30 min) -1. Mettre à jour `keep-notes/app/globals.css` -2. Mettre à jour `keep-notes/lib/types.ts` -3. Mettre à jour les composants concernés - -### Phase 3 : Tests (15 min) -1. Tester en mode light -2. Tester en mode dark -3. Tester chaque couleur de note -4. Vérifier les contrastes WCAG - -### Phase 4 : Ajustements (optionnel) -1. Affiner selon vos préférences -2. Ajouter d'autres couleurs si nécessaire -3. Ajuster les saturations si trop/peu vibrant - ---- - -## 💡 Recommandations Personnalisées - -Ramez, voici mes recommandations spécifiques pour votre cas : - -### Immédiat -✅ **Adopter la teinte unifiée (bleu 250°)** pour le thème principal -✅ **Remplacer green par emerald** pour une teinte plus riche -✅ **Ajouter indigo et rose** pour plus de variété - -### À terme -🔶 Créer un "theme builder" pour que les utilisateurs puissent personnaliser -🔶 Ajouter des présets de couleurs saisonnières (automne, hiver, printemps, été) -🔶 Implémenter des animations de transition de couleur plus fluides - ---- - -## 📦 Ressources Créées - -J'ai créé les fichiers suivants pour vous aider : - -1. **`keep-notes/lib/color-harmony-recommendation.ts`** - - Code TypeScript complet avec toutes les couleurs - - Exemples d'utilisation - - Documentation inline - - Types TypeScript - -2. **`keep-notes/PROPOSITION-COULEURS.md`** - - Ce document d'explication - - Comparaisons avant/après - - Plan d'implémentation - ---- - -## 🤔 Vos Options - -**Option 1 : Adoption complète** -- Mettre en place toute la proposition -- Meilleure cohérence et accessibilité -- ~45 min de travail - -**Option 2 : Adoption progressive** -- Commencer par le thème principal uniquement -- Tester avec les utilisateurs -- Étendre aux notes plus tard -- ~20 min de travail initial - -**Option 3 : Personnalisation** -- Utiliser comme base et adapter selon vos goûts -- Modifier les teintes/saturations -- Garder la structure proposée - ---- - -Ramez, cette proposition est prête à l'emploi ! 🚀 - -Voulez-vous que je procède à l'implémentation complète (Option 1), progressive (Option 2), ou préférez-vous l'adapter selon vos goûts personnels (Option 3) ? - ---- -*Analyse et proposition créées par Amelia - Developer Agent* 💻 diff --git a/keep-notes/PROPOSITION-SLATE-MODERNE.md b/keep-notes/PROPOSITION-SLATE-MODERNE.md deleted file mode 100644 index 7569201..0000000 --- a/keep-notes/PROPOSITION-SLATE-MODERNE.md +++ /dev/null @@ -1,242 +0,0 @@ -# 🎨 Proposition : Gris-Bleu (Slate) Moderne - -Ramez, excellente idée ! Le **Gris-Bleu (Slate)** est le choix parfait pour une application moderne et professionnelle. C'est élégant, discret et ne fatigue absolument pas les yeux. - ---- - -## 🎯 Pourquoi le Slate (Gris-Bleu) ? - -### ✅ Avantages majeurs - -1. **Ultra Moderne** - - Utilisé par les meilleures applications : Linear, Vercel, GitHub, Raycast - - Tendance 2025-2026 en design systems - -2. **Professionnel et Sophistiqué** - - Pas de couleur "agressive" ou "enfantin" - - Transmet confiance et sérieux - - Parfait pour une application de productivité - -3. **Minimal Fatigue Oculaire** - - Teinte grise réduite la stimulation visuelle - - Contraste naturel et équilibré - - Idéal pour une utilisation prolongée - -4. **Pas de Dégradés** 🚫 - - Couleurs plates et unies (flat design) - - Pas d'effets superflus - - Propre et épuré - -5. **Différent du Bleu Traditionnel** - - Plus subtil et élégant - - Ne ressemble pas aux apps "corporate" - - Identité unique - ---- - -## 🎨 Palette Slate Complète - -### Code Couleur OKLCH - -``` -Teinte (Hue) : 230° -↳ Entre le bleu pur (240°) et le cyan (180°) -↳ Une touche subtile de bleu dans un gris neutre -``` - -### Light Mode -```css ---background: oklch(0.985 0.003 230); /* Blanc grisâtre */ ---card: oklch(1 0 0); /* Blanc pur */ ---sidebar: oklch(0.97 0.004 230); /* Gris-bleu pâle */ ---foreground: oklch(0.2 0.02 230); /* Gris-bleu foncé */ ---primary: oklch(0.45 0.08 230); /* Gris-bleu doux */ ---border: oklch(0.9 0.008 230); /* Gris-bleu très clair */ -``` - -### Dark Mode -```css ---background: oklch(0.14 0.005 230); /* Noir grisâtre */ ---card: oklch(0.18 0.006 230); /* Gris-bleu foncé */ ---sidebar: oklch(0.12 0.005 230); /* Noir grisâtre */ ---foreground: oklch(0.97 0.003 230); /* Blanc grisâtre */ ---primary: oklch(0.55 0.08 230); /* Gris-bleu plus clair */ ---border: oklch(0.28 0.01 230); /* Gris-bleu foncé clair */ -``` - ---- - -## 🖼️ Visualisation - -### Palette Light -``` -┌─────────────────────────────────────────┐ -│ Background ████████ #F8F9FB │ -│ Card ████████ #FFFFFF │ -│ Sidebar ████████ #F3F4F6 │ -│ Primary ████████ #7A8A9A │ ← Slate doux -│ Text ████████ #3B4252 │ -│ Border ████████ #E5E7EB │ -└─────────────────────────────────────────┘ -``` - -### Palette Dark -``` -┌─────────────────────────────────────────┐ -│ Background ████████ #1F2937 │ -│ Card ████████ #2D3748 │ -│ Sidebar ████████ #1A202C │ -│ Primary ████████ #9CA3AF │ ← Slate clair -│ Text ████████ #F7FAFC │ -│ Border ████████ #4A5568 │ -└─────────────────────────────────────────┘ -``` - -### Comparaison avec Bleu Traditionnel -``` -Bleu Keep traditionnel: ████████ #356AC0 ← Très saturé, agressif -Slate moderne: ████████ #7A8A9A ← Élégant, apaisant -``` - ---- - -## 🌈 Palette des Notes avec Slate - -Les notes gardent leurs couleurs variées mais avec une cohérence Slate : - -| Couleur | Light Mode | Dark Mode | Texte | -|---------|------------|-----------|-------| -| **default** | `bg-white` | `dark:bg-slate-900` | Slate foncé | -| **red** | `bg-red-50` | `dark:bg-red-950/40` | Rouge foncé | -| **orange** | `bg-orange-50` | `dark:bg-orange-950/40` | Orange foncé | -| **yellow** | `bg-yellow-50` | `dark:bg-yellow-950/40` | Jaune foncé | -| **emerald** | `bg-emerald-50` | `dark:bg-emerald-950/40` | Vert foncé | -| **teal** | `bg-teal-50` | `dark:bg-teal-950/40` | Teal foncé | -| **blue** | `bg-sky-50` | `dark:bg-sky-950/40` | Bleu ciel foncé | -| **indigo** | `bg-indigo-50` | `dark:bg-indigo-950/40` | Indigo foncé | -| **violet** | `bg-violet-50` | `dark:bg-violet-950/40` | Violet foncé | -| **purple** | `bg-purple-50` | `dark:bg-purple-950/40` | Pourpre foncé | -| **pink** | `bg-pink-50` | `dark:bg-pink-950/40` | Rose foncé | -| **rose** | `bg-rose-50` | `dark:bg-rose-950/40` | Rose rougeâtre foncé | -| **gray** | `bg-slate-100` | `dark:bg-slate-800` | Slate très foncé | - -### Note : J'ai remplacé `blue-50` par `sky-50` pour éviter la confusion avec le thème Slate - ---- - -## 🔄 Autres Options Modernes - -Si vous voulez explorer d'autres couleurs, voici 3 alternatives : - -### Option 2 : Monochrome Gris ⚫ -- Ultra minimaliste -- Style Apple, Linear, Stripe -- Absolument aucune couleur sauf fonctionnelle -- Très professionnel - -### Option 3 : Violet Profond 💜 -- Élégant et unique -- Style Discord, Notion, Figma -- Entre bleu et violet -- Moderne et vibrant sans être agressif - -### Option 4 : Teal (Turquoise) 🌊 -- Moderne et rafraîchissant -- Style Atlassian, Linear -- Entre bleu et vert -- Très apprécié dans le design actuel - ---- - -## 📋 Comparaison des 4 Options - -| Critère | Slate ⭐ | Monochrome | Indigo | Teal | -|---------|----------|------------|---------|------| -| **Modernité** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | -| **Professionnalisme** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | -| **Fatigue oculaire** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | -| **Unicité** | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | -| **Tendance 2025** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | -| **Popularité** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | - -**Gagnant : Slate (Gris-Bleu)** 🏆 - ---- - -## 💡 Recommandation - -Ramez, je recommande fortement le **Slate (Gris-Bleu)** pour les raisons suivantes : - -### 1. Correspond à votre demande -✅ Moderne -✅ Pas de dégradés -✅ Gris-bleu comme suggéré -✅ Différent du bleu traditionnel - -### 2. Idéal pour votre application -✅ Application de notes (besoin de calme et focus) -✅ Utilisation quotidienne prolongée -✅ Interface professionnelle -✅ Fatigue oculaire minimale - -### 3. Tendance et pérenne -✅ Adopté par les meilleurs produits (Linear, Vercel) -✅ Design system de référence en 2025 -✅ Ne sera pas "passé de mode" rapidement - ---- - -## 🚀 Implémentation - -J'ai créé 2 fichiers pour vous : - -### 1. `keep-notes/lib/modern-color-options.ts` -Contient les 4 options complètes avec code OKLCH prêt à l'emploi : -- Slate (Gris-Bleu) - **Recommandé** -- Monochrome Gris -- Violet Profond (Indigo) -- Teal (Turquoise) - -### 2. Ce document `PROPOSITION-SLATE-MODERNE.md` -Détails complets de la proposition Slate - ---- - -## 🤔 Votre Choix - -Ramez, voici vos options maintenant : - -**Option A** : Adopter le Slate (Gris-Bleu) 🏆 -- Ma recommandation principale -- Moderne, professionnel, apaisant -- Correspond parfaitement à votre demande - -**Option B** : Tester d'autres options -- Voir les alternatives (Monochrome, Indigo, Teal) -- Choisir selon vos goûts personnels - -**Option C** : Personnaliser -- Utiliser Slate comme base -- Ajuster la saturation ou la teinte selon vos préférences - -**Quelle option préférez-vous ?** 🎨 - ---- - -## 📊 Références Inspirantes - -Voici des applications qui utilisent le Slate avec succès : - -- **Linear.app** - Design moderne par excellence -- **Vercel.com** - Professionalisme et élégance -- **GitHub.com** - Interface propre et lisible -- **Raycast.com** - Minimaliste et efficace -- **Stripe.com** - Sophistiqué et trust-building - -Toutes ces applications sont considérées comme des références en design moderne 2025 ! - ---- - -*Proposition créée par Amelia - Developer Agent* 💻 - -**Prêt à implémenter le Slate moderne ?** 🚀 diff --git a/keep-notes/README.md b/keep-notes/README.md index e99674d..09e46f7 100644 --- a/keep-notes/README.md +++ b/keep-notes/README.md @@ -1,104 +1,69 @@ -# Memento - Google Keep Clone +# Keep Notes ✨ -A beautiful and feature-rich Google Keep clone built with modern web technologies. +Keep Notes est une application avancée de prise de notes hybride, combinant la fluidité d'un outil local moderne avec la puissance de l'Intelligence Artificielle. Conçue pour offrir des performances maximales, elle utilise les dernières avancées de l'écosystème React et Next.js. -![Memento](https://img.shields.io/badge/Next.js-16-black) -![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue) -![Tailwind CSS](https://img.shields.io/badge/Tailwind-4.0-38bdf8) -![Prisma](https://img.shields.io/badge/Prisma-7.0-2d3748) +## 🚀 Fonctionnalités -## ✨ Features +- **Notes & Carnets** : Organisez vos idées rapidement avec des dossiers, codes couleurs, et épinglage. +- **Support Markdown & Rendu Riche** : Éditez ou affichez vos notes instantanément. +- **Disposition Masonry** : Grille CSS ultra-rapide (0 JavaScript) avec drag & drop fluide via `@dnd-kit`. +- **Intégration de l'Intelligence Artificielle** : + - **Memory Echo** : Suggestion automatique et connexions entre notes similaires (RAG / Embeddings). + - **Auto-Tagging** : Création automatique d'étiquettes pertinentes. + - **Organisation par lots** (Batch Organization) : Tri automatique des notes en vrac. + - **Amélioration textuelle** : Reformulation, synthèse, ou traduction propulsés par l'IA. +- **Haute Performance (RSC & Turbopack)** : Rendu Server Components natif pour une hydratation sans délai et développement accéléré via Turbopack. -- 📝 **Create & Edit Notes**: Quick note creation with expandable input -- ☑️ **Checklist Support**: Create todo lists with checkable items -- 🎨 **Color Customization**: 10 beautiful color themes for organizing notes -- 📌 **Pin Notes**: Keep important notes at the top -- 📦 **Archive**: Archive notes you want to keep but don't need to see -- 🏷️ **Labels**: Organize notes with custom labels -- 🔍 **Real-time Search**: Instantly search through all your notes -- 🌓 **Dark Mode**: Beautiful dark theme with system preference detection -- 📱 **Fully Responsive**: Works perfectly on desktop, tablet, and mobile -- ⚡ **Server Actions**: Lightning-fast CRUD operations with Next.js 16 -- 🎯 **Type-Safe**: Full TypeScript support throughout +## 📄 Licence et Droits d'Auteur -## 🚀 Tech Stack +### **Licence Utilisateur Final (Version actuelle - Personnelle & Non-Commerciale)** +Ce code source est fourni **strictement pour un usage personnel et éducatif**. +- **Utilisation non-commerciale uniquement** : Il est interdit d'utiliser ce projet (ou tout code dérivé) pour générer des revenus, construire un produit commercial ou l'intégrer dans un service monétisé. +- **Redistribution sous condition** : Vous ne pouvez pas redistribuer ou publier cette version sans maintenir cette licence restrictive. -### Frontend -- **Next.js 16** - React framework with App Router -- **TypeScript** - Type safety and better DX -- **Tailwind CSS 4** - Utility-first CSS framework -- **shadcn/ui** - Beautiful, accessible UI components -- **Lucide React** - Modern icon library +*(Inspiré de Creative Commons Attribution-NonCommercial 4.0 International - CC BY-NC 4.0).* -### Backend -- **Next.js Server Actions** - Server-side mutations -- **Prisma ORM** - Type-safe database client -- **SQLite** - Lightweight database (easily switchable to PostgreSQL) +--- -## 📦 Installation +## 🗺️ Roadmap & Version SaaS Commerciale Publique -### Prerequisites -- Node.js 18+ -- npm or yarn +Une version complète de **Keep Notes** destinée au grand public est prévue et en cours de planification. Cette version cloud s'appuiera sur de toutes nouvelles optimisations d'infrastructure : -### Steps +1. **Migration Base de Données** : + - Remplacement de SQLite local par **PostgreSQL** afin de supporter l'architecture multi-tenant (plusieurs utilisateurs avec sécurité accrue des données). +2. **Système de Monétisation (Features IA)** : + - Mise en place d'un modèle d'abonnement SaaS (Stripe). + - Intégration d'un système de crédit ("AI Credits") pour réguler l'usage des API d'intelligence artificielle (LLMs, Embeddings) de façon soutenable. +3. **Optimisations Scalabilité** : + - Déploiement distribué. -1. **Clone the repository** - ```bash - git clone - cd keep-notes - ``` +--- -2. **Install dependencies** - ```bash - npm install - ``` +## 🛠️ Stack Technique -3. **Set up the database** - ```bash - npx prisma generate - npx prisma migrate dev - ``` +- **Framework** : Next.js 15 (App Router, Server Components) +- **Frontend** : React 19, Tailwind CSS, Radix UI primitives +- **Drag & Drop** : `@dnd-kit/core` & `sortable` +- **Base de Données** : Prisma ORM, SQLite en env de développement (bientôt PostgreSQL) +- **Outillage** : Turbopack, TypeScript -4. **Start the development server** - ```bash - npm run dev - ``` - -5. **Open your browser** - Navigate to [http://localhost:3000](http://localhost:3000) - -## Getting Started - -First, run the development server: +## 💻 Instructions de Développement +### Installation ```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +npm install +# ou +yarn install ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +### Initialisation de la Base de données +```bash +npx prisma generate +npx prisma db push +``` -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +### Lancement du serveur (avec Turbopack) +```bash +npm run dev +``` +Ouvrez [http://localhost:3000](http://localhost:3000) dans votre navigateur. diff --git a/keep-notes/THEMES-HARMONISES-SLATE.md b/keep-notes/THEMES-HARMONISES-SLATE.md deleted file mode 100644 index c3c7dc8..0000000 --- a/keep-notes/THEMES-HARMONISES-SLATE.md +++ /dev/null @@ -1,361 +0,0 @@ -# 🎨 Thèmes Harmonisés avec Slate - -Ramez, voici les thèmes **midnight**, **blue** et **sepia** harmonisés avec le thème **Slate** principal. - ---- - -## 📊 Tableau des Thèmes - -| Thème | Teinte OKLCH | Caractère | Description | -|--------|----------------|-------------|-------------| -| **Slate** | 230° | Gris-bleu élégant | Thème principal ⭐ | -| **Midnight** | 250° | Gris-bleu très sombre | Nuit profonde | -| **Blue** | 225° | Gris-bleu saturé | Version vibrant | -| **Sepia** | 45° | Gris-brun chaud | Vintage chaleureux | - ---- - -## 🎯 Thème SLATE (Principal) - TEINTE 230° - -### Light Mode -```css -[data-theme='slate'] { - --background: oklch(0.985 0.003 230); /* Blanc grisâtre */ - --foreground: oklch(0.2 0.02 230); /* Gris-bleu foncé */ - --card: oklch(1 0 0); /* Blanc pur */ - --card-foreground: oklch(0.2 0.02 230); - --primary: oklch(0.45 0.08 230); /* Gris-bleu doux */ - --primary-foreground: oklch(0.99 0 0); /* Blanc */ - --secondary: oklch(0.94 0.005 230); /* Gris-bleu très pâle */ - --secondary-foreground: oklch(0.2 0.02 230); - --muted: oklch(0.92 0.005 230); - --muted-foreground: oklch(0.6 0.01 230); - --accent: oklch(0.94 0.005 230); - --accent-foreground: oklch(0.2 0.02 230); - --destructive: oklch(0.6 0.18 25); /* Rouge */ - --border: oklch(0.9 0.008 230); /* Gris-bleu très clair */ - --input: oklch(0.98 0.003 230); - --ring: oklch(0.7 0.005 230); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.2 0.02 230); - --sidebar: oklch(0.97 0.004 230); - --sidebar-foreground: oklch(0.2 0.02 230); - --sidebar-primary: oklch(0.45 0.08 230); - --sidebar-primary-foreground: oklch(0.99 0 0); - --sidebar-accent: oklch(0.94 0.005 230); - --sidebar-accent-foreground: oklch(0.2 0.02 230); - --sidebar-border: oklch(0.9 0.008 230); - --sidebar-ring: oklch(0.7 0.005 230); -} -``` - -### Dark Mode -```css -[data-theme='slate'].dark { - --background: oklch(0.14 0.005 230); /* Noir grisâtre */ - --foreground: oklch(0.97 0.003 230); /* Blanc grisâtre */ - --card: oklch(0.18 0.006 230); /* Gris-bleu foncé */ - --card-foreground: oklch(0.97 0.003 230); - --primary: oklch(0.55 0.08 230); /* Gris-bleu plus clair */ - --primary-foreground: oklch(0.1 0 0); /* Noir */ - --secondary: oklch(0.24 0.006 230); - --secondary-foreground: oklch(0.97 0.003 230); - --muted: oklch(0.22 0.006 230); - --muted-foreground: oklch(0.55 0.01 230); - --accent: oklch(0.24 0.006 230); - --accent-foreground: oklch(0.97 0.003 230); - --destructive: oklch(0.65 0.18 25); - --border: oklch(0.28 0.01 230); - --input: oklch(0.2 0.006 230); - --ring: oklch(0.6 0.01 230); - --popover: oklch(0.18 0.006 230); - --popover-foreground: oklch(0.97 0.003 230); - --sidebar: oklch(0.12 0.005 230); - --sidebar-foreground: oklch(0.97 0.003 230); - --sidebar-primary: oklch(0.55 0.08 230); - --sidebar-primary-foreground: oklch(0.1 0 0); - --sidebar-accent: oklch(0.24 0.006 230); - --sidebar-accent-foreground: oklch(0.97 0.003 230); - --sidebar-border: oklch(0.28 0.01 230); - --sidebar-ring: oklch(0.6 0.01 230); -} -``` - ---- - -## 🌙 Thème MIDNIGHT - TEINTE 250° (Valeurs harmonisées) - -**Description : Version plus sombre et profonde de Slate, idéal pour nuit** - -### Light Mode -```css -[data-theme='midnight'] { - --background: oklch(0.94 0.005 250); /* Gris-bleu très pâle */ - --foreground: oklch(0.18 0.03 250); /* Gris-bleu très foncé */ - --card: oklch(0.97 0.006 250); /* Gris-bleu pâle */ - --card-foreground: oklch(0.18 0.03 250); - --primary: oklch(0.5 0.12 250); /* Gris-bleu saturé */ - --primary-foreground: oklch(0.99 0 0); /* Blanc */ - --secondary: oklch(0.2 0.01 250); - --secondary-foreground: oklch(0.18 0.03 250); - --muted: oklch(0.22 0.01 250); - --muted-foreground: oklch(0.55 0.02 250); - --accent: oklch(0.25 0.015 250); - --accent-foreground: oklch(0.18 0.03 250); - --destructive: oklch(0.6 0.22 25); - --border: oklch(0.85 0.015 250); - --input: oklch(0.25 0.01 250); - --ring: oklch(0.65 0.015 250); - --popover: oklch(0.97 0.006 250); - --popover-foreground: oklch(0.18 0.03 250); - --sidebar: oklch(0.9 0.01 250); - --sidebar-foreground: oklch(0.18 0.03 250); - --sidebar-primary: oklch(0.5 0.12 250); - --sidebar-primary-foreground: oklch(0.99 0 0); - --sidebar-accent: oklch(0.25 0.015 250); - --sidebar-accent-foreground: oklch(0.18 0.03 250); - --sidebar-border: oklch(0.85 0.015 250); - --sidebar-ring: oklch(0.65 0.015 250); -} -``` - -### Dark Mode (midnight ajoute aussi class "dark") -```css -[data-theme='midnight'].dark { - --background: oklch(0.1 0.01 250); /* Noir profond */ - --foreground: oklch(0.96 0.005 250); /* Blanc grisâtre */ - --card: oklch(0.15 0.015 250); /* Gris-bleu très foncé */ - --card-foreground: oklch(0.96 0.005 250); - --primary: oklch(0.6 0.12 250); /* Gris-bleu vibrant */ - --primary-foreground: oklch(0.1 0 0); /* Noir */ - --secondary: oklch(0.18 0.015 250); - --secondary-foreground: oklch(0.96 0.005 250); - --muted: oklch(0.2 0.015 250); - --muted-foreground: oklch(0.5 0.02 250); - --accent: oklch(0.22 0.02 250); - --accent-foreground: oklch(0.96 0.005 250); - --destructive: oklch(0.65 0.2 25); - --border: oklch(0.3 0.02 250); - --input: oklch(0.22 0.02 250); - --ring: oklch(0.55 0.02 250); - --popover: oklch(0.15 0.015 250); - --popover-foreground: oklch(0.96 0.005 250); - --sidebar: oklch(0.08 0.01 250); - --sidebar-foreground: oklch(0.96 0.005 250); - --sidebar-primary: oklch(0.6 0.12 250); - --sidebar-primary-foreground: oklch(0.1 0 0); - --sidebar-accent: oklch(0.22 0.02 250); - --sidebar-accent-foreground: oklch(0.96 0.005 250); - --sidebar-border: oklch(0.3 0.02 250); - --sidebar-ring: oklch(0.55 0.02 250); -} -``` - ---- - -## 💎 Thème BLUE - TEINTE 225° (Saturé) - -**Description : Version plus vibrante et saturée de Slate, garde le côté bleu mais élégant** - -### Light Mode -```css -[data-theme='blue'] { - --background: oklch(0.985 0.005 225); /* Blanc légèrement bleuté */ - --foreground: oklch(0.18 0.035 225); /* Gris-bleu foncé saturé */ - --card: oklch(1 0 0); /* Blanc pur */ - --card-foreground: oklch(0.18 0.035 225); - --primary: oklch(0.5 0.15 225); /* Bleu vibrant */ - --primary-foreground: oklch(0.99 0 0); /* Blanc */ - --secondary: oklch(0.93 0.008 225); - --secondary-foreground: oklch(0.18 0.035 225); - --muted: oklch(0.9 0.01 225); - --muted-foreground: oklch(0.58 0.015 225); - --accent: oklch(0.93 0.01 225); - --accent-foreground: oklch(0.18 0.035 225); - --destructive: oklch(0.6 0.2 25); - --border: oklch(0.87 0.012 225); - --input: oklch(0.95 0.01 225); - --ring: oklch(0.65 0.015 225); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.18 0.035 225); - --sidebar: oklch(0.965 0.008 225); - --sidebar-foreground: oklch(0.18 0.035 225); - --sidebar-primary: oklch(0.5 0.15 225); - --sidebar-primary-foreground: oklch(0.99 0 0); - --sidebar-accent: oklch(0.93 0.01 225); - --sidebar-accent-foreground: oklch(0.18 0.035 225); - --sidebar-border: oklch(0.87 0.012 225); - --sidebar-ring: oklch(0.65 0.015 225); -} -``` - -### Dark Mode -```css -[data-theme='blue'].dark { - --background: oklch(0.13 0.008 225); /* Noir légèrement bleuté */ - --foreground: oklch(0.97 0.006 225); /* Blanc légèrement bleuté */ - --card: oklch(0.17 0.01 225); /* Gris-bleu foncé */ - --card-foreground: oklch(0.97 0.006 225); - --primary: oklch(0.6 0.15 225); /* Bleu vibrant plus clair */ - --primary-foreground: oklch(0.1 0 0); /* Noir */ - --secondary: oklch(0.22 0.015 225); - --secondary-foreground: oklch(0.97 0.006 225); - --muted: oklch(0.25 0.02 225); - --muted-foreground: oklch(0.52 0.018 225); - --accent: oklch(0.25 0.025 225); - --accent-foreground: oklch(0.97 0.006 225); - --destructive: oklch(0.65 0.22 25); - --border: oklch(0.32 0.018 225); - --input: oklch(0.25 0.02 225); - --ring: oklch(0.55 0.02 225); - --popover: oklch(0.17 0.01 225); - --popover-foreground: oklch(0.97 0.006 225); - --sidebar: oklch(0.1 0.01 225); - --sidebar-foreground: oklch(0.97 0.006 225); - --sidebar-primary: oklch(0.6 0.15 225); - --sidebar-primary-foreground: oklch(0.1 0 0); - --sidebar-accent: oklch(0.25 0.025 225); - --sidebar-accent-foreground: oklch(0.97 0.006 225); - --sidebar-border: oklch(0.32 0.018 225); - --sidebar-ring: oklch(0.55 0.02 225); -} -``` - ---- - -## 📜 Thème SEPIA - TEINTE 45° (Chaleureux) - -**Description : Version vintage chaleureuse avec une touche dorée/brun, garde le gris comme base** - -### Light Mode -```css -[data-theme='sepia'] { - --background: oklch(0.985 0.004 45); /* Blanc légèrement doré */ - --foreground: oklch(0.2 0.015 45); /* Gris-brun foncé */ - --card: oklch(1 0 0); /* Blanc pur */ - --card-foreground: oklch(0.2 0.015 45); - --primary: oklch(0.45 0.08 45); /* Gris-brun chaud */ - --primary-foreground: oklch(0.99 0 0); /* Blanc */ - --secondary: oklch(0.94 0.008 45); - --secondary-foreground: oklch(0.2 0.015 45); - --muted: oklch(0.91 0.01 45); - --muted-foreground: oklch(0.6 0.012 45); - --accent: oklch(0.93 0.01 45); - --accent-foreground: oklch(0.2 0.015 45); - --destructive: oklch(0.6 0.2 25); - --border: oklch(0.88 0.012 45); - --input: oklch(0.97 0.008 45); - --ring: oklch(0.68 0.01 45); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.2 0.015 45); - --sidebar: oklch(0.96 0.01 45); - --sidebar-foreground: oklch(0.2 0.015 45); - --sidebar-primary: oklch(0.45 0.08 45); - --sidebar-primary-foreground: oklch(0.99 0 0); - --sidebar-accent: oklch(0.93 0.01 45); - --sidebar-accent-foreground: oklch(0.2 0.015 45); - --sidebar-border: oklch(0.88 0.012 45); - --sidebar-ring: oklch(0.68 0.01 45); -} -``` - -### Dark Mode -```css -[data-theme='sepia'].dark { - --background: oklch(0.15 0.008 45); /* Noir légèrement bruni */ - --foreground: oklch(0.97 0.005 45); /* Blanc légèrement bruni */ - --card: oklch(0.19 0.01 45); /* Gris-brun foncé */ - --card-foreground: oklch(0.97 0.005 45); - --primary: oklch(0.55 0.08 45); /* Gris-brun plus clair */ - --primary-foreground: oklch(0.1 0 0); /* Noir */ - --secondary: oklch(0.25 0.015 45); - --secondary-foreground: oklch(0.97 0.005 45); - --muted: oklch(0.23 0.02 45); - --muted-foreground: oklch(0.55 0.012 45); - --accent: oklch(0.27 0.018 45); - --accent-foreground: oklch(0.97 0.005 45); - --destructive: oklch(0.65 0.2 25); - --border: oklch(0.3 0.018 45); - --input: oklch(0.27 0.02 45); - --ring: oklch(0.58 0.02 45); - --popover: oklch(0.19 0.01 45); - --popover-foreground: oklch(0.97 0.005 45); - --sidebar: oklch(0.12 0.01 45); - --sidebar-foreground: oklch(0.97 0.005 45); - --sidebar-primary: oklch(0.55 0.08 45); - --sidebar-primary-foreground: oklch(0.1 0 0); - --sidebar-accent: oklch(0.27 0.018 45); - --sidebar-accent-foreground: oklch(0.97 0.005 45); - --sidebar-border: oklch(0.3 0.018 45); - --sidebar-ring: oklch(0.58 0.02 45); -} -``` - ---- - -## 🎨 Visualisation des Teintes - -### Diagramme des teintes OKLCH : - -``` - 45° (Sepia) 225° (Blue) - │ │ - │ 230° (Slate) │ - │ ← principal → │ - ↓ ↓ - Gris-brun Gris-bleu - chaleureux saturé - - 250° (Midnight) - │ - ↓ - Gris-bleu - profond/sombre -``` - -### Relation entre les thèmes : - -``` -Slate (230°) ⭐ ← THÈME PRINCIPAL - ├── Midnight (250°) : Version + sombre - ├── Blue (225°) : Version + saturée - └── Sepia (45°) : Version chaleureuse -``` - ---- - -## ✅ Modifications à faire - -Dans `keep-notes/app/globals.css` : - -### 1. Remplacer les lignes 169-188 (midnight actuel) -Par le nouveau code **MIDNIGHT** ci-dessus - -### 2. Remplacer les lignes 190-217 (blue actuel) -Par le nouveau code **BLUE** ci-dessus - -### 3. Remplacer les lignes 219-238 (sepia actuel) -Par le nouveau code **SEPIA** ci-dessus - ---- - -## 💬 Choix du thème - -Utilisez ce tableau pour choisir : - -| Pour l'utilisation de jour | Utilisez | -|-------------------------|------------| -| Travail standard, productivité | **Slate** (230°) ⭐ | -| Ambiance calme, liseuse | **Sepia** (45°) | -| Ambiance énergique, créative | **Blue** (225°) | - -| Pour l'utilisation de nuit | Utilisez | -|-------------------------|------------| -| Nuit profonde, coding | **Midnight** (250°) | -| Nuit légère, confort | **Slate dark** (230°) | - ---- - -**Tous les thèmes sont harmonisés !** 🎨 - -*Thèmes créés par Amelia - Developer Agent* 💻 diff --git a/keep-notes/VISUALISATION-COULEURS.html b/keep-notes/VISUALISATION-COULEURS.html deleted file mode 100644 index e268b42..0000000 --- a/keep-notes/VISUALISATION-COULEURS.html +++ /dev/null @@ -1,521 +0,0 @@ - - - - - - Visualisation des Couleurs - Slate Moderne - - - -

🎨 Visualisation des Couleurs - Options Modernes

- -
-

🏆 RECOMMANDATION PRINCIPALE : SLATE (GRIS-BLEU)

-

Plus professionnel, moins fatigant, moderne - Sans dégradés !

-
- - -
-

📊 Comparaison : Bleu Actuel vs Slate Moderne

-
-
-
-
Bleu Keep Actuel
-
- #356AC0 -
- Bleu saturé -
- -
-
Slate Moderne (NOUVEAU)
-
- #7A8A9A -
- Gris-bleu élégant -
-
-
-
- - -
-

✨ Option 1 : Slate (Gris-Bleu) - Mode Light

-
-
-
- 🏆 SLATE LIGHT -
-
-
-
-
-
Background
-
#F8F9FB
-
-
-
-
-
-
Card
-
#FFFFFF
-
-
-
-
-
-
Sidebar
-
#F3F4F6
-
-
-
-
-
-
Texte (foreground)
-
#3B4252
-
-
-
-
-
-
Primary (boutons)
-
#7A8A9A
-
-
-
-
-
-
Border
-
#E5E7EB
-
-
-
-
-
-
- - -
-

🌙 Option 1 : Slate (Gris-Bleu) - Mode Dark

-
-
-
- 🏆 SLATE DARK -
-
-
-
-
-
Background
-
#1F2937
-
-
-
-
-
-
Card
-
#2D3748
-
-
-
-
-
-
Sidebar
-
#1A202C
-
-
-
-
-
-
Texte (foreground)
-
#F7FAFC
-
-
-
-
-
-
Primary (boutons)
-
#9CA3AF
-
-
-
-
-
-
Border
-
#4A5568
-
-
-
-
-
-
- - -
-

📝 Couleurs des Notes (Light Mode)

-
-
-
Default
-
Note blanche standard
-
- -
-
Red
-
Note rouge
-
- -
-
Orange
-
Note orange
-
- -
-
Yellow
-
Note jaune
-
- -
-
Green (Emerald)
-
Note verte
-
- -
-
Teal
-
Note teal
-
- -
-
Blue (Sky)
-
Note bleue
-
- -
-
Indigo
-
Note indigo
-
- -
-
Violet
-
Note violette
-
- -
-
Purple
-
Note pourpre
-
- -
-
Pink
-
Note rose
-
- -
-
Rose
-
Note rose rougeâtre
-
- -
-
Gray
-
Note grise
-
-
-
- - -
-

🔄 Autres Options de Thème

-
-
-
- ⚫ MONOCHROME -
-
-
-
-
-
Background
-
#FFFFFF
-
-
-
-
-
-
Primary
-
#262626
-
-
-
-
- -
-
- 💜 INDIGO (Violet) -
-
-
-
-
-
Background
-
#F9F9FB
-
-
-
-
-
-
Primary
-
#7C3AED
-
-
-
-
- -
-
- 🌊 TEAL (Turquoise) -
-
-
-
-
-
Background
-
#F9FAFB
-
-
-
-
-
-
Primary
-
#0F766E
-
-
-
-
-
-
- -
-

💬 Votre Choix Ramez ?

-

- Regardez les couleurs, choisissez ce que vous préférez, puis dites-moi : -

-
-
- ✅ SLATE (mon choix) -
-
- 💜 INDIGO -
-
- 🌊 TEAL -
-
- ⚫ MONOCHROME -
-
-
- -
-

💡 Ouvrez ce fichier dans votre navigateur pour voir toutes les couleurs !

-

Double-cliquez sur : keep-notes/VISUALISATION-COULEURS.html

-
- - diff --git a/keep-notes/fix-locales.js b/keep-notes/fix-locales.js deleted file mode 100644 index b84510e..0000000 --- a/keep-notes/fix-locales.js +++ /dev/null @@ -1,27 +0,0 @@ -const fs = require('fs'); - -function updateLocale(file, lang) { - const content = fs.readFileSync(file, 'utf8'); - const data = JSON.parse(content); - - if (lang === 'fr') { - data.ai.clarifyDesc = "Rendre le propos plus clair et compréhensible"; - data.ai.shortenDesc = "Résumer le texte et aller à l'essentiel"; - data.ai.improve = "Améliorer la rédaction"; - data.ai.improveDesc = "Corriger les fautes et le style"; - data.ai.toMarkdown = "Formater en Markdown"; - data.ai.toMarkdownDesc = "Ajouter des titres, des puces et structurer le texte"; - } else if (lang === 'en') { - data.ai.clarifyDesc = "Make the text clearer and easier to understand"; - data.ai.shortenDesc = "Summarize the text and get to the point"; - data.ai.improve = "Improve writing"; - data.ai.improveDesc = "Fix grammar and enhance style"; - data.ai.toMarkdown = "Format as Markdown"; - data.ai.toMarkdownDesc = "Add headings, bullet points and structure the text"; - } - - fs.writeFileSync(file, JSON.stringify(data, null, 2)); -} - -updateLocale('./locales/fr.json', 'fr'); -updateLocale('./locales/en.json', 'en'); diff --git a/keep-notes/fix-services.sh b/keep-notes/fix-services.sh deleted file mode 100644 index 5a10abe..0000000 --- a/keep-notes/fix-services.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -# Services à corriger -services=( - "lib/ai/services/batch-organization.service.ts" - "lib/ai/services/embedding.service.ts" - "lib/ai/services/auto-label-creation.service.ts" - "lib/ai/services/contextual-auto-tag.service.ts" - "lib/ai/services/notebook-suggestion.service.ts" - "lib/ai/services/notebook-summary.service.ts" -) - -echo "Services to fix:" -printf '%s\n' "${services[@]}" diff --git a/keep-notes/fix_ai_lang.py b/keep-notes/fix_ai_lang.py deleted file mode 100644 index 718a142..0000000 --- a/keep-notes/fix_ai_lang.py +++ /dev/null @@ -1,70 +0,0 @@ -import re - -# 1. Update types.ts -with open('lib/ai/types.ts', 'r') as f: - types_content = f.read() - -types_content = types_content.replace( - 'generateTags(content: string): Promise', - 'generateTags(content: string, language?: string): Promise' -) -with open('lib/ai/types.ts', 'w') as f: - f.write(types_content) - -# 2. Update OllamaProvider -with open('lib/ai/providers/ollama.ts', 'r') as f: - ollama_content = f.read() - -ollama_content = ollama_content.replace( - 'async generateTags(content: string): Promise', - 'async generateTags(content: string, language: string = "en"): Promise' -) - -# Replace the hardcoded prompt build logic -prompt_logic = """ - const promptText = language === 'fa' - ? `متن زیر را تحلیل کن و مفاهیم کلیدی را به عنوان برچسب استخراج کن (حداکثر ۱-۳ کلمه).\nقوانین:\n- کلمات ربط را حذف کن.\n- عبارات ترکیبی را حفظ کن.\n- حداکثر ۵ برچسب.\nپاسخ فقط به صورت لیست JSON با فرمت [{"tag": "string", "confidence": number}]\nمتن: "${content}"` - : language === 'fr' - ? `Analyse la note suivante et extrais les concepts clés sous forme de tags courts (1-3 mots max).\nRègles:\n- Pas de mots de liaison.\n- Garde les expressions composées ensemble.\n- Normalise en minuscules sauf noms propres.\n- Maximum 5 tags.\nRéponds UNIQUEMENT sous forme de liste JSON d'objets : [{"tag": "string", "confidence": number}].\nContenu de la note: "${content}"` - : `Analyze the following note and extract key concepts as short tags (1-3 words max).\nRules:\n- No stop words.\n- Keep compound expressions together.\n- Lowercase unless proper noun.\n- Max 5 tags.\nRespond ONLY as a JSON list of objects: [{"tag": "string", "confidence": number}].\nNote content: "${content}"`; - - const response = await fetch(`${this.baseUrl}/generate`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - model: this.modelName, - prompt: promptText, - stream: false, - }), - }); -""" - -# The original has: -# const response = await fetch(`${this.baseUrl}/generate`, { -# method: 'POST', -# headers: { 'Content-Type': 'application/json' }, -# body: JSON.stringify({ -# model: this.modelName, -# prompt: `Analyse la note suivante... - -ollama_content = re.sub( - r'const response = await fetch\(`\$\{this\.baseUrl\}/generate`.*?\}\),\n\s*\}\);', - prompt_logic.strip(), - ollama_content, - flags=re.DOTALL -) - -with open('lib/ai/providers/ollama.ts', 'w') as f: - f.write(ollama_content) - -# 3. Update route.ts -with open('app/api/ai/tags/route.ts', 'r') as f: - route_content = f.read() - -route_content = route_content.replace( - 'const tags = await provider.generateTags(content);', - 'const tags = await provider.generateTags(content, language);' -) -with open('app/api/ai/tags/route.ts', 'w') as f: - f.write(route_content) - diff --git a/keep-notes/fix_api_labels.py b/keep-notes/fix_api_labels.py deleted file mode 100644 index ccc8bd0..0000000 --- a/keep-notes/fix_api_labels.py +++ /dev/null @@ -1,25 +0,0 @@ -with open('app/api/labels/[id]/route.ts', 'r') as f: - content = f.read() - -# Fix targetUserId logic -content = content.replace( - 'if (name && name.trim() !== currentLabel.name && currentLabel.userId) {', - 'const targetUserIdPut = currentLabel.userId || currentLabel.notebook?.userId || session.user.id;\n if (name && name.trim() !== currentLabel.name && targetUserIdPut) {' -) -content = content.replace( - 'userId: currentLabel.userId,', - 'userId: targetUserIdPut,' -) - -content = content.replace( - 'if (label.userId) {', - 'const targetUserIdDel = label.userId || label.notebook?.userId || session.user.id;\n if (targetUserIdDel) {' -) -content = content.replace( - 'userId: label.userId,', - 'userId: targetUserIdDel,' -) - -with open('app/api/labels/[id]/route.ts', 'w') as f: - f.write(content) - diff --git a/keep-notes/fix_auto_tag_hook.py b/keep-notes/fix_auto_tag_hook.py deleted file mode 100644 index ccd87fa..0000000 --- a/keep-notes/fix_auto_tag_hook.py +++ /dev/null @@ -1,18 +0,0 @@ -with open('hooks/use-auto-tagging.ts', 'r') as f: - content = f.read() - -if 'useLanguage' not in content: - content = "import { useLanguage } from '@/lib/i18n'\n" + content - -content = content.replace( - 'export function useAutoTagging(notebookId?: string | null) {', - 'export function useAutoTagging(notebookId?: string | null) {\n const { language } = useLanguage();' -) - -content = content.replace( - "language: document.documentElement.lang || 'en',", - "language: language || document.documentElement.lang || 'en'," -) - -with open('hooks/use-auto-tagging.ts', 'w') as f: - f.write(content) diff --git a/keep-notes/fix_date_locale.py b/keep-notes/fix_date_locale.py deleted file mode 100644 index 475f2f7..0000000 --- a/keep-notes/fix_date_locale.py +++ /dev/null @@ -1,41 +0,0 @@ -import re - -files_to_fix = [ - 'components/note-inline-editor.tsx', - 'components/notes-tabs-view.tsx', - 'components/note-card.tsx' -] - -replacement_func = """import { faIR } from 'date-fns/locale' - -function getDateLocale(language: string) { - if (language === 'fr') return fr - if (language === 'fa') return faIR - return enUS -}""" - -for file in files_to_fix: - with open(file, 'r') as f: - content = f.read() - - # 1. Replace the getDateLocale function - content = re.sub( - r'function getDateLocale\(language: string\) \{\s*if \(language === \'fr\'\) return fr\s*return enUS\s*\}', - "function getDateLocale(language: string) {\n if (language === 'fr') return fr;\n if (language === 'fa') return require('date-fns/locale').faIR;\n return enUS;\n}", - content - ) - - # Also fix translations for "Modifiée" and "Créée" in inline editor (they are currently hardcoded) - if 'note-inline-editor.tsx' in file: - content = content.replace( - "Modifiée {formatDistanceToNow(new Date(note.updatedAt), { addSuffix: true, locale: dateLocale })}", - "{t('notes.modified') || 'Modifiée'} {formatDistanceToNow(new Date(note.updatedAt), { addSuffix: true, locale: dateLocale })}" - ) - content = content.replace( - "Créée {formatDistanceToNow(new Date(note.createdAt), { addSuffix: true, locale: dateLocale })}", - "{t('notes.created') || 'Créée'} {formatDistanceToNow(new Date(note.createdAt), { addSuffix: true, locale: dateLocale })}" - ) - - with open(file, 'w') as f: - f.write(content) - diff --git a/keep-notes/fix_dialog.py b/keep-notes/fix_dialog.py deleted file mode 100644 index c531b8f..0000000 --- a/keep-notes/fix_dialog.py +++ /dev/null @@ -1,102 +0,0 @@ -import re - -with open('components/label-management-dialog.tsx', 'r') as f: - content = f.read() - -# Add useNoteRefresh import -if 'useNoteRefresh' not in content: - content = content.replace("import { useLanguage } from '@/lib/i18n'", "import { useLanguage } from '@/lib/i18n'\nimport { useNoteRefresh } from '@/context/NoteRefreshContext'") - -# Add useNoteRefresh to component -content = content.replace("const { t } = useLanguage()", "const { t } = useLanguage()\n const { triggerRefresh } = useNoteRefresh()\n const [confirmDeleteId, setConfirmDeleteId] = useState(null)") - -# Modify handleDeleteLabel -old_delete = """ const handleDeleteLabel = async (id: string) => { - if (confirm(t('labels.confirmDelete'))) { - try { - await deleteLabel(id) - } catch (error) { - console.error('Failed to delete label:', error) - } - } - }""" -new_delete = """ const handleDeleteLabel = async (id: string) => { - try { - await deleteLabel(id) - triggerRefresh() - setConfirmDeleteId(null) - } catch (error) { - console.error('Failed to delete label:', error) - } - }""" -content = content.replace(old_delete, new_delete) - -# Also adding triggerRefresh() on addLabel and updateLabel: -content = content.replace( - "await addLabel(trimmed, 'gray')", - "await addLabel(trimmed, 'gray')\n triggerRefresh()" -) -content = content.replace( - "await updateLabel(id, { color })", - "await updateLabel(id, { color })\n triggerRefresh()" -) - -# Inline confirm UI: Change the Trash2 button area -old_div = """
- - -
""" - -new_div = """ {confirmDeleteId === label.id ? ( -
- {t('labels.confirmDeleteShort') || 'Confirmer ?'} - - -
- ) : ( -
- - -
- )}""" - -content = content.replace(old_div, new_div) - -with open('components/label-management-dialog.tsx', 'w') as f: - f.write(content) diff --git a/keep-notes/fix_dialog_dir.py b/keep-notes/fix_dialog_dir.py deleted file mode 100644 index e6c51a3..0000000 --- a/keep-notes/fix_dialog_dir.py +++ /dev/null @@ -1,22 +0,0 @@ -with open('components/label-management-dialog.tsx', 'r') as f: - content = f.read() - -# Add language to useLanguage() -content = content.replace( - 'const { t } = useLanguage()', - 'const { t, language } = useLanguage()' -) - -# Add dir to Dialog -content = content.replace( - '', - '' -) - -content = content.replace( - '', - '' -) - -with open('components/label-management-dialog.tsx', 'w') as f: - f.write(content) diff --git a/keep-notes/fix_end3.py b/keep-notes/fix_end3.py deleted file mode 100644 index fa9d18d..0000000 --- a/keep-notes/fix_end3.py +++ /dev/null @@ -1,7 +0,0 @@ -with open('components/notebooks-list.tsx', 'r') as f: - content = f.read() - -content = content.replace('right-3', 'end-3') - -with open('components/notebooks-list.tsx', 'w') as f: - f.write(content) diff --git a/keep-notes/fix_filter_and_toggle.py b/keep-notes/fix_filter_and_toggle.py deleted file mode 100644 index 1307bdf..0000000 --- a/keep-notes/fix_filter_and_toggle.py +++ /dev/null @@ -1,47 +0,0 @@ -import json - -def update_json(filepath, updates): - with open(filepath, 'r+', encoding='utf-8') as f: - data = json.load(f) - for key, val in updates.items(): - keys = key.split('.') - d = data - for k in keys[:-1]: - if k not in d: d[k] = {} - d = d[k] - d[keys[-1]] = val - f.seek(0) - json.dump(data, f, ensure_ascii=False, indent=2) - f.truncate() - -fa_updates = { - 'notes.viewTabs': 'نمایش زبانه‌ای', - 'notes.viewCards': 'نمایش کارتی', - 'labels.filter': 'فیلتر بر اساس برچسب', - 'labels.title': 'برچسب‌ها', - 'general.clear': 'پاک کردن' -} -fr_updates = { - 'notes.viewTabs': 'Vue par onglets', - 'notes.viewCards': 'Vue par cartes' -} -en_updates = { - 'notes.viewTabs': 'Tabs View', - 'notes.viewCards': 'Cards View' -} - -update_json('locales/fa.json', fa_updates) -update_json('locales/fr.json', fr_updates) -update_json('locales/en.json', en_updates) - -# Now update label-filter.tsx to add explicit dir to wrapping div -with open('components/label-filter.tsx', 'r') as f: - content = f.read() - -content = content.replace( - '
', - '
' -) - -with open('components/label-filter.tsx', 'w') as f: - f.write(content) diff --git a/keep-notes/fix_filter_button_dir.py b/keep-notes/fix_filter_button_dir.py deleted file mode 100644 index e5d0577..0000000 --- a/keep-notes/fix_filter_button_dir.py +++ /dev/null @@ -1,10 +0,0 @@ -with open('components/label-filter.tsx', 'r') as f: - content = f.read() - -content = content.replace( - '', - '' -) - -with open('components/label-filter.tsx', 'w') as f: - f.write(content) diff --git a/keep-notes/fix_hook_lang.py b/keep-notes/fix_hook_lang.py deleted file mode 100644 index 8ace7c2..0000000 --- a/keep-notes/fix_hook_lang.py +++ /dev/null @@ -1,11 +0,0 @@ -with open('hooks/use-auto-tagging.ts', 'r') as f: - content = f.read() - -# Make sure we add `const { language } = useLanguage();` -content = content.replace( - 'export function useAutoTagging({ content, notebookId, enabled = true }: UseAutoTaggingProps) {', - 'export function useAutoTagging({ content, notebookId, enabled = true }: UseAutoTaggingProps) {\n const { language } = useLanguage();' -) - -with open('hooks/use-auto-tagging.ts', 'w') as f: - f.write(content) diff --git a/keep-notes/fix_note_input.py b/keep-notes/fix_note_input.py deleted file mode 100644 index 20c6b91..0000000 --- a/keep-notes/fix_note_input.py +++ /dev/null @@ -1,18 +0,0 @@ -with open('components/note-input.tsx', 'r') as f: - content = f.read() - -old_call = """ const { suggestions, isAnalyzing } = useAutoTagging({ - content: type === 'text' ? fullContentForAI : '', - enabled: type === 'text' && isExpanded - })""" - -new_call = """ const { suggestions, isAnalyzing } = useAutoTagging({ - content: type === 'text' ? fullContentForAI : '', - enabled: type === 'text' && isExpanded, - notebookId: currentNotebookId - })""" - -content = content.replace(old_call, new_call) - -with open('components/note-input.tsx', 'w') as f: - f.write(content) diff --git a/keep-notes/fix_notebooks.py b/keep-notes/fix_notebooks.py deleted file mode 100644 index e3d9da8..0000000 --- a/keep-notes/fix_notebooks.py +++ /dev/null @@ -1,22 +0,0 @@ -import re - -with open('components/notebooks-list.tsx', 'r') as f: - content = f.read() - -# 1. Add `language` to `useLanguage` -content = content.replace("const { t } = useLanguage()", "const { t, language } = useLanguage()") - -# 2. Add `dir=\"auto\"` and logical properties to active notebook (isExpanded section) -# Replace pl-12 pr-4 with ps-12 pe-4, mr-2 with me-2, ml-2 with ms-2, rounded-r-full with rounded-e-full, text-left with text-start -content = content.replace("rounded-r-full", "rounded-e-full") -content = content.replace("pl-12", "ps-12").replace("pr-4", "pe-4") -content = content.replace("mr-2", "me-2").replace("ml-2", "ms-2") -content = content.replace("text-left", "text-start") -content = content.replace("pr-24", "pe-24") - -# 3. Format numbers: ((notebook as any).notesCount) -> new Intl.NumberFormat(language).format((notebook as any).notesCount) -# Look for: ({(notebook as any).notesCount}) -content = content.replace("({(notebook as any).notesCount})", "({new Intl.NumberFormat(language).format((notebook as any).notesCount)})") - -with open('components/notebooks-list.tsx', 'w') as f: - f.write(content) diff --git a/keep-notes/fix_ollama_provider.py b/keep-notes/fix_ollama_provider.py deleted file mode 100644 index 69ad60c..0000000 --- a/keep-notes/fix_ollama_provider.py +++ /dev/null @@ -1,68 +0,0 @@ -with open('lib/ai/providers/ollama.ts', 'r') as f: - content = f.read() - -# Restore generateTitles and generateText completely -# I will find the boundaries of generateTitles and generateText and replace them. - -import re - -# We will cut the string from async generateTitles to the end of class, and replace it manually. -start_index = content.find('async generateTitles(prompt: string): Promise {') - -if start_index != -1: - content = content[:start_index] + """async generateTitles(prompt: string): Promise { - try { - const response = await fetch(`${this.baseUrl}/generate`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - model: this.modelName, - prompt: `${prompt}\\n\\nRéponds UNIQUEMENT sous forme de tableau JSON : [{"title": "string", "confidence": number}]`, - stream: false, - }), - }); - - if (!response.ok) throw new Error(`Ollama error: ${response.statusText}`); - - const data = await response.json(); - const text = data.response; - - // Extraire le JSON de la réponse - const jsonMatch = text.match(/\[\s*\{[\s\S]*\}\s*\]/); - if (jsonMatch) { - return JSON.parse(jsonMatch[0]); - } - - return []; - } catch (e) { - console.error('Erreur génération titres Ollama:', e); - return []; - } - } - - async generateText(prompt: string): Promise { - try { - const response = await fetch(`${this.baseUrl}/generate`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - model: this.modelName, - prompt: prompt, - stream: false, - }), - }); - - if (!response.ok) throw new Error(`Ollama error: ${response.statusText}`); - - const data = await response.json(); - return data.response.trim(); - } catch (e) { - console.error('Erreur génération texte Ollama:', e); - throw e; - } - } -} -""" - -with open('lib/ai/providers/ollama.ts', 'w') as f: - f.write(content) diff --git a/keep-notes/fix_sidebar.py b/keep-notes/fix_sidebar.py deleted file mode 100644 index cde03e2..0000000 --- a/keep-notes/fix_sidebar.py +++ /dev/null @@ -1,10 +0,0 @@ -with open('components/sidebar.tsx', 'r') as f: - content = f.read() - -content = content.replace('rounded-r-full', 'rounded-e-full') -content = content.replace('mr-2', 'me-2') -content = content.replace('pl-4', 'ps-4') -content = content.replace('pr-3', 'pe-3') - -with open('components/sidebar.tsx', 'w') as f: - f.write(content) diff --git a/keep-notes/fix_tabs.py b/keep-notes/fix_tabs.py deleted file mode 100644 index e542cbb..0000000 --- a/keep-notes/fix_tabs.py +++ /dev/null @@ -1,10 +0,0 @@ -with open('components/notes-tabs-view.tsx', 'r') as f: - content = f.read() - -content = content.replace('rounded-l-xl', 'rounded-s-xl') -content = content.replace('pr-1', 'pe-1') -content = content.replace('pr-3', 'pe-3') -content = content.replace('ml-2', 'ms-2') - -with open('components/notes-tabs-view.tsx', 'w') as f: - f.write(content) diff --git a/keep-notes/fix_translation.py b/keep-notes/fix_translation.py deleted file mode 100644 index 1b3dfe8..0000000 --- a/keep-notes/fix_translation.py +++ /dev/null @@ -1,34 +0,0 @@ -import json -import os - -def update_locale(file, updates): - if not os.path.exists(file): - return - with open(file, 'r', encoding='utf-8') as f: - data = json.load(f) - - if 'notes' not in data: - data['notes'] = {} - - for k, v in updates.items(): - data['notes'][k] = v - - with open(file, 'w', encoding='utf-8') as f: - json.dump(data, f, indent=2, ensure_ascii=False) - -fa_updates = { - 'modified': 'ویرایش شده', - 'created': 'ایجاد شده' -} -en_updates = { - 'modified': 'Modified', - 'created': 'Created' -} -fr_updates = { - 'modified': 'Modifiée', - 'created': 'Créée' -} - -update_locale('locales/fa.json', fa_updates) -update_locale('locales/en.json', en_updates) -update_locale('locales/fr.json', fr_updates) diff --git a/keep-notes/git_version_backup.txt b/keep-notes/git_version_backup.txt deleted file mode 100644 index b1c0073..0000000 --- a/keep-notes/git_version_backup.txt +++ /dev/null @@ -1,301 +0,0 @@ -'use client' - -import { useState, useEffect, useRef, useCallback, memo, useMemo } from 'react'; -import { Note } from '@/lib/types'; -import { NoteCard } from './note-card'; -import { NoteEditor } from './note-editor'; -import { updateFullOrderWithoutRevalidation } from '@/app/actions/notes'; -import { useResizeObserver } from '@/hooks/use-resize-observer'; -import { useNotebookDrag } from '@/context/notebook-drag-context'; -import { useLanguage } from '@/lib/i18n'; - -interface MasonryGridProps { - notes: Note[]; - onEdit?: (note: Note, readOnly?: boolean) => void; -} - -interface MasonryItemProps { - note: Note; - onEdit: (note: Note, readOnly?: boolean) => void; - onResize: () => void; - onDragStart?: (noteId: string) => void; - onDragEnd?: () => void; - isDragging?: boolean; -} - -function getSizeClasses(size: string = 'small') { - switch (size) { - case 'medium': - return 'w-full sm:w-full lg:w-2/3 xl:w-2/4 2xl:w-2/5'; - case 'large': - return 'w-full'; - case 'small': - default: - return 'w-full sm:w-1/2 lg:w-1/3 xl:w-1/4 2xl:w-1/5'; - } -} - -const MasonryItem = memo(function MasonryItem({ note, onEdit, onResize, onDragStart, onDragEnd, isDragging }: MasonryItemProps) { - const resizeRef = useResizeObserver(onResize); - - const sizeClasses = getSizeClasses(note.size); - - return ( -
-
- -
-
- ); -}, (prev, next) => { - // Custom comparison to avoid re-render on function prop changes if note data is same - return prev.note.id === next.note.id && prev.note.order === next.note.order && prev.isDragging === next.isDragging; -}); - -export function MasonryGrid({ notes, onEdit }: MasonryGridProps) { - const { t } = useLanguage(); - const [editingNote, setEditingNote] = useState<{ note: Note; readOnly?: boolean } | null>(null); - const { startDrag, endDrag, draggedNoteId } = useNotebookDrag(); - - // Use external onEdit if provided, otherwise use internal state - const handleEdit = useCallback((note: Note, readOnly?: boolean) => { - if (onEdit) { - onEdit(note, readOnly); - } else { - setEditingNote({ note, readOnly }); - } - }, [onEdit]); - - const pinnedGridRef = useRef(null); - const othersGridRef = useRef(null); - const pinnedMuuri = useRef(null); - const othersMuuri = useRef(null); - - // Memoize filtered and sorted notes to avoid recalculation on every render - const pinnedNotes = useMemo( - () => notes.filter(n => n.isPinned).sort((a, b) => a.order - b.order), - [notes] - ); - const othersNotes = useMemo( - () => notes.filter(n => !n.isPinned).sort((a, b) => a.order - b.order), - [notes] - ); - - // CRITICAL: Sync editingNote when underlying note changes (e.g., after moving to notebook) - // This ensures the NoteEditor gets the updated note with the new notebookId - useEffect(() => { - if (!editingNote) return; - - // Find the updated version of the currently edited note in the notes array - const updatedNote = notes.find(n => n.id === editingNote.note.id); - - if (updatedNote) { - // Check if any key properties changed (especially notebookId) - const notebookIdChanged = updatedNote.notebookId !== editingNote.note.notebookId; - - if (notebookIdChanged) { - // Update the editingNote with the new data - setEditingNote(prev => prev ? { ...prev, note: updatedNote } : null); - } - } - }, [notes, editingNote]); - - const handleDragEnd = useCallback(async (grid: any) => { - if (!grid) return; - - const items = grid.getItems(); - const ids = items - .map((item: any) => item.getElement()?.getAttribute('data-id')) - .filter((id: any): id is string => !!id); - - try { - // Save order to database WITHOUT revalidating the page - // Muuri has already updated the visual layout, so we don't need to reload - await updateFullOrderWithoutRevalidation(ids); - } catch (error) { - console.error('Failed to persist order:', error); - } - }, []); - - const refreshLayout = useCallback(() => { - // Use requestAnimationFrame for smoother updates - requestAnimationFrame(() => { - if (pinnedMuuri.current) { - pinnedMuuri.current.refreshItems().layout(); - } - if (othersMuuri.current) { - othersMuuri.current.refreshItems().layout(); - } - }); - }, []); - - // Initialize Muuri grids once on mount and sync when needed - useEffect(() => { - let isMounted = true; - let muuriInitialized = false; - - const initMuuri = async () => { - // Prevent duplicate initialization - if (muuriInitialized) return; - muuriInitialized = true; - - // Import web-animations-js polyfill - await import('web-animations-js'); - // Dynamic import of Muuri to avoid SSR window error - const MuuriClass = (await import('muuri')).default; - - if (!isMounted) return; - - // Detect if we are on a touch device (mobile behavior) - const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; - const isMobileWidth = window.innerWidth < 768; - const isMobile = isTouchDevice || isMobileWidth; - - const layoutOptions = { - dragEnabled: true, - // Use drag handle for mobile devices to allow smooth scrolling - // On desktop, whole card is draggable (no handle needed) - dragHandle: isMobile ? '.muuri-drag-handle' : undefined, - dragContainer: document.body, - dragStartPredicate: { - distance: 10, - delay: 0, - }, - dragPlaceholder: { - enabled: true, - createElement: (item: any) => { - const el = item.getElement().cloneNode(true); - el.style.opacity = '0.5'; - return el; - }, - }, - dragAutoScroll: { - targets: [window], - speed: (item: any, target: any, intersection: any) => { - return intersection * 20; - }, - }, - }; - - // Initialize pinned grid - if (pinnedGridRef.current && !pinnedMuuri.current) { - pinnedMuuri.current = new MuuriClass(pinnedGridRef.current, layoutOptions) - .on('dragEnd', () => handleDragEnd(pinnedMuuri.current)); - } - - // Initialize others grid - if (othersGridRef.current && !othersMuuri.current) { - othersMuuri.current = new MuuriClass(othersGridRef.current, layoutOptions) - .on('dragEnd', () => handleDragEnd(othersMuuri.current)); - } - }; - - initMuuri(); - - return () => { - isMounted = false; - pinnedMuuri.current?.destroy(); - othersMuuri.current?.destroy(); - pinnedMuuri.current = null; - othersMuuri.current = null; - }; - // Only run once on mount - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // Synchronize items when notes change (e.g. searching, adding) - useEffect(() => { - requestAnimationFrame(() => { - if (pinnedMuuri.current) { - pinnedMuuri.current.refreshItems().layout(); - } - if (othersMuuri.current) { - othersMuuri.current.refreshItems().layout(); - } - }); - }, [notes]); - - return ( -
- {pinnedNotes.length > 0 && ( -
-

{t('notes.pinned')}

-
- {pinnedNotes.map(note => ( - - ))} -
-
- )} - - {othersNotes.length > 0 && ( -
- {pinnedNotes.length > 0 && ( -

{t('notes.others')}

- )} -
- {othersNotes.map(note => ( - - ))} -
-
- )} - - {editingNote && ( - setEditingNote(null)} - /> - )} - - -
- ); -} diff --git a/keep-notes/prisma/dev.db b/keep-notes/prisma/dev.db index 7103d8fbd06f5864743cb89a79eaae6a7fda2084..645297f788e3992b6e33f11fdda25adc8fae32fa 100644 GIT binary patch delta 7480 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 void; -} - -interface MasonryItemProps { - note: Note; - onEdit: (note: Note, readOnly?: boolean) => void; - onResize: () => void; - onDragStart?: (noteId: string) => void; - onDragEnd?: () => void; - isDragging?: boolean; -} - -function getSizeClasses(size: string = 'small') { - switch (size) { - case 'medium': - return 'w-full sm:w-full lg:w-2/3 xl:w-2/4 2xl:w-2/5'; - case 'large': - return 'w-full'; - case 'small': - default: - return 'w-full sm:w-1/2 lg:w-1/3 xl:w-1/4 2xl:w-1/5'; - } -} - -const MasonryItem = memo(function MasonryItem({ note, onEdit, onResize, onDragStart, onDragEnd, isDragging }: MasonryItemProps) { - const resizeRef = useResizeObserver(onResize); - - const sizeClasses = getSizeClasses(note.size); - - return ( -
-
- -
-
- ); -}, (prev, next) => { - // Custom comparison to avoid re-render on function prop changes if note data is same - return prev.note.id === next.note.id && prev.note.order === next.note.order && prev.isDragging === next.isDragging; -}); - -export function MasonryGrid({ notes, onEdit }: MasonryGridProps) { - const { t } = useLanguage(); - const [editingNote, setEditingNote] = useState<{ note: Note; readOnly?: boolean } | null>(null); - const { startDrag, endDrag, draggedNoteId } = useNotebookDrag(); - - // Use external onEdit if provided, otherwise use internal state - const handleEdit = useCallback((note: Note, readOnly?: boolean) => { - if (onEdit) { - onEdit(note, readOnly); - } else { - setEditingNote({ note, readOnly }); - } - }, [onEdit]); - - const pinnedGridRef = useRef(null); - const othersGridRef = useRef(null); - const pinnedMuuri = useRef(null); - const othersMuuri = useRef(null); - - // Memoize filtered and sorted notes to avoid recalculation on every render - const pinnedNotes = useMemo( - () => notes.filter(n => n.isPinned).sort((a, b) => a.order - b.order), - [notes] - ); - const othersNotes = useMemo( - () => notes.filter(n => !n.isPinned).sort((a, b) => a.order - b.order), - [notes] - ); - - // CRITICAL: Sync editingNote when underlying note changes (e.g., after moving to notebook) - // This ensures the NoteEditor gets the updated note with the new notebookId - useEffect(() => { - if (!editingNote) return; - - // Find the updated version of the currently edited note in the notes array - const updatedNote = notes.find(n => n.id === editingNote.note.id); - - if (updatedNote) { - // Check if any key properties changed (especially notebookId) - const notebookIdChanged = updatedNote.notebookId !== editingNote.note.notebookId; - - if (notebookIdChanged) { - // Update the editingNote with the new data - setEditingNote(prev => prev ? { ...prev, note: updatedNote } : null); - } - } - }, [notes, editingNote]); - - const handleDragEnd = useCallback(async (grid: any) => { - if (!grid) return; - - const items = grid.getItems(); - const ids = items - .map((item: any) => item.getElement()?.getAttribute('data-id')) - .filter((id: any): id is string => !!id); - - try { - // Save order to database WITHOUT revalidating the page - // Muuri has already updated the visual layout, so we don't need to reload - await updateFullOrderWithoutRevalidation(ids); - } catch (error) { - console.error('Failed to persist order:', error); - } - }, []); - - const refreshLayout = useCallback(() => { - // Use requestAnimationFrame for smoother updates - requestAnimationFrame(() => { - if (pinnedMuuri.current) { - pinnedMuuri.current.refreshItems().layout(); - } - if (othersMuuri.current) { - othersMuuri.current.refreshItems().layout(); - } - }); - }, []); - - // Initialize Muuri grids once on mount and sync when needed - useEffect(() => { - let isMounted = true; - let muuriInitialized = false; - - const initMuuri = async () => { - // Prevent duplicate initialization - if (muuriInitialized) return; - muuriInitialized = true; - - // Import web-animations-js polyfill - await import('web-animations-js'); - // Dynamic import of Muuri to avoid SSR window error - const MuuriClass = (await import('muuri')).default; - - if (!isMounted) return; - - // Detect if we are on a touch device (mobile behavior) - const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; - const isMobileWidth = window.innerWidth < 768; - const isMobile = isTouchDevice || isMobileWidth; - - const layoutOptions = { - dragEnabled: true, - // Use drag handle for mobile devices to allow smooth scrolling - // On desktop, whole card is draggable (no handle needed) - dragHandle: isMobile ? '.muuri-drag-handle' : undefined, - dragContainer: document.body, - dragStartPredicate: { - distance: 10, - delay: 0, - }, - dragPlaceholder: { - enabled: true, - createElement: (item: any) => { - const el = item.getElement().cloneNode(true); - el.style.opacity = '0.5'; - return el; - }, - }, - dragAutoScroll: { - targets: [window], - speed: (item: any, target: any, intersection: any) => { - return intersection * 20; - }, - }, - }; - - // Initialize pinned grid - if (pinnedGridRef.current && !pinnedMuuri.current) { - pinnedMuuri.current = new MuuriClass(pinnedGridRef.current, layoutOptions) - .on('dragEnd', () => handleDragEnd(pinnedMuuri.current)); - } - - // Initialize others grid - if (othersGridRef.current && !othersMuuri.current) { - othersMuuri.current = new MuuriClass(othersGridRef.current, layoutOptions) - .on('dragEnd', () => handleDragEnd(othersMuuri.current)); - } - }; - - initMuuri(); - - return () => { - isMounted = false; - pinnedMuuri.current?.destroy(); - othersMuuri.current?.destroy(); - pinnedMuuri.current = null; - othersMuuri.current = null; - }; - // Only run once on mount - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // Synchronize items when notes change (e.g. searching, adding) - useEffect(() => { - requestAnimationFrame(() => { - if (pinnedMuuri.current) { - pinnedMuuri.current.refreshItems().layout(); - } - if (othersMuuri.current) { - othersMuuri.current.refreshItems().layout(); - } - }); - }, [notes]); - - return ( -
- {pinnedNotes.length > 0 && ( -
-

{t('notes.pinned')}

-
- {pinnedNotes.map(note => ( - - ))} -
-
- )} - - {othersNotes.length > 0 && ( -
- {pinnedNotes.length > 0 && ( -

{t('notes.others')}

- )} -
- {othersNotes.map(note => ( - - ))} -
-
- )} - - {editingNote && ( - setEditingNote(null)} - /> - )} - - -
- ); -} diff --git a/keep-notes/temp_git_masonry.txt b/keep-notes/temp_git_masonry.txt deleted file mode 100644 index b1c0073..0000000 --- a/keep-notes/temp_git_masonry.txt +++ /dev/null @@ -1,301 +0,0 @@ -'use client' - -import { useState, useEffect, useRef, useCallback, memo, useMemo } from 'react'; -import { Note } from '@/lib/types'; -import { NoteCard } from './note-card'; -import { NoteEditor } from './note-editor'; -import { updateFullOrderWithoutRevalidation } from '@/app/actions/notes'; -import { useResizeObserver } from '@/hooks/use-resize-observer'; -import { useNotebookDrag } from '@/context/notebook-drag-context'; -import { useLanguage } from '@/lib/i18n'; - -interface MasonryGridProps { - notes: Note[]; - onEdit?: (note: Note, readOnly?: boolean) => void; -} - -interface MasonryItemProps { - note: Note; - onEdit: (note: Note, readOnly?: boolean) => void; - onResize: () => void; - onDragStart?: (noteId: string) => void; - onDragEnd?: () => void; - isDragging?: boolean; -} - -function getSizeClasses(size: string = 'small') { - switch (size) { - case 'medium': - return 'w-full sm:w-full lg:w-2/3 xl:w-2/4 2xl:w-2/5'; - case 'large': - return 'w-full'; - case 'small': - default: - return 'w-full sm:w-1/2 lg:w-1/3 xl:w-1/4 2xl:w-1/5'; - } -} - -const MasonryItem = memo(function MasonryItem({ note, onEdit, onResize, onDragStart, onDragEnd, isDragging }: MasonryItemProps) { - const resizeRef = useResizeObserver(onResize); - - const sizeClasses = getSizeClasses(note.size); - - return ( -
-
- -
-
- ); -}, (prev, next) => { - // Custom comparison to avoid re-render on function prop changes if note data is same - return prev.note.id === next.note.id && prev.note.order === next.note.order && prev.isDragging === next.isDragging; -}); - -export function MasonryGrid({ notes, onEdit }: MasonryGridProps) { - const { t } = useLanguage(); - const [editingNote, setEditingNote] = useState<{ note: Note; readOnly?: boolean } | null>(null); - const { startDrag, endDrag, draggedNoteId } = useNotebookDrag(); - - // Use external onEdit if provided, otherwise use internal state - const handleEdit = useCallback((note: Note, readOnly?: boolean) => { - if (onEdit) { - onEdit(note, readOnly); - } else { - setEditingNote({ note, readOnly }); - } - }, [onEdit]); - - const pinnedGridRef = useRef(null); - const othersGridRef = useRef(null); - const pinnedMuuri = useRef(null); - const othersMuuri = useRef(null); - - // Memoize filtered and sorted notes to avoid recalculation on every render - const pinnedNotes = useMemo( - () => notes.filter(n => n.isPinned).sort((a, b) => a.order - b.order), - [notes] - ); - const othersNotes = useMemo( - () => notes.filter(n => !n.isPinned).sort((a, b) => a.order - b.order), - [notes] - ); - - // CRITICAL: Sync editingNote when underlying note changes (e.g., after moving to notebook) - // This ensures the NoteEditor gets the updated note with the new notebookId - useEffect(() => { - if (!editingNote) return; - - // Find the updated version of the currently edited note in the notes array - const updatedNote = notes.find(n => n.id === editingNote.note.id); - - if (updatedNote) { - // Check if any key properties changed (especially notebookId) - const notebookIdChanged = updatedNote.notebookId !== editingNote.note.notebookId; - - if (notebookIdChanged) { - // Update the editingNote with the new data - setEditingNote(prev => prev ? { ...prev, note: updatedNote } : null); - } - } - }, [notes, editingNote]); - - const handleDragEnd = useCallback(async (grid: any) => { - if (!grid) return; - - const items = grid.getItems(); - const ids = items - .map((item: any) => item.getElement()?.getAttribute('data-id')) - .filter((id: any): id is string => !!id); - - try { - // Save order to database WITHOUT revalidating the page - // Muuri has already updated the visual layout, so we don't need to reload - await updateFullOrderWithoutRevalidation(ids); - } catch (error) { - console.error('Failed to persist order:', error); - } - }, []); - - const refreshLayout = useCallback(() => { - // Use requestAnimationFrame for smoother updates - requestAnimationFrame(() => { - if (pinnedMuuri.current) { - pinnedMuuri.current.refreshItems().layout(); - } - if (othersMuuri.current) { - othersMuuri.current.refreshItems().layout(); - } - }); - }, []); - - // Initialize Muuri grids once on mount and sync when needed - useEffect(() => { - let isMounted = true; - let muuriInitialized = false; - - const initMuuri = async () => { - // Prevent duplicate initialization - if (muuriInitialized) return; - muuriInitialized = true; - - // Import web-animations-js polyfill - await import('web-animations-js'); - // Dynamic import of Muuri to avoid SSR window error - const MuuriClass = (await import('muuri')).default; - - if (!isMounted) return; - - // Detect if we are on a touch device (mobile behavior) - const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; - const isMobileWidth = window.innerWidth < 768; - const isMobile = isTouchDevice || isMobileWidth; - - const layoutOptions = { - dragEnabled: true, - // Use drag handle for mobile devices to allow smooth scrolling - // On desktop, whole card is draggable (no handle needed) - dragHandle: isMobile ? '.muuri-drag-handle' : undefined, - dragContainer: document.body, - dragStartPredicate: { - distance: 10, - delay: 0, - }, - dragPlaceholder: { - enabled: true, - createElement: (item: any) => { - const el = item.getElement().cloneNode(true); - el.style.opacity = '0.5'; - return el; - }, - }, - dragAutoScroll: { - targets: [window], - speed: (item: any, target: any, intersection: any) => { - return intersection * 20; - }, - }, - }; - - // Initialize pinned grid - if (pinnedGridRef.current && !pinnedMuuri.current) { - pinnedMuuri.current = new MuuriClass(pinnedGridRef.current, layoutOptions) - .on('dragEnd', () => handleDragEnd(pinnedMuuri.current)); - } - - // Initialize others grid - if (othersGridRef.current && !othersMuuri.current) { - othersMuuri.current = new MuuriClass(othersGridRef.current, layoutOptions) - .on('dragEnd', () => handleDragEnd(othersMuuri.current)); - } - }; - - initMuuri(); - - return () => { - isMounted = false; - pinnedMuuri.current?.destroy(); - othersMuuri.current?.destroy(); - pinnedMuuri.current = null; - othersMuuri.current = null; - }; - // Only run once on mount - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // Synchronize items when notes change (e.g. searching, adding) - useEffect(() => { - requestAnimationFrame(() => { - if (pinnedMuuri.current) { - pinnedMuuri.current.refreshItems().layout(); - } - if (othersMuuri.current) { - othersMuuri.current.refreshItems().layout(); - } - }); - }, [notes]); - - return ( -
- {pinnedNotes.length > 0 && ( -
-

{t('notes.pinned')}

-
- {pinnedNotes.map(note => ( - - ))} -
-
- )} - - {othersNotes.length > 0 && ( -
- {pinnedNotes.length > 0 && ( -

{t('notes.others')}

- )} -
- {othersNotes.map(note => ( - - ))} -
-
- )} - - {editingNote && ( - setEditingNote(null)} - /> - )} - - -
- ); -} diff --git a/keep-notes/temp_git_version.txt b/keep-notes/temp_git_version.txt deleted file mode 100644 index b1c0073..0000000 --- a/keep-notes/temp_git_version.txt +++ /dev/null @@ -1,301 +0,0 @@ -'use client' - -import { useState, useEffect, useRef, useCallback, memo, useMemo } from 'react'; -import { Note } from '@/lib/types'; -import { NoteCard } from './note-card'; -import { NoteEditor } from './note-editor'; -import { updateFullOrderWithoutRevalidation } from '@/app/actions/notes'; -import { useResizeObserver } from '@/hooks/use-resize-observer'; -import { useNotebookDrag } from '@/context/notebook-drag-context'; -import { useLanguage } from '@/lib/i18n'; - -interface MasonryGridProps { - notes: Note[]; - onEdit?: (note: Note, readOnly?: boolean) => void; -} - -interface MasonryItemProps { - note: Note; - onEdit: (note: Note, readOnly?: boolean) => void; - onResize: () => void; - onDragStart?: (noteId: string) => void; - onDragEnd?: () => void; - isDragging?: boolean; -} - -function getSizeClasses(size: string = 'small') { - switch (size) { - case 'medium': - return 'w-full sm:w-full lg:w-2/3 xl:w-2/4 2xl:w-2/5'; - case 'large': - return 'w-full'; - case 'small': - default: - return 'w-full sm:w-1/2 lg:w-1/3 xl:w-1/4 2xl:w-1/5'; - } -} - -const MasonryItem = memo(function MasonryItem({ note, onEdit, onResize, onDragStart, onDragEnd, isDragging }: MasonryItemProps) { - const resizeRef = useResizeObserver(onResize); - - const sizeClasses = getSizeClasses(note.size); - - return ( -
-
- -
-
- ); -}, (prev, next) => { - // Custom comparison to avoid re-render on function prop changes if note data is same - return prev.note.id === next.note.id && prev.note.order === next.note.order && prev.isDragging === next.isDragging; -}); - -export function MasonryGrid({ notes, onEdit }: MasonryGridProps) { - const { t } = useLanguage(); - const [editingNote, setEditingNote] = useState<{ note: Note; readOnly?: boolean } | null>(null); - const { startDrag, endDrag, draggedNoteId } = useNotebookDrag(); - - // Use external onEdit if provided, otherwise use internal state - const handleEdit = useCallback((note: Note, readOnly?: boolean) => { - if (onEdit) { - onEdit(note, readOnly); - } else { - setEditingNote({ note, readOnly }); - } - }, [onEdit]); - - const pinnedGridRef = useRef(null); - const othersGridRef = useRef(null); - const pinnedMuuri = useRef(null); - const othersMuuri = useRef(null); - - // Memoize filtered and sorted notes to avoid recalculation on every render - const pinnedNotes = useMemo( - () => notes.filter(n => n.isPinned).sort((a, b) => a.order - b.order), - [notes] - ); - const othersNotes = useMemo( - () => notes.filter(n => !n.isPinned).sort((a, b) => a.order - b.order), - [notes] - ); - - // CRITICAL: Sync editingNote when underlying note changes (e.g., after moving to notebook) - // This ensures the NoteEditor gets the updated note with the new notebookId - useEffect(() => { - if (!editingNote) return; - - // Find the updated version of the currently edited note in the notes array - const updatedNote = notes.find(n => n.id === editingNote.note.id); - - if (updatedNote) { - // Check if any key properties changed (especially notebookId) - const notebookIdChanged = updatedNote.notebookId !== editingNote.note.notebookId; - - if (notebookIdChanged) { - // Update the editingNote with the new data - setEditingNote(prev => prev ? { ...prev, note: updatedNote } : null); - } - } - }, [notes, editingNote]); - - const handleDragEnd = useCallback(async (grid: any) => { - if (!grid) return; - - const items = grid.getItems(); - const ids = items - .map((item: any) => item.getElement()?.getAttribute('data-id')) - .filter((id: any): id is string => !!id); - - try { - // Save order to database WITHOUT revalidating the page - // Muuri has already updated the visual layout, so we don't need to reload - await updateFullOrderWithoutRevalidation(ids); - } catch (error) { - console.error('Failed to persist order:', error); - } - }, []); - - const refreshLayout = useCallback(() => { - // Use requestAnimationFrame for smoother updates - requestAnimationFrame(() => { - if (pinnedMuuri.current) { - pinnedMuuri.current.refreshItems().layout(); - } - if (othersMuuri.current) { - othersMuuri.current.refreshItems().layout(); - } - }); - }, []); - - // Initialize Muuri grids once on mount and sync when needed - useEffect(() => { - let isMounted = true; - let muuriInitialized = false; - - const initMuuri = async () => { - // Prevent duplicate initialization - if (muuriInitialized) return; - muuriInitialized = true; - - // Import web-animations-js polyfill - await import('web-animations-js'); - // Dynamic import of Muuri to avoid SSR window error - const MuuriClass = (await import('muuri')).default; - - if (!isMounted) return; - - // Detect if we are on a touch device (mobile behavior) - const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; - const isMobileWidth = window.innerWidth < 768; - const isMobile = isTouchDevice || isMobileWidth; - - const layoutOptions = { - dragEnabled: true, - // Use drag handle for mobile devices to allow smooth scrolling - // On desktop, whole card is draggable (no handle needed) - dragHandle: isMobile ? '.muuri-drag-handle' : undefined, - dragContainer: document.body, - dragStartPredicate: { - distance: 10, - delay: 0, - }, - dragPlaceholder: { - enabled: true, - createElement: (item: any) => { - const el = item.getElement().cloneNode(true); - el.style.opacity = '0.5'; - return el; - }, - }, - dragAutoScroll: { - targets: [window], - speed: (item: any, target: any, intersection: any) => { - return intersection * 20; - }, - }, - }; - - // Initialize pinned grid - if (pinnedGridRef.current && !pinnedMuuri.current) { - pinnedMuuri.current = new MuuriClass(pinnedGridRef.current, layoutOptions) - .on('dragEnd', () => handleDragEnd(pinnedMuuri.current)); - } - - // Initialize others grid - if (othersGridRef.current && !othersMuuri.current) { - othersMuuri.current = new MuuriClass(othersGridRef.current, layoutOptions) - .on('dragEnd', () => handleDragEnd(othersMuuri.current)); - } - }; - - initMuuri(); - - return () => { - isMounted = false; - pinnedMuuri.current?.destroy(); - othersMuuri.current?.destroy(); - pinnedMuuri.current = null; - othersMuuri.current = null; - }; - // Only run once on mount - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // Synchronize items when notes change (e.g. searching, adding) - useEffect(() => { - requestAnimationFrame(() => { - if (pinnedMuuri.current) { - pinnedMuuri.current.refreshItems().layout(); - } - if (othersMuuri.current) { - othersMuuri.current.refreshItems().layout(); - } - }); - }, [notes]); - - return ( -
- {pinnedNotes.length > 0 && ( -
-

{t('notes.pinned')}

-
- {pinnedNotes.map(note => ( - - ))} -
-
- )} - - {othersNotes.length > 0 && ( -
- {pinnedNotes.length > 0 && ( -

{t('notes.others')}

- )} -
- {othersNotes.map(note => ( - - ))} -
-
- )} - - {editingNote && ( - setEditingNote(null)} - /> - )} - - -
- ); -} diff --git a/keep-notes/test-ai-tags.json b/keep-notes/test-ai-tags.json deleted file mode 100644 index 51c5530..0000000 --- a/keep-notes/test-ai-tags.json +++ /dev/null @@ -1 +0,0 @@ -{"content":"This is a test note about artificial intelligence and machine learning in Python"} diff --git a/keep-notes/test-memory-echo.js b/keep-notes/test-memory-echo.js deleted file mode 100644 index 0b61ca0..0000000 --- a/keep-notes/test-memory-echo.js +++ /dev/null @@ -1,8 +0,0 @@ -// Test Memory Echo API -async function testMemoryEcho() { - const res = await fetch('http://localhost:3000/api/ai/echo'); - const data = await res.json(); - console.log('Memory Echo Response:', JSON.stringify(data, null, 2)); -} - -testMemoryEcho(); diff --git a/keep-notes/test-reformulate.json b/keep-notes/test-reformulate.json deleted file mode 100644 index 5130042..0000000 --- a/keep-notes/test-reformulate.json +++ /dev/null @@ -1 +0,0 @@ -{"text":"This is a test paragraph that needs to be rewritten. It contains multiple sentences and should be improved.","mode":"clarify"} diff --git a/keep-notes/update_labels_dict.py b/keep-notes/update_labels_dict.py deleted file mode 100644 index 92110a1..0000000 --- a/keep-notes/update_labels_dict.py +++ /dev/null @@ -1,47 +0,0 @@ -import json - -def update_json(filepath, updates): - with open(filepath, 'r+', encoding='utf-8') as f: - data = json.load(f) - for key, val in updates.items(): - keys = key.split('.') - d = data - for k in keys[:-1]: - if k not in d: d[k] = {} - d = d[k] - d[keys[-1]] = val - f.seek(0) - json.dump(data, f, ensure_ascii=False, indent=2) - f.truncate() - -fa_updates = { - 'sidebar.editLabels': 'ویرایش برچسب‌ها', - 'sidebar.edit': 'ویرایش یادداشت', - 'labels.confirmDeleteShort': 'تایید؟', - 'common.cancel': 'لغو', - 'common.delete': 'حذف', - 'labels.editLabels': 'ویرایش برچسب‌ها', - 'labels.editLabelsDescription': 'برچسب‌های خود را مدیریت کنید', - 'labels.newLabelPlaceholder': 'برچسب جدید...', - 'labels.loading': 'در حال بارگذاری...', - 'labels.noLabelsFound': 'برچسبی یافت نشد', - 'labels.changeColor': 'تغییر رنگ', - 'labels.deleteTooltip': 'حذف برچسب', -} - -fr_updates = { - 'labels.confirmDeleteShort': 'Confirmer ?', - 'common.cancel': 'Annuler', - 'common.delete': 'Supprimer' -} - -en_updates = { - 'labels.confirmDeleteShort': 'Confirm?', - 'common.cancel': 'Cancel', - 'common.delete': 'Delete' -} - -update_json('locales/fa.json', fa_updates) -update_json('locales/fr.json', fr_updates) -update_json('locales/en.json', en_updates) - diff --git a/keep-notes/update_toggle_dir.py b/keep-notes/update_toggle_dir.py deleted file mode 100644 index 17fed66..0000000 --- a/keep-notes/update_toggle_dir.py +++ /dev/null @@ -1,17 +0,0 @@ -with open('components/notes-view-toggle.tsx', 'r') as f: - content = f.read() - -# Add language to useLanguage() -content = content.replace( - 'const { t } = useLanguage()', - 'const { t, language } = useLanguage()' -) - -# Add dir to div wrapper -content = content.replace( - 'className={cn(\n \'inline-flex rounded-full border border-border bg-muted/40 p-0.5 shadow-sm\',\n className\n )}', - 'dir={language === \'fa\' || language === \'ar\' ? \'rtl\' : \'ltr\'}\n className={cn(\n \'inline-flex rounded-full border border-border bg-muted/40 p-0.5 shadow-sm\',\n className\n )}' -) - -with open('components/notes-view-toggle.tsx', 'w') as f: - f.write(content)