From 3a16f266dad20a708af2e7c17f9d06ea781a4206 Mon Sep 17 00:00:00 2001 From: pika Date: Thu, 17 Apr 2025 15:36:24 +0200 Subject: [PATCH] wip --- app/__init__.py | 105 ++++++ app/auth/__pycache__/__init__.cpython-313.pyc | Bin 293 -> 0 bytes app/auth/__pycache__/forms.cpython-313.pyc | Bin 1941 -> 0 bytes app/auth/__pycache__/routes.cpython-313.pyc | Bin 4588 -> 0 bytes app/auth/routes.py | 6 +- .../__pycache__/__init__.cpython-313.pyc | Bin 163 -> 0 bytes .../__pycache__/document.cpython-313.pyc | Bin 6298 -> 0 bytes app/models/__pycache__/user.cpython-313.pyc | Bin 2680 -> 0 bytes app/models/document.py | 10 +- app/routes.py | 99 +++--- app/static/css/main.css | 79 ++++- app/templates/base.html | 303 +++++++++++++----- app/templates/category.html | 26 +- app/templates/category_edit.html | 46 ++- app/templates/index.html | 6 + 15 files changed, 511 insertions(+), 169 deletions(-) delete mode 100644 app/auth/__pycache__/__init__.cpython-313.pyc delete mode 100644 app/auth/__pycache__/forms.cpython-313.pyc delete mode 100644 app/auth/__pycache__/routes.cpython-313.pyc delete mode 100644 app/models/__pycache__/__init__.cpython-313.pyc delete mode 100644 app/models/__pycache__/document.cpython-313.pyc delete mode 100644 app/models/__pycache__/user.cpython-313.pyc 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 64ff801f6402db64f6bced773e5d198d8cbf596f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 293 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 A8~^|S 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 674d89affa23fbedecf3ae9512f6f214a1a2cb1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 163 zcmey&%ge<81U&YC(-na9V-N=h7@>^MEI`IohI9r^M!%H|MNB~6XOPq_C;i;K68(() z+*JLP#F9jPm;B_?+|<01V*QlVGJUt4#Nuq-u*_Utpm?!&M`lJw#v*1Q3jna{C)NM} diff --git a/app/models/__pycache__/document.cpython-313.pyc b/app/models/__pycache__/document.cpython-313.pyc deleted file mode 100644 index 58f71a20afa01e623a1f15d454cfcb339f57cd26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6298 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 K;yW$P3jYs+Q8$|a 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 %}