diff --git a/app/__init__.py b/app/__init__.py index 0e99557..e2cb096 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -21,6 +21,108 @@ class Config: PERMANENT_SESSION_LIFETIME = timedelta(hours=12) SESSION_TYPE = 'filesystem' +def init_db(app): + """Initialize the database and create tables if they don't exist.""" + with app.app_context(): + db.create_all() + from app.models.user import User + # Create a demo user if no users exist + if User.query.count() == 0: + from app.models.document import Document, Category, Tag + from werkzeug.security import generate_password_hash + print('Creating demo user...') + + # Create demo user + demo_user = User(username='demo') + demo_user.set_password('password') + db.session.add(demo_user) + db.session.flush() # To get the user ID + + # Create a root category for the demo user + root_category = Category( + name='My Documents', + icon='mdi-folder', + description='Default document category', + user_id=demo_user.id, + is_root=True + ) + db.session.add(root_category) + + # Create some sample categories + categories = [ + Category( + name='Vim Commands', + icon='mdi-vim', + user_id=demo_user.id, + description='Essential Vim commands and shortcuts' + ), + Category( + name='Flask Development', + icon='mdi-flask', + user_id=demo_user.id, + description='Flask web development notes' + ), + Category( + name='Python Snippets', + icon='mdi-language-python', + user_id=demo_user.id, + description='Useful Python code snippets' + ) + ] + + for category in categories: + db.session.add(category) + + # Create a sample document + sample_doc = Document( + title='Getting Started with Vim', + content="""# Getting Started with Vim + +## Basic Commands + +### Movement +- `h` - move left +- `j` - move down +- `k` - move up +- `l` - move right + +### Modes +- `i` - enter insert mode +- `Esc` - return to normal mode +- `v` - enter visual mode +- `:` - enter command mode + +> Vim has a steep learning curve, but it's worth it! + +> [!TIP] +> Use `vimtutor` to learn Vim basics interactively. + +> [!NOTE] +> Vim is available on almost all Unix-like systems. +""", + user_id=demo_user.id, + category_id=categories[0].id + ) + db.session.add(sample_doc) + + # Create some tags + tags = [ + Tag(name='vim', user_id=demo_user.id, color='#50fa7b'), + Tag(name='editor', user_id=demo_user.id, color='#bd93f9'), + Tag(name='tutorial', user_id=demo_user.id, color='#ff79c6') + ] + + for tag in tags: + db.session.add(tag) + + # Associate tags with the document + sample_doc.tags = tags + + # Commit all changes + db.session.commit() + + print('Demo user and sample data created successfully!') + def create_app(config_class=Config): app = Flask(__name__) app.config.from_object(config_class) @@ -51,6 +153,9 @@ def create_app(config_class=Config): # Create app instance app = create_app() +# Initialize database +init_db(app) + # Import models after db initialization to avoid circular imports from app.models.document import Document, Category, Tag from app.models.user import User \ No newline at end of file diff --git a/app/auth/__pycache__/__init__.cpython-313.pyc b/app/auth/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 64ff801..0000000 Binary files a/app/auth/__pycache__/__init__.cpython-313.pyc and /dev/null differ diff --git a/app/auth/__pycache__/forms.cpython-313.pyc b/app/auth/__pycache__/forms.cpython-313.pyc deleted file mode 100644 index fe292b5..0000000 Binary files a/app/auth/__pycache__/forms.cpython-313.pyc and /dev/null differ diff --git a/app/auth/__pycache__/routes.cpython-313.pyc b/app/auth/__pycache__/routes.cpython-313.pyc deleted file mode 100644 index 81b8c71..0000000 Binary files a/app/auth/__pycache__/routes.cpython-313.pyc and /dev/null differ diff --git a/app/auth/routes.py b/app/auth/routes.py index bf591d1..570c3b6 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -45,9 +45,9 @@ def signup(): # Create root category for the user root_category = Category( - name='Library', - icon='mdi-bookshelf', - description='General storage for documents and categories', + name='', # Empty name for the root + icon='mdi-folder-outline', + description='Default container for documents and categories', user_id=user.id, is_root=True ) diff --git a/app/models/__pycache__/__init__.cpython-313.pyc b/app/models/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 674d89a..0000000 Binary files a/app/models/__pycache__/__init__.cpython-313.pyc and /dev/null differ diff --git a/app/models/__pycache__/document.cpython-313.pyc b/app/models/__pycache__/document.cpython-313.pyc deleted file mode 100644 index 58f71a2..0000000 Binary files a/app/models/__pycache__/document.cpython-313.pyc and /dev/null differ diff --git a/app/models/__pycache__/user.cpython-313.pyc b/app/models/__pycache__/user.cpython-313.pyc deleted file mode 100644 index a2bd566..0000000 Binary files a/app/models/__pycache__/user.cpython-313.pyc and /dev/null differ diff --git a/app/models/document.py b/app/models/document.py index 7958751..01c8b25 100644 --- a/app/models/document.py +++ b/app/models/document.py @@ -48,10 +48,18 @@ class Category(db.Model): def __repr__(self): return f'' + @property + def display_name(self): + """Return a display name for the category, showing 'Root' for the root category with empty name""" + if self.is_root and not self.name: + return "Root" + return self.name + def to_dict(self): return { 'id': self.id, - 'name': self.name, + 'name': self.name or '', # Ensure name is never null + 'display_name': self.display_name, 'icon': self.icon, 'description': self.description, 'parent_id': self.parent_id, diff --git a/app/routes.py b/app/routes.py index 76f055f..60b26b4 100644 --- a/app/routes.py +++ b/app/routes.py @@ -7,6 +7,7 @@ import json from datetime import datetime import io from sqlalchemy import or_ +import re main = Blueprint('main', __name__) @@ -164,9 +165,9 @@ def save_document(): if not root_category: # Create a root category if it doesn't exist root_category = Category( - name='Library', - icon='mdi-bookshelf', - description='General storage for documents and categories', + name='', + icon='mdi-folder-outline', + description='Default container for documents and categories', user_id=current_user.id, is_root=True ) @@ -341,54 +342,64 @@ def delete_category(category_id): return jsonify({'success': True}) -@main.route('/api/search', methods=['GET']) -@login_required +@main.route('/search') def search_documents(): - """Search for documents by title, content, or tags""" query = request.args.get('q', '') + user_id = current_user.id - if not query or len(query) < 2: - return jsonify({'results': []}) + if not query: + return jsonify([]) - # Search in title, content, and tags for current user's documents only - docs = Document.query.filter( - Document.user_id == current_user.id, - or_( - Document.title.ilike(f'%{query}%'), - Document.content.ilike(f'%{query}%'), - Document.tags.any(Tag.name.ilike(f'%{query}%')) - ) - ).limit(10).all() + # Parse hashtags from the query + hashtags = re.findall(r'#(\w+)', query) + # Remove hashtags from the main query text + cleaned_query = re.sub(r'#\w+', '', query).strip() + + # Base query for documents + base_query = Document.query.filter_by(user_id=user_id) results = [] - for doc in docs: - # Find match in content - match = None - if query.lower() in doc.content.lower(): - # Find the sentence containing the match - content_lower = doc.content.lower() - query_pos = content_lower.find(query.lower()) - - # Get a snippet around the match - start = max(0, content_lower.rfind('.', 0, query_pos) + 1) - end = content_lower.find('.', query_pos) - if end == -1: - end = min(len(doc.content), query_pos + 200) - - match = doc.content[start:end].strip() + + # If we have a cleaned query (non-hashtag part), search by title and content + if cleaned_query: + title_content_results = base_query.filter( + or_( + Document.title.ilike(f'%{cleaned_query}%'), + Document.content.ilike(f'%{cleaned_query}%') + ) + ).all() + results.extend(title_content_results) + + # If we have hashtags, search by tags (OR operation between tags) + if hashtags: + tag_results = base_query.join(document_tags).join(Tag).filter( + Tag.name.in_(hashtags) + ).all() - # Get category name - category_name = doc.category.name if doc.category else None - - results.append({ + # Add unique tag results to the results list + for doc in tag_results: + if doc not in results: + results.append(doc) + + # If no specific search criteria (cleaned query or hashtags), return empty list + if not cleaned_query and not hashtags: + return jsonify([]) + + # Convert to list of dictionaries for JSON response + docs_list = [] + for doc in results: + doc_dict = { 'id': doc.id, 'title': doc.title, - 'category': category_name, - 'tags': [tag.name for tag in doc.tags], - 'match': match - }) + 'preview': doc.content[:150] + '...' if len(doc.content) > 150 else doc.content, + 'category_id': doc.category_id, + 'created_at': doc.created_at.strftime('%Y-%m-%d %H:%M'), + 'updated_at': doc.updated_at.strftime('%Y-%m-%d %H:%M') if doc.updated_at else None, + 'tags': [tag.name for tag in doc.tags] + } + docs_list.append(doc_dict) - return jsonify({'results': results}) + return jsonify(docs_list) @main.route('/api/tags', methods=['GET']) @login_required @@ -433,8 +444,10 @@ def new_category(): if parent_id: parent = Category.query.filter_by(id=parent_id, user_id=current_user.id).first_or_404() - - return render_template('category_edit.html', category=None, parent=parent) + return render_template('category_edit.html', category=None, parent=parent) + else: + # No parent specified, creating a top-level category + return render_template('category_edit.html', category=None, parent=None) @main.route('/category//edit', methods=['GET']) @login_required diff --git a/app/static/css/main.css b/app/static/css/main.css index d5db682..4995e6c 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -35,8 +35,7 @@ /* Active view styling */ .active-view { - background-color: rgba(76, 175, 80, 0.2); /* Primary color with opacity */ - color: #4CAF50; /* Primary color */ + @apply bg-primary/20 text-primary; } /* Fix for sidebar hiding */ @@ -78,6 +77,16 @@ z-index: 10; } +/* Sidebar section headers */ +.sidebar-section-header { + font-size: 0.75rem; + text-transform: uppercase; + color: rgba(156, 163, 175, 0.7); + letter-spacing: 0.05em; + padding: 0.5rem 1rem 0.25rem; + margin-top: 0.5rem; +} + /* Drag and drop styles */ .drop-target { background-color: rgba(80, 250, 123, 0.15); @@ -172,4 +181,70 @@ .markdown-body .admonition-danger .admonition-title { color: #cf222e; +} + +/* Modern Sidebar Styling */ +aside { + background-color: #1a1b26; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.2); +} + +/* Reduce vertical spacing of sidebar items */ +aside nav ul li { + margin: 0.15rem 0; +} + +/* Better styling for sidebar links */ +aside nav ul li a { + padding: 0.4rem 0.75rem; + border-radius: 0.25rem; + transition: all 0.15s ease; +} + +aside nav ul li a:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +/* Remove the border-left styling in category trees */ +aside nav ul.ml-3.pl-3.border-l.border-gray-700, +.ml-3.pl-3.border-l.border-gray-700, +.ml-2.pl-2.border-l.border-gray-700 { + margin-left: 1.25rem !important; + padding-left: 0 !important; + border-left: none !important; +} + +/* Better nested document styling */ +.document-item a, +.category-item a { + font-size: 0.875rem; + display: flex; + align-items: center; + padding: 0.35rem 0.5rem; +} + +/* Cleaner category headers */ +aside nav ul li div.block.font-medium { + padding: 0.4rem 0.75rem; + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: rgba(255, 255, 255, 0.5); + margin-top: 1rem; +} + +/* Make the toggle buttons more subtle */ +.toggle-btn { + opacity: 0.6; + transition: all 0.15s ease; +} + +.toggle-btn:hover { + opacity: 1; +} + +/* Better styling for the primary action button */ +aside nav ul li a.bg-primary { + margin-top: 0.5rem; + box-shadow: 0 3px 8px rgba(80, 250, 123, 0.2); } \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index e68e656..dc23bb0 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -56,49 +56,53 @@
@@ -72,7 +80,7 @@
-

{{ subcategory.name }}

+

{{ subcategory.display_name }}

{% if subcategory.description %} @@ -94,7 +102,7 @@

New Subcategory

-

Add a subcategory to {{ category.name }}

+

Add a subcategory to {{ category.display_name }}

diff --git a/app/templates/category_edit.html b/app/templates/category_edit.html index 4d95598..5b61c6f 100644 --- a/app/templates/category_edit.html +++ b/app/templates/category_edit.html @@ -8,9 +8,15 @@ +{% if category %} Back +{% else %} + + Back + +{% endif %} {% if category and not category.is_root %}