""" Wiki-style link processing for Notes feature. Supports [[note-title]] for links and ![[note-title]] for embedding. """ import re from flask import g, url_for from models import Note, NoteVisibility from sqlalchemy import or_, and_ def process_wiki_links(content, current_note_id=None): """ Process wiki-style links in content. - [[note-title]] becomes a link to the note - ![[note-title]] embeds the note content Args: content: Markdown content with potential wiki links current_note_id: ID of the current note (to avoid circular embeds) Returns: Processed content with wiki links replaced """ # Process embeds first (![[...]]) to avoid link processing inside embedded content content = process_wiki_embeds(content, current_note_id) # Then process regular links ([[...]]) content = process_wiki_regular_links(content) return content def process_wiki_regular_links(content): """Convert [[note-title]] to HTML links""" def replace_link(match): link_text = match.group(1).strip() # Try to find the note by title or slug note = find_note_by_reference(link_text) if note: # Create a link to the note url = url_for('notes.view_note', slug=note.slug) return f'{link_text}' else: # Note not found - create a broken link indicator return f'{link_text}' # Pattern to match [[text]] but not ![[text]] pattern = r'(?Circular reference detected: {embed_ref}' # Check if user can view the note if not note.can_user_view(g.user): return f'
' # Add to embedded set embedded_notes.add(note.id) # Get the note content without frontmatter from frontmatter_utils import parse_frontmatter _, body = parse_frontmatter(note.content) # Process any wiki links in the embedded content (but not embeds to avoid deep recursion) embedded_content = process_wiki_regular_links(body) # Render the embedded content import markdown html_content = markdown.markdown(embedded_content, extensions=['extra', 'codehilite', 'toc']) # Create an embedded note block embed_html = f''' ''' return embed_html else: # Note not found return f'' # Pattern to match ![[text]] pattern = r'\!\[\[([^\]]+)\]\]' return re.sub(pattern, replace_embed, content) def find_note_by_reference(reference): """ Find a note by title or slug reference. First tries exact slug match, then exact title match, then case-insensitive title match. Args: reference: The note reference (title or slug) Returns: Note object or None """ if not g.user or not g.user.company_id: return None # Build base query for notes the user can see base_query = Note.query.filter( Note.company_id == g.user.company_id, Note.is_archived == False ).filter( or_( # Private notes created by user and_(Note.visibility == NoteVisibility.PRIVATE, Note.created_by_id == g.user.id), # Team notes from user's team and_(Note.visibility == NoteVisibility.TEAM, Note.created_by.has(team_id=g.user.team_id)), # Company notes Note.visibility == NoteVisibility.COMPANY ) ) # Try exact slug match first note = base_query.filter_by(slug=reference).first() if note: return note # Try exact title match note = base_query.filter_by(title=reference).first() if note: return note # Try case-insensitive title match note = base_query.filter(Note.title.ilike(reference)).first() if note: return note # Try slug-ified version of the reference import re slugified = re.sub(r'[^\w\s-]', '', reference.lower()) slugified = re.sub(r'[-\s]+', '-', slugified).strip('-') if slugified != reference: note = base_query.filter_by(slug=slugified).first() if note: return note return None