Allow uploading of files and folders.
This commit is contained in:
@@ -52,6 +52,18 @@ class Note(db.Model):
|
||||
# Pin important notes
|
||||
is_pinned = db.Column(db.Boolean, default=False)
|
||||
|
||||
# File-based note support
|
||||
is_file_based = db.Column(db.Boolean, default=False) # True if note was created from uploaded file
|
||||
file_path = db.Column(db.String(500), nullable=True) # Path to uploaded file
|
||||
file_type = db.Column(db.String(20), nullable=True) # 'markdown', 'image', etc.
|
||||
original_filename = db.Column(db.String(255), nullable=True) # Original uploaded filename
|
||||
file_size = db.Column(db.Integer, nullable=True) # File size in bytes
|
||||
mime_type = db.Column(db.String(100), nullable=True) # MIME type of file
|
||||
|
||||
# For images
|
||||
image_width = db.Column(db.Integer, nullable=True)
|
||||
image_height = db.Column(db.Integer, nullable=True)
|
||||
|
||||
# Soft delete
|
||||
is_archived = db.Column(db.Boolean, default=False)
|
||||
archived_at = db.Column(db.DateTime, nullable=True)
|
||||
@@ -162,12 +174,18 @@ class Note(db.Model):
|
||||
return text
|
||||
|
||||
def render_html(self):
|
||||
"""Render markdown content to HTML"""
|
||||
"""Render markdown content to HTML with Wiki-style link support"""
|
||||
try:
|
||||
import markdown
|
||||
from frontmatter_utils import parse_frontmatter
|
||||
from wiki_links import process_wiki_links
|
||||
|
||||
# Extract body content without frontmatter
|
||||
_, body = parse_frontmatter(self.content)
|
||||
|
||||
# Process Wiki-style links before markdown rendering
|
||||
body = process_wiki_links(body, current_note_id=self.id)
|
||||
|
||||
# Use extensions for better markdown support
|
||||
html = markdown.markdown(body, extensions=['extra', 'codehilite', 'toc'])
|
||||
return html
|
||||
@@ -262,6 +280,77 @@ class Note(db.Model):
|
||||
"""Check if this note has any active share links"""
|
||||
return any(s.is_valid() for s in self.shares)
|
||||
|
||||
|
||||
@property
|
||||
def is_image(self):
|
||||
"""Check if this is an image note"""
|
||||
return self.file_type == 'image'
|
||||
|
||||
@property
|
||||
def is_markdown_file(self):
|
||||
"""Check if this is a markdown file note"""
|
||||
return self.file_type == 'markdown'
|
||||
|
||||
@property
|
||||
def is_pdf(self):
|
||||
"""Check if this is a PDF note"""
|
||||
return self.file_type == 'document' and self.original_filename and self.original_filename.lower().endswith('.pdf')
|
||||
|
||||
@property
|
||||
def file_url(self):
|
||||
"""Get the URL to access the uploaded file"""
|
||||
if self.file_path:
|
||||
return f'/uploads/notes/{self.file_path}'
|
||||
return None
|
||||
|
||||
@property
|
||||
def thumbnail_url(self):
|
||||
"""Get thumbnail URL for image notes"""
|
||||
if self.is_image and self.file_path:
|
||||
# Could implement thumbnail generation later
|
||||
return self.file_url
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def allowed_file(filename):
|
||||
"""Check if file extension is allowed"""
|
||||
ALLOWED_EXTENSIONS = {
|
||||
'markdown': {'md', 'markdown', 'mdown', 'mkd'},
|
||||
'image': {'png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'},
|
||||
'text': {'txt'},
|
||||
'document': {'pdf', 'doc', 'docx'}
|
||||
}
|
||||
|
||||
if '.' not in filename:
|
||||
return False
|
||||
|
||||
ext = filename.rsplit('.', 1)[1].lower()
|
||||
|
||||
# Check all allowed extensions
|
||||
for file_type, extensions in ALLOWED_EXTENSIONS.items():
|
||||
if ext in extensions:
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_file_type_from_filename(filename):
|
||||
"""Determine file type from extension"""
|
||||
if '.' not in filename:
|
||||
return 'unknown'
|
||||
|
||||
ext = filename.rsplit('.', 1)[1].lower()
|
||||
|
||||
if ext in {'md', 'markdown', 'mdown', 'mkd'}:
|
||||
return 'markdown'
|
||||
elif ext in {'png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'}:
|
||||
return 'image'
|
||||
elif ext == 'txt':
|
||||
return 'text'
|
||||
elif ext in {'pdf', 'doc', 'docx'}:
|
||||
return 'document'
|
||||
else:
|
||||
return 'other'
|
||||
|
||||
|
||||
class NoteLink(db.Model):
|
||||
"""Links between notes for creating relationships"""
|
||||
|
||||
92
models/note_attachment.py
Normal file
92
models/note_attachment.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Note attachment model for storing uploaded files associated with notes.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from . import db
|
||||
|
||||
|
||||
class NoteAttachment(db.Model):
|
||||
"""Model for files attached to notes (images, documents, etc.)"""
|
||||
__tablename__ = 'note_attachment'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
note_id = db.Column(db.Integer, db.ForeignKey('note.id'), nullable=False)
|
||||
|
||||
# File information
|
||||
filename = db.Column(db.String(255), nullable=False) # Stored filename
|
||||
original_filename = db.Column(db.String(255), nullable=False) # Original upload name
|
||||
file_path = db.Column(db.String(500), nullable=False) # Relative path from uploads dir
|
||||
file_size = db.Column(db.Integer) # Size in bytes
|
||||
mime_type = db.Column(db.String(100))
|
||||
|
||||
# File type for easier filtering
|
||||
file_type = db.Column(db.String(20)) # 'image', 'document', 'archive', etc.
|
||||
|
||||
# Metadata
|
||||
uploaded_at = db.Column(db.DateTime, default=datetime.now)
|
||||
uploaded_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
|
||||
# For images: dimensions
|
||||
image_width = db.Column(db.Integer)
|
||||
image_height = db.Column(db.Integer)
|
||||
|
||||
# Soft delete
|
||||
is_deleted = db.Column(db.Boolean, default=False)
|
||||
deleted_at = db.Column(db.DateTime)
|
||||
|
||||
# Relationships
|
||||
note = db.relationship('Note', backref='attachments')
|
||||
uploaded_by = db.relationship('User', backref='uploaded_attachments')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<NoteAttachment {self.original_filename} for Note {self.note_id}>'
|
||||
|
||||
@property
|
||||
def is_image(self):
|
||||
"""Check if attachment is an image"""
|
||||
return self.file_type == 'image'
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""Get the URL to access this attachment"""
|
||||
return f'/uploads/notes/{self.file_path}'
|
||||
|
||||
def get_file_extension(self):
|
||||
"""Get file extension"""
|
||||
return self.original_filename.rsplit('.', 1)[1].lower() if '.' in self.original_filename else ''
|
||||
|
||||
@staticmethod
|
||||
def allowed_file(filename, file_type='any'):
|
||||
"""Check if file extension is allowed"""
|
||||
ALLOWED_EXTENSIONS = {
|
||||
'image': {'png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'},
|
||||
'document': {'pdf', 'doc', 'docx', 'txt', 'md', 'csv', 'xls', 'xlsx'},
|
||||
'archive': {'zip', 'tar', 'gz', '7z'},
|
||||
'any': {'png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'pdf', 'doc',
|
||||
'docx', 'txt', 'md', 'csv', 'xls', 'xlsx', 'zip', 'tar', 'gz', '7z'}
|
||||
}
|
||||
|
||||
if '.' not in filename:
|
||||
return False
|
||||
|
||||
ext = filename.rsplit('.', 1)[1].lower()
|
||||
allowed = ALLOWED_EXTENSIONS.get(file_type, ALLOWED_EXTENSIONS['any'])
|
||||
return ext in allowed
|
||||
|
||||
@staticmethod
|
||||
def get_file_type(filename):
|
||||
"""Determine file type from extension"""
|
||||
if '.' not in filename:
|
||||
return 'unknown'
|
||||
|
||||
ext = filename.rsplit('.', 1)[1].lower()
|
||||
|
||||
if ext in {'png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'}:
|
||||
return 'image'
|
||||
elif ext in {'pdf', 'doc', 'docx', 'txt', 'md', 'csv', 'xls', 'xlsx'}:
|
||||
return 'document'
|
||||
elif ext in {'zip', 'tar', 'gz', '7z'}:
|
||||
return 'archive'
|
||||
else:
|
||||
return 'other'
|
||||
@@ -183,6 +183,9 @@ class UserPreferences(db.Model):
|
||||
time_format = db.Column(db.String(10), default='24h')
|
||||
time_format_24h = db.Column(db.Boolean, default=True) # True for 24h, False for 12h
|
||||
|
||||
# Note preview preferences
|
||||
note_preview_font = db.Column(db.String(50), default='system') # system, serif, sans-serif, monospace, etc.
|
||||
|
||||
# Time tracking preferences
|
||||
time_rounding_minutes = db.Column(db.Integer, default=0) # 0, 5, 10, 15, 30, 60
|
||||
round_to_nearest = db.Column(db.Boolean, default=False) # False=round down, True=round to nearest
|
||||
|
||||
Reference in New Issue
Block a user