graph rendering bugfix

This commit is contained in:
Gamosoft 2026-03-24 13:23:47 +01:00
parent b0ce8ebde1
commit bade08b0b8
2 changed files with 104 additions and 50 deletions

View File

@ -1333,6 +1333,12 @@ async def get_graph():
# Match links that don't start with http://, https://, mailto:, #, etc. # Match links that don't start with http://, https://, mailto:, #, etc.
markdown_links = re.findall(r'\[([^\]]+)\]\((?!https?://|mailto:|#|data:)([^\)]+)\)', content) markdown_links = re.findall(r'\[([^\]]+)\]\((?!https?://|mailto:|#|data:)([^\)]+)\)', content)
# Get source note's folder for resolving relative links
# Use forward slashes consistently (note_paths uses forward slashes)
source_folder = str(Path(note['path']).parent).replace('\\', '/')
if source_folder == '.':
source_folder = ''
# Process wikilinks # Process wikilinks
for target in wikilinks: for target in wikilinks:
target = target.strip() target = target.strip()
@ -1341,20 +1347,34 @@ async def get_graph():
# Try to match target to an existing note # Try to match target to an existing note
target_path = None target_path = None
# 1. Exact path match # 1. Try resolving relative to source note's folder first
if target in note_paths: if source_folder and '/' not in target:
target_path = target if target.endswith('.md') else target + '.md' relative_path = f"{source_folder}/{target}"
# 2. Path with .md extension relative_path_lower = relative_path.lower()
elif target + '.md' in note_paths:
target_path = target + '.md' if relative_path in note_paths:
# 3. Case-insensitive path match (e.g., [[Folder/Note]] -> folder/note.md) target_path = relative_path if relative_path.endswith('.md') else relative_path + '.md'
elif target_lower in note_paths_lower: elif relative_path + '.md' in note_paths:
target_path = note_paths_lower[target_lower] target_path = relative_path + '.md'
elif target_lower + '.md' in note_paths_lower: elif relative_path_lower in note_paths_lower:
target_path = note_paths_lower[target_lower + '.md'] target_path = note_paths_lower[relative_path_lower]
# 4. Just note name (case-insensitive) elif relative_path_lower + '.md' in note_paths_lower:
elif target_lower in note_names: target_path = note_paths_lower[relative_path_lower + '.md']
target_path = note_names[target_lower]
# 2. Exact path match (absolute or already has folder)
if not target_path:
if target in note_paths:
target_path = target if target.endswith('.md') else target + '.md'
elif target + '.md' in note_paths:
target_path = target + '.md'
# 3. Case-insensitive path match (e.g., [[Folder/Note]] -> folder/note.md)
elif target_lower in note_paths_lower:
target_path = note_paths_lower[target_lower]
elif target_lower + '.md' in note_paths_lower:
target_path = note_paths_lower[target_lower + '.md']
# 4. Just note name (case-insensitive) - global match
elif target_lower in note_names:
target_path = note_names[target_lower]
if target_path and target_path != note['path']: if target_path and target_path != note['path']:
edges.append({ edges.append({
@ -1381,34 +1401,42 @@ async def get_graph():
# Add .md extension if not present and doesn't have other extension # Add .md extension if not present and doesn't have other extension
link_path_with_md = link_path if link_path.endswith('.md') else link_path + '.md' link_path_with_md = link_path if link_path.endswith('.md') else link_path + '.md'
link_path_lower = link_path.lower()
link_path_with_md_lower = link_path_with_md.lower()
# Try to match target to an existing note # Try to match target to an existing note
target_path = None target_path = None
# 1. Exact path match (with or without .md) # 1. First, try resolving relative to source note's folder
if link_path in note_paths: if source_folder and not link_path.startswith('/'):
target_path = link_path if link_path.endswith('.md') else link_path + '.md' relative_path = f"{source_folder}/{link_path}"
elif link_path_with_md in note_paths: relative_path_with_md = f"{source_folder}/{link_path_with_md}"
target_path = link_path_with_md relative_path_lower = relative_path.lower()
# 2. Case-insensitive path match relative_path_with_md_lower = relative_path_with_md.lower()
elif link_path_lower in note_paths_lower:
target_path = note_paths_lower[link_path_lower]
elif link_path_with_md_lower in note_paths_lower:
target_path = note_paths_lower[link_path_with_md_lower]
# 3. Try matching by filename only (for relative links)
else:
# Extract just the filename
filename = link_path.split('/')[-1]
filename_lower = filename.lower()
filename_with_md = filename if filename.endswith('.md') else filename + '.md'
filename_with_md_lower = filename_with_md.lower()
if filename_lower in note_names: if relative_path in note_paths:
target_path = note_names[filename_lower] target_path = relative_path if relative_path.endswith('.md') else relative_path + '.md'
elif filename_with_md_lower in note_names: elif relative_path_with_md in note_paths:
target_path = note_names[filename_with_md_lower] target_path = relative_path_with_md
elif relative_path_lower in note_paths_lower:
target_path = note_paths_lower[relative_path_lower]
elif relative_path_with_md_lower in note_paths_lower:
target_path = note_paths_lower[relative_path_with_md_lower]
# 2. Try exact path match from root (for absolute paths or notes at root)
if not target_path:
link_path_lower = link_path.lower()
link_path_with_md_lower = link_path_with_md.lower()
if link_path in note_paths:
target_path = link_path if link_path.endswith('.md') else link_path + '.md'
elif link_path_with_md in note_paths:
target_path = link_path_with_md
# Case-insensitive path match
elif link_path_lower in note_paths_lower:
target_path = note_paths_lower[link_path_lower]
elif link_path_with_md_lower in note_paths_lower:
target_path = note_paths_lower[link_path_with_md_lower]
# No global filename fallback for markdown links - they must resolve as paths
if target_path and target_path != note['path']: if target_path and target_path != note['path']:
edges.append({ edges.append({

View File

@ -989,11 +989,11 @@ def apply_template_placeholders(content: str, note_path: str) -> str:
def get_backlinks(notes_dir: str, target_note_path: str) -> List[Dict]: def get_backlinks(notes_dir: str, target_note_path: str) -> List[Dict]:
""" """
Find all notes that link TO the specified note (reverse links / backlinks). Find all notes that link TO the specified note (reverse links / backlinks).
Args: Args:
notes_dir: Base directory containing notes notes_dir: Base directory containing notes
target_note_path: Path of the note to find backlinks for target_note_path: Path of the note to find backlinks for
Returns: Returns:
List of backlink objects with path, context, and line_number List of backlink objects with path, context, and line_number
""" """
@ -1002,13 +1002,15 @@ def get_backlinks(notes_dir: str, target_note_path: str) -> List[Dict]:
# Normalize target path for matching # Normalize target path for matching
target_path = target_note_path target_path = target_note_path
target_path_lower = target_path.lower()
target_path_no_ext = target_path.replace('.md', '') target_path_no_ext = target_path.replace('.md', '')
target_path_no_ext_lower = target_path_no_ext.lower()
target_name = Path(target_path).stem.lower() target_name = Path(target_path).stem.lower()
# Build set of ways to reference the target note # For wikilinks: global name matching (find note anywhere by name)
target_refs = { wikilink_refs = {
target_path.lower(), target_path_lower,
target_path_no_ext.lower(), target_path_no_ext_lower,
target_name, target_name,
} }
@ -1022,6 +1024,11 @@ def get_backlinks(notes_dir: str, target_note_path: str) -> List[Dict]:
if source_path == target_path: if source_path == target_path:
continue continue
# Get source folder for resolving relative markdown links
source_folder = str(Path(source_path).parent).replace('\\', '/')
if source_folder == '.':
source_folder = ''
# Read note content # Read note content
full_path = Path(notes_dir) / source_path full_path = Path(notes_dir) / source_path
try: try:
@ -1035,14 +1042,14 @@ def get_backlinks(notes_dir: str, target_note_path: str) -> List[Dict]:
for line_num, line in enumerate(lines, 1): for line_num, line in enumerate(lines, 1):
# Find wikilinks: [[target]] or [[target|display]] # Find wikilinks: [[target]] or [[target|display]]
# Wikilinks use GLOBAL matching (find note anywhere by name)
wikilink_matches = re.finditer(r'\[\[([^\]|]+)(?:\|[^\]]+)?\]\]', line) wikilink_matches = re.finditer(r'\[\[([^\]|]+)(?:\|[^\]]+)?\]\]', line)
for match in wikilink_matches: for match in wikilink_matches:
link_target = match.group(1).strip().lower() link_target = match.group(1).strip().lower()
link_target_no_ext = link_target.replace('.md', '') link_target_no_ext = link_target.replace('.md', '')
# Check if this link points to our target # Check if this wikilink points to our target (global match)
if link_target in target_refs or link_target_no_ext in target_refs: if link_target in wikilink_refs or link_target_no_ext in wikilink_refs:
# Extract context around the match
start = max(0, match.start() - 30) start = max(0, match.start() - 30)
end = min(len(line), match.end() + 30) end = min(len(line), match.end() + 30)
context = line[start:end] context = line[start:end]
@ -1058,6 +1065,7 @@ def get_backlinks(notes_dir: str, target_note_path: str) -> List[Dict]:
}) })
# Find markdown links: [text](path) # Find markdown links: [text](path)
# Markdown links must RESOLVE as paths (relative to source or absolute)
markdown_matches = re.finditer(r'\[([^\]]+)\]\((?!https?://|mailto:|#|data:)([^\)]+)\)', line) markdown_matches = re.finditer(r'\[([^\]]+)\]\((?!https?://|mailto:|#|data:)([^\)]+)\)', line)
for match in markdown_matches: for match in markdown_matches:
link_path = match.group(2).split('#')[0] # Remove anchor link_path = match.group(2).split('#')[0] # Remove anchor
@ -1068,11 +1076,29 @@ def get_backlinks(notes_dir: str, target_note_path: str) -> List[Dict]:
if link_path.startswith('./'): if link_path.startswith('./'):
link_path = link_path[2:] link_path = link_path[2:]
link_path_lower = link_path.lower() # Add .md if not present
link_path_no_ext = link_path_lower.replace('.md', '') link_path_with_md = link_path if link_path.endswith('.md') else link_path + '.md'
# Check if this link points to our target # Resolve the link path to get the actual target
if link_path_lower in target_refs or link_path_no_ext in target_refs: resolved_path = None
# 1. Try resolving relative to source folder
if source_folder and not link_path.startswith('/'):
relative_path = f"{source_folder}/{link_path_with_md}"
if relative_path.lower() == target_path_lower:
resolved_path = target_path
elif f"{source_folder}/{link_path}".lower() == target_path_no_ext_lower:
resolved_path = target_path
# 2. Try as absolute path from root
if not resolved_path:
if link_path_with_md.lower() == target_path_lower:
resolved_path = target_path
elif link_path.lower() == target_path_no_ext_lower:
resolved_path = target_path
# Only add if the resolved path matches the target
if resolved_path:
start = max(0, match.start() - 30) start = max(0, match.start() - 30)
end = min(len(line), match.end() + 30) end = min(len(line), match.end() + 30)
context = line[start:end] context = line[start:end]