diff --git a/docs/COLLECTIONS_FILE_TAGGING_GUIDE.md b/docs/COLLECTIONS_FILE_TAGGING_GUIDE.md new file mode 100644 index 000000000..5f8c5323a --- /dev/null +++ b/docs/COLLECTIONS_FILE_TAGGING_GUIDE.md @@ -0,0 +1,1412 @@ +# מדריך למימוש תיוג קבצים ב"אוספים שלי" 🏷️ + +## תוכן עניינים +1. [סקירה כללית](#סקירה-כללית) +2. [התגים המוצעים](#התגים-המוצעים) +3. [ארכיטקטורה ומבנה נתונים](#ארכיטקטורה-ומבנה-נתונים) +4. [שינויים נדרשים ב-Backend](#שינויים-נדרשים-ב-backend) +5. [שינויים נדרשים ב-Frontend](#שינויים-נדרשים-ב-frontend) +6. [UI/UX Components](#uiux-components) +7. [דוגמאות קוד](#דוגמאות-קוד) +8. [שלבי מימוש מומלצים](#שלבי-מימוש-מומלצים) +9. [שיקולים נוספים](#שיקולים-נוספים) + +--- + +## סקירה כללית + +מטרת המדריך היא להוסיף יכולת תיוג של קבצים באוספים באמצעות **אימוג'ים** שיאפשרו: +- **סימון עדיפויות** (דחוף/לא דחוף) +- **קטגוריות פונקציונליות** (באג, דאטה-בייס, ניסיוני) +- **סטטוס וסנטימנט** (מועדף, קסום, רעיון) +- **סדר טיפול** (תיוג מספרי 1️⃣2️⃣3️⃣) + +התיוג יהיה **ברמת הפריט בתוך האוסף** (collection item) - כלומר, אותו קובץ יכול לקבל תגיות שונות באוספים שונים. + +--- + +## התגים המוצעים + +### רשימת התגים + +| אימוג'י | שם | תיאור | קטגוריה | +|---------|-----|--------|----------| +| 🐢 | לא דחוף | משימה ללא לחץ זמן | עדיפות | +| 🔥 | דחוף | דורש טיפול מיידי | עדיפות | +| 🔮 | קסום | קוד מיוחד/מעניין | סנטימנט | +| ♥️ | מועדף | קובץ חשוב במיוחד | סנטימנט | +| 🔐 | סודי | מידע רגיש | אבטחה | +| 💭 | רעיון/idea | רעיון לפיתוח עתידי | סטטוס | +| ⏸️ | מושהה/paused | עבודה זמנית מושהית | סטטוס | +| 🎯 | מטרה/goal | יעד לביצוע | סטטוס | +| 🐛 | תיקון באג | תיקון שגיאה | קטגוריה | +| 🗄️ | דאטה-בייס | קשור למסד נתונים | קטגוריה | +| 🧪 | ניסיוני/testing | קוד בשלב בדיקה | קטגוריה | +| 1️⃣ | סדר ראשון | קובץ ראשון בסדר הטיפול | סדר | +| 2️⃣ | סדר שני | קובץ שני בסדר הטיפול | סדר | +| 3️⃣ | סדר שלישי | קובץ שלישי בסדר הטיפול | סדר | + +### ארגון לפי קטגוריות + +```python +TAG_CATEGORIES = { + 'priority': ['🐢', '🔥'], # עדיפות + 'sentiment': ['🔮', '♥️'], # סנטימנט + 'security': ['🔐'], # אבטחה + 'status': ['💭', '⏸️', '🎯'], # סטטוס + 'category': ['🐛', '🗄️', '🧪'], # קטגוריה פונקציונלית + 'order': ['1️⃣', '2️⃣', '3️⃣'] # סדר טיפול +} +``` + +--- + +## ארכיטקטורה ומבנה נתונים + +### מבנה נתונים קיים + +כרגע, בקולקציה `collection_items` יש את המבנה הבא: + +```javascript +{ + "_id": ObjectId, + "collection_id": ObjectId, + "user_id": int, + "source": str, // "regular" | "large" + "file_name": str, + "note": str, // עד 500 תווים + "pinned": bool, + "custom_order": int, + "workspace_state": str, // "todo" | "in_progress" | "done" + "added_at": datetime, + "updated_at": datetime +} +``` + +### שינוי מוצע: הוספת שדה `tags` + +```javascript +{ + "_id": ObjectId, + "collection_id": ObjectId, + "user_id": int, + "source": str, + "file_name": str, + "note": str, + "pinned": bool, + "custom_order": int, + "workspace_state": str, + "tags": [str], // ✨ שדה חדש - רשימת אימוג'י תגיות + "added_at": datetime, + "updated_at": datetime +} +``` + +### דוגמאות למסמכים עם תגיות + +```javascript +// דוגמה 1: קובץ עם באג דחוף +{ + "file_name": "auth_service.py", + "tags": ["🐛", "🔥", "1️⃣"], // באג דחוף, ראשון בתור + "note": "תיקון בעיית התחברות" +} + +// דוגמה 2: רעיון לעתיד +{ + "file_name": "ml_model.py", + "tags": ["💭", "🔮", "🧪"], // רעיון קסום וניסיוני + "note": "מודל ML חדש לניסוי" +} + +// דוגמה 3: דאטה-בייס סודי +{ + "file_name": "migration_001.sql", + "tags": ["🗄️", "🔐"], // דאטה-בייס רגיש + "note": "מיגרציה לסכמה חדשה" +} +``` + +--- + +## שינויים נדרשים ב-Backend + +### 1. קבועים ו-Validation + +**קובץ:** `database/collections_manager.py` + +הוסף בתחילת הקובץ: + +```python +# תגיות מותרות (whitelist) +ALLOWED_TAGS = [ + # עדיפות + '🐢', # לא דחוף + '🔥', # דחוף + + # סנטימנט + '🔮', # קסום + '♥️', # מועדף + + # אבטחה + '🔐', # סודי + + # סטטוס + '💭', # רעיון + '⏸️', # מושהה + '🎯', # מטרה + + # קטגוריה + '🐛', # באג + '🗄️', # דאטה-בייס + '🧪', # ניסיוני + + # סדר + '1️⃣', # ראשון + '2️⃣', # שני + '3️⃣', # שלישי +] + +# קטגוריות תגיות +TAG_CATEGORIES = { + 'priority': ['🐢', '🔥'], + 'sentiment': ['🔮', '♥️'], + 'security': ['🔐'], + 'status': ['💭', '⏸️', '🎯'], + 'category': ['🐛', '🗄️', '🧪'], + 'order': ['1️⃣', '2️⃣', '3️⃣'] +} + +# מטאדאטה לכל תגית +TAG_METADATA = { + '🐢': {'name_he': 'לא דחוף', 'name_en': 'low priority', 'category': 'priority'}, + '🔥': {'name_he': 'דחוף', 'name_en': 'urgent', 'category': 'priority'}, + '🔮': {'name_he': 'קסום', 'name_en': 'magic', 'category': 'sentiment'}, + '♥️': {'name_he': 'מועדף', 'name_en': 'favorite', 'category': 'sentiment'}, + '🔐': {'name_he': 'סודי', 'name_en': 'secret', 'category': 'security'}, + '💭': {'name_he': 'רעיון', 'name_en': 'idea', 'category': 'status'}, + '⏸️': {'name_he': 'מושהה', 'name_en': 'paused', 'category': 'status'}, + '🎯': {'name_he': 'מטרה', 'name_en': 'goal', 'category': 'status'}, + '🐛': {'name_he': 'באג', 'name_en': 'bug', 'category': 'category'}, + '🗄️': {'name_he': 'דאטה-בייס', 'name_en': 'database', 'category': 'category'}, + '🧪': {'name_he': 'ניסיוני', 'name_en': 'experimental', 'category': 'category'}, + '1️⃣': {'name_he': 'ראשון', 'name_en': 'first', 'category': 'order'}, + '2️⃣': {'name_he': 'שני', 'name_en': 'second', 'category': 'order'}, + '3️⃣': {'name_he': 'שלישי', 'name_en': 'third', 'category': 'order'}, +} + +# מגבלות +MAX_TAGS_PER_ITEM = 10 # מקסימום תגיות לפריט +``` + +### 2. פונקציית Validation + +הוסף פונקציה ל-`CollectionsManager`: + +```python +def _validate_tags(self, tags): + """ + מוודא שרשימת התגיות תקינה. + + Args: + tags: רשימת תגיות (אימוג'ים) + + Returns: + tuple: (is_valid, error_message) + """ + if tags is None: + return True, None + + if not isinstance(tags, list): + return False, "tags must be a list" + + if len(tags) > MAX_TAGS_PER_ITEM: + return False, f"maximum {MAX_TAGS_PER_ITEM} tags allowed per item" + + # בדיקת uniqueness + if len(tags) != len(set(tags)): + return False, "duplicate tags not allowed" + + # בדיקת whitelist + for tag in tags: + if tag not in ALLOWED_TAGS: + return False, f"invalid tag: {tag}" + + return True, None +``` + +### 3. עדכון `add_items()` לתמיכה בתגיות + +עדכן את המתודה `add_items()` ב-`CollectionsManager`: + +```python +def add_items(self, user_id, collection_id, items): + """ + הוספת פריטים לאוסף (מצב manual או mixed). + + Args: + items: רשימה של דיקטים עם source, file_name, note (אופציונלי), tags (אופציונלי) + """ + # ... קוד קיים לפני ... + + for item_dict in items: + source = item_dict.get('source', 'regular') + file_name = item_dict.get('file_name') + note = (item_dict.get('note') or '').strip()[:500] + tags = item_dict.get('tags', []) # ✨ קריאת תגיות + + # ולידציית תגיות + is_valid, error = self._validate_tags(tags) + if not is_valid: + raise ValueError(f"Invalid tags for {file_name}: {error}") + + # ... קוד קיים ... + + # יצירת המסמך + item_doc = { + 'collection_id': oid, + 'user_id': user_id, + 'source': source, + 'file_name': file_name, + 'note': note, + 'tags': tags, # ✨ שמירת תגיות + 'pinned': False, + 'custom_order': 0, + 'workspace_state': None, + 'added_at': datetime.utcnow(), + 'updated_at': datetime.utcnow() + } + + # ... המשך קוד קיים ... +``` + +### 4. פונקציה חדשה: `update_item_tags()` + +הוסף מתודה חדשה ל-`CollectionsManager`: + +```python +def update_item_tags(self, user_id, item_id, tags): + """ + עדכון תגיות של פריט קיים באוסף. + + Args: + user_id: מזהה משתמש + item_id: מזהה הפריט (collection_item _id) + tags: רשימת תגיות חדשה + + Returns: + dict: הפריט המעודכן או None + """ + # ולידציה + is_valid, error = self._validate_tags(tags) + if not is_valid: + raise ValueError(f"Invalid tags: {error}") + + try: + item_id_obj = ObjectId(item_id) + except: + return None + + # עדכון במסד הנתונים + result = self.db.collection_items.update_one( + { + '_id': item_id_obj, + 'user_id': user_id # ACL check + }, + { + '$set': { + 'tags': tags, + 'updated_at': datetime.utcnow() + } + } + ) + + if result.matched_count == 0: + return None + + # קריאת הפריט המעודכן + item = self.db.collection_items.find_one({'_id': item_id_obj}) + + # ביטול cache + if item: + cid = item['collection_id'] + cache.delete_pattern(f"collections_items:{user_id}:{cid}:*") + cache.delete_pattern(f"collections_detail:{user_id}:{cid}") + + emit_event('collections_item_tags_update', { + 'user_id': user_id, + 'item_id': str(item_id), + 'tags': tags + }) + + return self._public_item(item) if item else None +``` + +### 5. עדכון `_public_item()` להחזרת תגיות + +וודא ש-`_public_item()` מחזירה את שדה ה-`tags`: + +```python +def _public_item(self, doc): + """המרת מסמך פריט למבנה ציבורי""" + if not doc: + return None + + return { + 'id': str(doc['_id']), + 'source': doc.get('source', 'regular'), + 'file_name': doc.get('file_name'), + 'note': doc.get('note', ''), + 'tags': doc.get('tags', []), # ✨ החזרת תגיות + 'pinned': doc.get('pinned', False), + 'custom_order': doc.get('custom_order', 0), + 'workspace_state': doc.get('workspace_state'), + 'added_at': doc.get('added_at'), + 'updated_at': doc.get('updated_at') + } +``` + +### 6. פונקציה לקבלת מטאדאטה של תגיות + +```python +def get_tags_metadata(self): + """ + החזרת מטאדאטה על כל התגיות הזמינות. + + Returns: + dict: { + 'allowed_tags': [...], + 'categories': {...}, + 'metadata': {...} + } + """ + return { + 'allowed_tags': ALLOWED_TAGS, + 'categories': TAG_CATEGORIES, + 'metadata': TAG_METADATA + } +``` + +### 7. אינדקס במסד הנתונים + +הוסף אינדקס לשדה `tags` לצורך חיפוש מהיר: + +```python +# בפונקציה ensure_indexes() או ב-migration script +self.db.collection_items.create_index([ + ('collection_id', 1), + ('tags', 1) +]) +``` + +--- + +## שינויים נדרשים ב-Frontend + +### 1. API Functions + +**קובץ:** `webapp/static/js/collections.js` + +הוסף בתוך אובייקט ה-`api`: + +```javascript +const api = { + // ... פונקציות קיימות ... + + /** + * עדכון תגיות של פריט באוסף + */ + updateItemTags(itemId, tags) { + return fetch(`/api/collections/items/${itemId}/tags`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ tags }) + }).then(r => r.json()); + }, + + /** + * קבלת מטאדאטה של תגיות זמינות + */ + getTagsMetadata() { + return fetch('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/api/collections/tags/metadata') + .then(r => r.json()); + } +}; +``` + +### 2. קבועים גלובליים + +הוסף בתחילת הקובץ: + +```javascript +// תגיות זמינות (יטען מהשרת) +let TAGS_METADATA = null; + +// מטא-מידע על תגיות (default עד שיטען מהשרת) +const DEFAULT_TAGS_METADATA = { + allowed_tags: [ + '🐢', '🔥', '🔮', '♥️', '🔐', '💭', + '⏸️', '🎯', '🐛', '🗄️', '🧪', + '1️⃣', '2️⃣', '3️⃣' + ], + categories: { + priority: ['🐢', '🔥'], + sentiment: ['🔮', '♥️'], + security: ['🔐'], + status: ['💭', '⏸️', '🎯'], + category: ['🐛', '🗄️', '🧪'], + order: ['1️⃣', '2️⃣', '3️⃣'] + }, + metadata: { + '🐢': { name_he: 'לא דחוף', name_en: 'low priority', category: 'priority' }, + '🔥': { name_he: 'דחוף', name_en: 'urgent', category: 'priority' }, + '🔮': { name_he: 'קסום', name_en: 'magic', category: 'sentiment' }, + '♥️': { name_he: 'מועדף', name_en: 'favorite', category: 'sentiment' }, + '🔐': { name_he: 'סודי', name_en: 'secret', category: 'security' }, + '💭': { name_he: 'רעיון', name_en: 'idea', category: 'status' }, + '⏸️': { name_he: 'מושהה', name_en: 'paused', category: 'status' }, + '🎯': { name_he: 'מטרה', name_en: 'goal', category: 'status' }, + '🐛': { name_he: 'באג', name_en: 'bug', category: 'category' }, + '🗄️': { name_he: 'דאטה-בייס', name_en: 'database', category: 'category' }, + '🧪': { name_he: 'ניסיוני', name_en: 'experimental', category: 'category' }, + '1️⃣': { name_he: 'ראשון', name_en: 'first', category: 'order' }, + '2️⃣': { name_he: 'שני', name_en: 'second', category: 'order' }, + '3️⃣': { name_he: 'שלישי', name_en: 'third', category: 'order' } + } +}; +``` + +### 3. טעינת מטאדאטה בעת אתחול + +```javascript +/** + * אתחול מטאדאטה של תגיות + */ +async function initTagsMetadata() { + try { + const resp = await api.getTagsMetadata(); + if (resp.ok) { + TAGS_METADATA = resp; + } else { + TAGS_METADATA = DEFAULT_TAGS_METADATA; + } + } catch (err) { + console.error('Failed to load tags metadata:', err); + TAGS_METADATA = DEFAULT_TAGS_METADATA; + } +} + +// קריאה באתחול +document.addEventListener('DOMContentLoaded', async () => { + await initTagsMetadata(); + // ... המשך אתחול קיים ... +}); +``` + +### 4. רינדור תגיות בפריט + +הוסף פונקציה לרינדור תגיות: + +```javascript +/** + * בניית HTML לתגיות של פריט + * @param {Array} tags - רשימת תגיות (אימוג'ים) + * @param {String} itemId - מזהה הפריט + * @returns {String} HTML + */ +function buildItemTagsHtml(tags, itemId) { + if (!tags || tags.length === 0) { + return `אין תגיות`; + } + + const meta = TAGS_METADATA || DEFAULT_TAGS_METADATA; + + const tagsHtml = tags.map(tag => { + const info = meta.metadata[tag] || {}; + const tooltip = info.name_he || tag; + return `${tag}`; + }).join(''); + + return `
${tagsHtml}
`; +} +``` + +### 5. עדכון רינדור פריט בליסטה + +עדכן את הפונקציה שמרנדרת פריטים כדי לכלול תגיות: + +```javascript +/** + * רינדור פריט בודד ברשימה (עדכון לקוד קיים) + */ +function renderCollectionItem(item) { + const { id, file_name, source, pinned, tags = [] } = item; + + const tagsHtml = buildItemTagsHtml(tags, id); + + return ` +
+ ⋮⋮ + ${file_name} + ${tagsHtml} +
+ + + +
+
+ `; +} +``` + +### 6. מודל עריכת תגיות + +צור פונקציה לפתיחת מודל עריכת תגיות: + +```javascript +/** + * פתיחת מודל לעריכת תגיות של פריט + * @param {String} itemId - מזהה הפריט + * @param {Array} currentTags - תגיות נוכחיות + */ +function openTagsEditorModal(itemId, currentTags = []) { + const meta = TAGS_METADATA || DEFAULT_TAGS_METADATA; + + // בניית HTML של בוחר תגיות לפי קטגוריות + let categoriesHtml = ''; + + Object.entries(meta.categories).forEach(([catKey, catTags]) => { + const categoryNames = { + priority: 'עדיפות', + sentiment: 'סנטימנט', + security: 'אבטחה', + status: 'סטטוס', + category: 'קטגוריה', + order: 'סדר' + }; + + const catName = categoryNames[catKey] || catKey; + + const tagsHtml = catTags.map(tag => { + const info = meta.metadata[tag] || {}; + const selected = currentTags.includes(tag) ? 'selected' : ''; + return ` + + `; + }).join(''); + + categoriesHtml += ` +
+

${catName}

+
+ ${tagsHtml} +
+
+ `; + }); + + const modalHtml = ` + + `; + + // הוספת המודל ל-DOM + const existingModal = document.getElementById('tagsEditorModal'); + if (existingModal) existingModal.remove(); + + document.body.insertAdjacentHTML('beforeend', modalHtml); + const modal = document.getElementById('tagsEditorModal'); + + // Selected tags state + let selectedTags = [...currentTags]; + + // Event listeners לבחירת תגיות + modal.querySelectorAll('.tag-option').forEach(btn => { + btn.addEventListener('click', () => { + const tag = btn.dataset.tag; + + if (selectedTags.includes(tag)) { + // הסרת תגית + selectedTags = selectedTags.filter(t => t !== tag); + btn.classList.remove('selected'); + } else { + // הוספת תגית + if (selectedTags.length >= 10) { + showToast('ניתן לבחור עד 10 תגיות', 'warning'); + return; + } + selectedTags.push(tag); + btn.classList.add('selected'); + } + + // עדכון תצוגת תגיות נבחרות + updateSelectedTagsPreview(); + }); + }); + + // עדכון תצוגה + function updateSelectedTagsPreview() { + const container = modal.querySelector('.selected-tags-container'); + if (selectedTags.length === 0) { + container.innerHTML = 'לא נבחרו תגיות'; + } else { + container.innerHTML = selectedTags + .map(t => `${t}`) + .join(''); + } + } + + // שמירת תגיות + document.getElementById('saveTagsBtn').addEventListener('click', async () => { + try { + const resp = await api.updateItemTags(itemId, selectedTags); + if (resp.ok) { + showToast('התגיות עודכנו בהצלחה', 'success'); + modal.remove(); + + // רענון תצוגת הפריט + await renderCollectionItems(currentCollectionId); + } else { + showToast(resp.error || 'שגיאה בעדכון תגיות', 'error'); + } + } catch (err) { + console.error('Error updating tags:', err); + showToast('שגיאה בעדכון תגיות', 'error'); + } + }); + + // סגירת מודל + modal.querySelectorAll('.modal-close').forEach(btn => { + btn.addEventListener('click', () => modal.remove()); + }); + + // סגירה בלחיצה מחוץ למודל + modal.addEventListener('click', (e) => { + if (e.target === modal) modal.remove(); + }); + + // הצגת המודל + modal.style.display = 'flex'; +} +``` + +### 7. Event Listeners לכפתור עריכת תגיות + +הוסף event delegation לכפתור עריכת התגיות: + +```javascript +/** + * Wire up tags editor button clicks + */ +function wireTagsEditorButtons(container) { + container.addEventListener('click', (e) => { + const btn = e.target.closest('.btn-tag-edit'); + if (!btn) return; + + e.preventDefault(); + e.stopPropagation(); + + const itemId = btn.dataset.itemId; + const itemEl = container.querySelector(`[data-item-id="${itemId}"]`); + + // מציאת תגיות נוכחיות + let currentTags = []; + const tagsContainer = itemEl.querySelector('.item-tags'); + if (tagsContainer) { + currentTags = Array.from(tagsContainer.querySelectorAll('.item-tag')) + .map(el => el.dataset.tag); + } + + openTagsEditorModal(itemId, currentTags); + }); +} + +// קריאה לפונקציה זו בעת רינדור האוסף +function renderCollectionItems(collectionId) { + // ... קוד קיים ... + + const contentArea = document.querySelector('.collections-content'); + wireTagsEditorButtons(contentArea); + + // ... המשך קוד ... +} +``` + +--- + +## UI/UX Components + +### 1. סטיילינג CSS + +**קובץ:** `webapp/static/css/collections.css` + +הוסף CSS עבור תגיות: + +```css +/* ========== Item Tags Styling ========== */ + +.item-tags { + display: inline-flex; + gap: 4px; + align-items: center; + margin: 0 8px; + flex-wrap: wrap; +} + +.item-tag { + display: inline-flex; + align-items: center; + font-size: 16px; + padding: 2px 4px; + border-radius: 4px; + background: var(--bg-secondary, #f0f0f0); + cursor: help; + transition: transform 0.15s; +} + +.item-tag:hover { + transform: scale(1.15); +} + +.item-tags-empty { + font-size: 12px; + color: var(--text-muted, #999); + font-style: italic; +} + +/* ========== Tags Editor Modal ========== */ + +.tags-editor-modal .modal-content { + max-width: 600px; + max-height: 80vh; + overflow-y: auto; +} + +.tags-selected-preview { + margin-bottom: 20px; + padding: 12px; + background: var(--bg-secondary, #f5f5f5); + border-radius: 8px; +} + +.selected-tags-container { + display: flex; + gap: 6px; + flex-wrap: wrap; + margin-top: 8px; +} + +.selected-tag { + font-size: 20px; + padding: 4px 8px; + background: var(--color-primary, #4a90e2); + color: white; + border-radius: 6px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.no-tags { + font-size: 13px; + color: var(--text-muted, #999); + font-style: italic; +} + +/* ========== Tag Categories ========== */ + +.tags-categories { + display: flex; + flex-direction: column; + gap: 20px; +} + +.tag-category { + border: 1px solid var(--border-color, #e0e0e0); + border-radius: 8px; + padding: 12px; + background: var(--bg-primary, white); +} + +.tag-category-title { + font-size: 14px; + font-weight: 600; + margin: 0 0 10px 0; + color: var(--text-primary, #333); +} + +.tag-category-options { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.tag-option { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 6px 10px; + font-size: 18px; + border: 2px solid var(--border-color, #ddd); + border-radius: 8px; + background: var(--bg-primary, white); + cursor: pointer; + transition: all 0.2s; +} + +.tag-option:hover { + border-color: var(--color-primary, #4a90e2); + transform: scale(1.05); +} + +.tag-option.selected { + border-color: var(--color-primary, #4a90e2); + background: var(--color-primary-light, #e3f2fd); + box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.2); +} + +.tag-name { + font-size: 12px; + color: var(--text-secondary, #666); +} + +/* ========== Item Actions with Tag Button ========== */ + +.item-actions { + display: flex; + gap: 4px; + align-items: center; + margin-right: auto; +} + +.btn-tag-edit { + padding: 4px 8px; + font-size: 16px; + border: none; + background: transparent; + cursor: pointer; + border-radius: 4px; + transition: background 0.2s; +} + +.btn-tag-edit:hover { + background: var(--bg-hover, #f0f0f0); +} + +/* ========== Dark Mode Support ========== */ + +[data-theme="dark"] .item-tag { + background: var(--bg-tertiary, #2a2a2a); +} + +[data-theme="dark"] .tags-selected-preview { + background: var(--bg-tertiary, #2a2a2a); +} + +[data-theme="dark"] .tag-category { + background: var(--bg-secondary, #1e1e1e); + border-color: var(--border-color, #444); +} + +[data-theme="dark"] .tag-option { + background: var(--bg-secondary, #1e1e1e); + border-color: var(--border-color, #444); +} + +[data-theme="dark"] .tag-option.selected { + background: var(--color-primary-dark, #1e4d7b); +} +``` + +### 2. עדכון מבנה HTML של פריט + +המבנה המעודכן של פריט ברשימה: + +```html +
+ ⋮⋮ + file.py + + +
+ 🔥 + 🐛 + 1️⃣ +
+ + +
+ + + +
+
+``` + +### 3. תמיכה ב-Workspace Kanban + +עדכון כרטיסי הקנבן כדי להציג תגיות: + +```javascript +function buildWorkspaceCardHtml(item) { + const { id, file_name, tags = [] } = item; + const tagsHtml = buildItemTagsHtml(tags, id); + + return ` +
+ +
+
${file_name}
+ ${tagsHtml} +
נוסף ${formatDate(item.added_at)}
+
+
+ + + +
+
+ `; +} +``` + +--- + +## דוגמאות קוד + +### Backend API Endpoint + +**קובץ:** `webapp/collections_api.py` + +```python +@collections_bp.route('/items//tags', methods=['PATCH']) +@require_auth +@traced() +def update_item_tags(item_id): + """ + עדכון תגיות של פריט באוסף. + + Body: {"tags": ["🔥", "🐛"]} + """ + user_id = session['user_id'] + data = request.get_json() or {} + tags = data.get('tags', []) + + mgr = CollectionsManager() + + try: + updated_item = mgr.update_item_tags(user_id, item_id, tags) + + if not updated_item: + return jsonify({'ok': False, 'error': 'Item not found'}), 404 + + return jsonify({ + 'ok': True, + 'item': updated_item + }) + + except ValueError as e: + return jsonify({'ok': False, 'error': str(e)}), 400 + except Exception as e: + logger.exception(f'Error updating item tags: {e}') + emit_event('collections_item_tags_update_error', { + 'user_id': user_id, + 'item_id': item_id, + 'error': str(e) + }) + return jsonify({'ok': False, 'error': 'Internal error'}), 500 + + +@collections_bp.route('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/tags/metadata', methods=['GET']) +@traced() +def get_tags_metadata(): + """ + קבלת מטאדאטה של תגיות זמינות. + """ + mgr = CollectionsManager() + metadata = mgr.get_tags_metadata() + + return jsonify({ + 'ok': True, + **metadata + }) +``` + +### Frontend - הוספת פריט עם תגיות + +```javascript +async function addFileToCollection(collectionId, fileName, tags = []) { + const resp = await api.addItems(collectionId, [ + { + source: 'regular', + file_name: fileName, + note: '', + tags: tags // ✨ תגיות + } + ]); + + if (resp.ok) { + showToast('הקובץ נוסף בהצלחה', 'success'); + await renderCollectionItems(collectionId); + } else { + showToast(resp.error || 'שגיאה בהוספת קובץ', 'error'); + } +} + +// דוגמת שימוש +addFileToCollection('collection123', 'auth.py', ['🔥', '🐛', '1️⃣']); +``` + +### סינון פריטים לפי תגיות (Frontend) + +```javascript +/** + * סינון פריטים לפי תגיות + * @param {Array} items - כל הפריטים + * @param {Array} filterTags - תגיות לסינון + * @returns {Array} פריטים מסוננים + */ +function filterItemsByTags(items, filterTags) { + if (!filterTags || filterTags.length === 0) { + return items; + } + + return items.filter(item => { + const itemTags = item.tags || []; + // בדיקה אם הפריט מכיל לפחות אחת מהתגיות + return filterTags.some(tag => itemTags.includes(tag)); + }); +} + +// דוגמה: הצגת רק פריטים דחופים +const urgentItems = filterItemsByTags(allItems, ['🔥']); +``` + +--- + +## שלבי מימוש מומלצים + +### שלב 1: Backend Infrastructure (יום 1) + +1. **עדכון `collections_manager.py`:** + - ✅ הוספת קבועים: `ALLOWED_TAGS`, `TAG_CATEGORIES`, `TAG_METADATA` + - ✅ פונקציית `_validate_tags()` + - ✅ עדכון `add_items()` לתמיכה בתגיות + - ✅ פונקציה חדשה `update_item_tags()` + - ✅ עדכון `_public_item()` להחזרת tags + - ✅ פונקציה `get_tags_metadata()` + +2. **אינדקס במסד נתונים:** + ```bash + # הרצת script ליצירת אינדקס + python -c "from database.collections_manager import CollectionsManager; mgr = CollectionsManager(); mgr.db.collection_items.create_index([('collection_id', 1), ('tags', 1)])" + ``` + +3. **יחידות בדיקה:** + - בדיקת validation של תגיות + - בדיקת הוספת פריט עם תגיות + - בדיקת עדכון תגיות + - בדיקת החזרת metadata + +### שלב 2: API Layer (יום 1-2) + +1. **עדכון `collections_api.py`:** + - ✅ Endpoint: `PATCH /api/collections/items//tags` + - ✅ Endpoint: `GET /api/collections/tags/metadata` + - ✅ עדכון response של `GET /api/collections//items` להחזיר tags + +2. **בדיקות API:** + ```bash + # בדיקה ידנית עם curl + curl -X PATCH http://localhost:5000/api/collections/items/ABC123/tags \ + -H "Content-Type: application/json" \ + -d '{"tags": ["🔥", "🐛"]}' + ``` + +### שלב 3: Frontend - Core Functionality (יום 2-3) + +1. **עדכון `collections.js`:** + - ✅ הוספת `api.updateItemTags()` + - ✅ הוספת `api.getTagsMetadata()` + - ✅ פונקציה `initTagsMetadata()` + - ✅ פונקציה `buildItemTagsHtml()` + - ✅ עדכון `renderCollectionItem()` להצגת תגיות + +2. **בדיקות ידניות:** + - פתיחת דף Collections + - וידוא טעינת metadata + - וידוא הצגת תגיות בפריטים קיימים + +### שלב 4: Tags Editor UI (יום 3-4) + +1. **פיתוח מודל עריכה:** + - ✅ פונקציה `openTagsEditorModal()` + - ✅ Event listeners לבחירת תגיות + - ✅ עדכון תצוגה בזמן אמת + - ✅ שמירת תגיות + +2. **Wire up כפתור עריכה:** + - ✅ פונקציה `wireTagsEditorButtons()` + - ✅ הוספת כפתור 🏷️ בכל פריט + +### שלב 5: CSS Styling (יום 4) + +1. **עדכון `collections.css`:** + - ✅ סטיילינג `.item-tags`, `.item-tag` + - ✅ סטיילינג `.tags-editor-modal` + - ✅ סטיילינג `.tag-category`, `.tag-option` + - ✅ תמיכה ב-Dark Mode + +2. **בדיקות UI:** + - תצוגה תקינה במצב Light/Dark + - Responsive design (<900px) + - Accessibility (keyboard navigation) + +### שלב 6: Workspace Integration (יום 5) + +1. **עדכון Kanban Board:** + - ✅ עדכון `buildWorkspaceCardHtml()` להצגת תגיות + - ✅ Wire up כפתור עריכה בכרטיסים + - ✅ תמיכה ב-drag & drop עם תגיות + +### שלב 7: Advanced Features (אופציונלי, יום 6-7) + +1. **סינון לפי תגיות:** + - פונקציה `filterItemsByTags()` + - UI dropdown לסינון + - Cache תוצאות סינון + +2. **מיון לפי תגיות:** + - מיון לפי עדיפות (🔥 לפני 🐢) + - מיון לפי סדר (1️⃣ → 2️⃣ → 3️⃣) + +3. **Bulk operations:** + - בחירת מספר פריטים + - עדכון תגיות מרובות בבת אחת + +### שלב 8: Testing & Polish (יום 7-8) + +1. **בדיקות אוטומטיות:** + - Unit tests ל-Backend + - Integration tests ל-API + - E2E tests עם Playwright/Selenium + +2. **בדיקות ידניות:** + - Flow מלא: יצירה → עריכה → מחיקה + - Edge cases (10 tags, Unicode, RTL) + - Performance (1000+ items) + +3. **דוקומנטציה:** + - עדכון `docs/user/my_collections.rst` + - Changelog + - Screenshots + +--- + +## שיקולים נוספים + +### 1. ביצועים (Performance) + +**אופטימיזציות:** +- אינדקס על שדה `tags` לחיפוש מהיר +- Cache metadata של תגיות (TTL 1 hour) +- Lazy loading של פריטים עם pagination +- Debounce על עדכוני tags (300ms) + +**מדדים לניטור:** +```python +# Time to fetch items with tags +# Time to update tags +# Cache hit rate for metadata +``` + +### 2. אבטחה (Security) + +**וולידציות:** +- ✅ Whitelist קפדנית של תגיות +- ✅ מגבלה של 10 tags per item +- ✅ ACL check - user_id filtering +- ✅ Sanitization של input +- ✅ Rate limiting על API endpoints + +**מניעת XSS:** +```javascript +// כבר מטופל - אימוג'ים הם Unicode safe +// אך וודא שלא מאפשרים HTML/JavaScript בתגיות +``` + +### 3. נגישות (Accessibility) + +**WCAG 2.1 AA:** +- Keyboard navigation במודל התגיות (Tab, Enter, Escape) +- ARIA labels לכפתורים +- Focus management בפתיחת/סגירת מודל +- Screen reader support (alt text לאימוג'ים) + +```html + +``` + +### 4. Internationalization (i18n) + +התגיות כבר דו-לשוניות (`name_he`, `name_en`): + +```javascript +const lang = document.documentElement.lang || 'he'; +const tagName = TAG_METADATA[tag][`name_${lang}`]; +``` + +### 5. Analytics & Observability + +**אירועים לניטור:** +```python +emit_event('collections_tags_added', { + 'user_id': user_id, + 'item_id': item_id, + 'tags': tags, + 'count': len(tags) +}) + +emit_event('collections_tags_removed', {...}) +emit_event('collections_tags_filtered', {...}) +``` + +**מטריקות:** +- כמה משתמשים משתמשים בתיוג? +- מהן התגיות הפופולריות ביותר? +- כמה תגיות בממוצע לפריט? + +### 6. Migration של נתונים קיימים + +אם יש פריטים קיימים ב-DB: + +```python +# Migration script +def migrate_existing_items_add_tags_field(): + """הוספת שדה tags ריק לפריטים קיימים""" + from database.collections_manager import CollectionsManager + + mgr = CollectionsManager() + result = mgr.db.collection_items.update_many( + {'tags': {'$exists': False}}, + {'$set': {'tags': []}} + ) + + print(f"Updated {result.modified_count} items with empty tags field") + +# הרצה +if __name__ == '__main__': + migrate_existing_items_add_tags_field() +``` + +### 7. Feature Flags + +**שלבי rollout:** +```python +# config.py +FEATURE_COLLECTIONS_TAGS = os.getenv('FEATURE_COLLECTIONS_TAGS', 'true').lower() == 'true' + +# בקוד +if FEATURE_COLLECTIONS_TAGS: + # הצגת UI של תגיות + pass +``` + +### 8. חיפוש ע"י תגיות (Smart Collections) + +הרחבה עתידית - אפשר להוסיף לחיפוש של Smart Collections: + +```python +# ב-CollectionsManager +def compute_smart_items(self, user_id, rules, limit=200): + # ... קוד קיים ... + + # הוספת חיפוש לפי תגיות + if rules.get('tags'): + pipeline.append({ + '$match': { + 'tags': {'$all': rules['tags']} # כל התגיות חייבות להתאים + } + }) +``` + +### 9. Export/Import של תגיות + +תמיכה ב-export ל-JSON/CSV: + +```javascript +function exportCollectionWithTags(collectionId) { + const items = await api.getItems(collectionId); + + const csv = items.map(item => { + return `${item.file_name},"${(item.tags || []).join(',')}",${item.note}`; + }).join('\n'); + + downloadFile('collection_export.csv', csv); +} +``` + +--- + +## סיכום + +### מה כלול במדריך זה: + +✅ **ארכיטקטורה מפורטת** של My Collections הקיים +✅ **מבנה נתונים** מוצע לתיוג עם 14 אימוג'ים +✅ **שינויים Backend** - validation, CRUD, API endpoints +✅ **שינויים Frontend** - UI components, מודל עריכה, רינדור +✅ **CSS Styling** מלא עם תמיכה ב-Dark Mode +✅ **דוגמאות קוד** מוכנות לשימוש +✅ **שלבי מימוש** צעד-אחר-צעד (8 ימים) +✅ **שיקולים** - ביצועים, אבטחה, נגישות + +### הצעדים הבאים: + +1. **תחילת פיתוח** - התחל בשלב 1 (Backend Infrastructure) +2. **סקירת קוד** - וודא שכל שינוי עובר code review +3. **בדיקות** - כתוב tests לכל שכבה +4. **דוקומנטציה** - עדכן את תיעוד המשתמש +5. **Rollout** - השק בהדרגה עם feature flag + +--- + +**נוצר עבור:** פרויקט CodeBot +**גרסה:** 1.0 +**תאריך:** 2026-01-29 +**כותב:** Claude Code Assistant