links to internal notes with sections

This commit is contained in:
Gamosoft 2025-12-16 17:09:36 +01:00
parent 53e9eff2c2
commit a9bb6d32fb
1 changed files with 59 additions and 22 deletions

View File

@ -2064,42 +2064,79 @@ function noteApp() {
// Prevent default navigation for internal links // Prevent default navigation for internal links
event.preventDefault(); event.preventDefault();
// Remove any anchor from the href (e.g., "note.md#section" -> "note.md") // Parse href into note path and anchor (e.g., "note.md#section" -> notePath="note.md", anchor="section")
// Also decode URL encoding (e.g., "note%203.md" -> "note 3.md") const decodedHref = decodeURIComponent(href);
const notePath = decodeURIComponent(href.split('#')[0]); const hashIndex = decodedHref.indexOf('#');
const notePath = hashIndex !== -1 ? decodedHref.substring(0, hashIndex) : decodedHref;
const anchor = hashIndex !== -1 ? decodedHref.substring(hashIndex + 1) : null;
// Skip if it's just an anchor link // If it's just an anchor link (#heading), scroll within current note
if (!notePath && anchor) {
this.scrollToAnchor(anchor);
return;
}
// Skip if no path
if (!notePath) return; if (!notePath) return;
// Find the note by path (try exact match first, then with .md extension) // Find the note by path (try exact match first, then with .md extension)
const note = this.notes.find(n => let targetNote = this.notes.find(n =>
n.path === notePath || n.path === notePath ||
n.path === notePath + '.md' n.path === notePath + '.md'
); );
if (note) {
this.loadNote(note.path); if (!targetNote) {
} else {
// Try to find by name (in case link uses just the note name without path) // Try to find by name (in case link uses just the note name without path)
const noteByName = this.notes.find(n => targetNote = this.notes.find(n =>
n.name === notePath || n.name === notePath ||
n.name === notePath + '.md' || n.name === notePath + '.md' ||
// Also match by filename at end of path (case-insensitive)
n.name.toLowerCase() === notePath.toLowerCase() || n.name.toLowerCase() === notePath.toLowerCase() ||
n.name.toLowerCase() === (notePath + '.md').toLowerCase() n.name.toLowerCase() === (notePath + '.md').toLowerCase()
); );
if (noteByName) { }
this.loadNote(noteByName.path);
} else { if (!targetNote) {
// Last resort: case-insensitive path matching // Last resort: case-insensitive path matching
const noteByPathCI = this.notes.find(n => targetNote = this.notes.find(n =>
n.path.toLowerCase() === notePath.toLowerCase() || n.path.toLowerCase() === notePath.toLowerCase() ||
n.path.toLowerCase() === (notePath + '.md').toLowerCase() n.path.toLowerCase() === (notePath + '.md').toLowerCase()
); );
if (noteByPathCI) { }
this.loadNote(noteByPathCI.path);
if (targetNote) {
// Load the note, then scroll to anchor if present
this.loadNote(targetNote.path).then(() => {
if (anchor) {
// Small delay to ensure content is rendered
setTimeout(() => this.scrollToAnchor(anchor), 100);
}
});
} else { } else {
alert(this.t('notes.not_found', { path: notePath })); alert(this.t('notes.not_found', { path: notePath }));
} }
},
// Scroll to an anchor (heading) by slug - reuses outline data
scrollToAnchor(anchor) {
// Normalize the anchor (GitHub-style slug)
const targetSlug = anchor
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-');
// Find matching heading in outline
const heading = this.outline.find(h => h.slug === targetSlug);
if (heading) {
this.scrollToHeading(heading);
} else {
// Fallback: try to find heading by exact text match
const headingByText = this.outline.find(h =>
h.text.toLowerCase().replace(/\s+/g, '-') === anchor.toLowerCase()
);
if (headingByText) {
this.scrollToHeading(headingByText);
} }
} }
}, },