backlink retrieval
This commit is contained in:
parent
c466f0b16f
commit
57032c6c28
|
|
@ -43,6 +43,7 @@ from .utils import (
|
|||
get_template_content,
|
||||
apply_template_placeholders,
|
||||
paginate,
|
||||
get_backlinks,
|
||||
)
|
||||
from .plugins import PluginManager
|
||||
from .themes import get_available_themes, get_theme_css
|
||||
|
|
@ -1076,8 +1077,8 @@ async def list_notes(
|
|||
|
||||
|
||||
@api_router.get("/notes/{note_path:path}", tags=["Notes"])
|
||||
async def get_note(note_path: str):
|
||||
"""Get a specific note's content"""
|
||||
async def get_note(note_path: str, include_backlinks: bool = True):
|
||||
"""Get a specific note's content with optional backlinks"""
|
||||
try:
|
||||
content = get_note_content(config['storage']['notes_dir'], note_path)
|
||||
if content is None:
|
||||
|
|
@ -1088,11 +1089,16 @@ async def get_note(note_path: str):
|
|||
if transformed_content is not None:
|
||||
content = transformed_content
|
||||
|
||||
return {
|
||||
response = {
|
||||
"path": note_path,
|
||||
"content": content,
|
||||
"metadata": create_note_metadata(config['storage']['notes_dir'], note_path)
|
||||
}
|
||||
|
||||
if include_backlinks:
|
||||
response["backlinks"] = get_backlinks(config['storage']['notes_dir'], note_path)
|
||||
|
||||
return response
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
|
|
|
|||
114
backend/utils.py
114
backend/utils.py
|
|
@ -984,3 +984,117 @@ def apply_template_placeholders(content: str, note_path: str) -> str:
|
|||
|
||||
return result
|
||||
|
||||
|
||||
def get_backlinks(notes_dir: str, target_note_path: str) -> List[Dict]:
|
||||
"""
|
||||
Find all notes that link TO the specified note (reverse links / backlinks).
|
||||
|
||||
Args:
|
||||
notes_dir: Base directory containing notes
|
||||
target_note_path: Path of the note to find backlinks for
|
||||
|
||||
Returns:
|
||||
List of backlink objects with path, context, and line_number
|
||||
"""
|
||||
import urllib.parse
|
||||
|
||||
backlinks = []
|
||||
notes, _folders = scan_notes_fast_walk(notes_dir, include_media=False)
|
||||
|
||||
# Normalize target path for matching
|
||||
target_path = target_note_path
|
||||
target_path_no_ext = target_path.replace('.md', '')
|
||||
target_name = Path(target_path).stem.lower()
|
||||
|
||||
# Build set of ways to reference the target note
|
||||
target_refs = {
|
||||
target_path.lower(),
|
||||
target_path_no_ext.lower(),
|
||||
target_name,
|
||||
}
|
||||
|
||||
for note in notes:
|
||||
if note.get('type') != 'note':
|
||||
continue
|
||||
|
||||
source_path = note['path']
|
||||
|
||||
# Skip self-references
|
||||
if source_path == target_path:
|
||||
continue
|
||||
|
||||
# Read note content
|
||||
full_path = Path(notes_dir) / source_path
|
||||
try:
|
||||
with open(full_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
lines = content.split('\n')
|
||||
found_links = []
|
||||
|
||||
for line_num, line in enumerate(lines, 1):
|
||||
# Find wikilinks: [[target]] or [[target|display]]
|
||||
wikilink_matches = re.finditer(r'\[\[([^\]|]+)(?:\|[^\]]+)?\]\]', line)
|
||||
for match in wikilink_matches:
|
||||
link_target = match.group(1).strip().lower()
|
||||
link_target_no_ext = link_target.replace('.md', '')
|
||||
|
||||
# Check if this link points to our target
|
||||
if link_target in target_refs or link_target_no_ext in target_refs:
|
||||
# Extract context around the match
|
||||
start = max(0, match.start() - 30)
|
||||
end = min(len(line), match.end() + 30)
|
||||
context = line[start:end]
|
||||
if start > 0:
|
||||
context = '...' + context
|
||||
if end < len(line):
|
||||
context = context + '...'
|
||||
|
||||
found_links.append({
|
||||
"line_number": line_num,
|
||||
"context": context,
|
||||
"type": "wikilink"
|
||||
})
|
||||
|
||||
# Find markdown links: [text](path)
|
||||
markdown_matches = re.finditer(r'\[([^\]]+)\]\((?!https?://|mailto:|#|data:)([^\)]+)\)', line)
|
||||
for match in markdown_matches:
|
||||
link_path = match.group(2).split('#')[0] # Remove anchor
|
||||
if not link_path:
|
||||
continue
|
||||
|
||||
link_path = urllib.parse.unquote(link_path)
|
||||
if link_path.startswith('./'):
|
||||
link_path = link_path[2:]
|
||||
|
||||
link_path_lower = link_path.lower()
|
||||
link_path_no_ext = link_path_lower.replace('.md', '')
|
||||
|
||||
# Check if this link points to our target
|
||||
if link_path_lower in target_refs or link_path_no_ext in target_refs:
|
||||
start = max(0, match.start() - 30)
|
||||
end = min(len(line), match.end() + 30)
|
||||
context = line[start:end]
|
||||
if start > 0:
|
||||
context = '...' + context
|
||||
if end < len(line):
|
||||
context = context + '...'
|
||||
|
||||
found_links.append({
|
||||
"line_number": line_num,
|
||||
"context": context,
|
||||
"type": "markdown"
|
||||
})
|
||||
|
||||
# If we found links in this note, add it to backlinks
|
||||
if found_links:
|
||||
backlinks.append({
|
||||
"path": source_path,
|
||||
"name": note['name'].replace('.md', ''),
|
||||
"references": found_links[:3] # Limit to 3 references per note
|
||||
})
|
||||
|
||||
return backlinks
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue