graph rendering bugfix
This commit is contained in:
parent
b0ce8ebde1
commit
bade08b0b8
102
backend/main.py
102
backend/main.py
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue