Add backlink
This commit is contained in:
@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -17,9 +18,10 @@ import (
|
||||
|
||||
// Indexer maintient un index en memoire des tags associes aux fichiers Markdown.
|
||||
type Indexer struct {
|
||||
mu sync.RWMutex
|
||||
tags map[string][]string
|
||||
docs map[string]*Document
|
||||
mu sync.RWMutex
|
||||
tags map[string][]string
|
||||
docs map[string]*Document
|
||||
backlinks map[string][]string // note path -> list of notes that reference it
|
||||
}
|
||||
|
||||
// Document représente une note indexée pour la recherche.
|
||||
@ -51,8 +53,9 @@ type SearchResult struct {
|
||||
// New cree une nouvelle instance d Indexer.
|
||||
func New() *Indexer {
|
||||
return &Indexer{
|
||||
tags: make(map[string][]string),
|
||||
docs: make(map[string]*Document),
|
||||
tags: make(map[string][]string),
|
||||
docs: make(map[string]*Document),
|
||||
backlinks: make(map[string][]string),
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,9 +115,31 @@ func (i *Indexer) Load(root string) error {
|
||||
indexed[tag] = list
|
||||
}
|
||||
|
||||
// Build backlinks index
|
||||
backlinksMap := make(map[string][]string)
|
||||
for sourcePath, doc := range documents {
|
||||
links := extractInternalLinks(doc.Body)
|
||||
for _, targetPath := range links {
|
||||
// Add sourcePath to the backlinks of targetPath
|
||||
if _, ok := backlinksMap[targetPath]; !ok {
|
||||
backlinksMap[targetPath] = make([]string, 0)
|
||||
}
|
||||
// Avoid duplicates
|
||||
if !containsString(backlinksMap[targetPath], sourcePath) {
|
||||
backlinksMap[targetPath] = append(backlinksMap[targetPath], sourcePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort backlinks for consistency
|
||||
for _, links := range backlinksMap {
|
||||
sort.Strings(links)
|
||||
}
|
||||
|
||||
i.mu.Lock()
|
||||
i.tags = indexed
|
||||
i.docs = documents
|
||||
i.backlinks = backlinksMap
|
||||
i.mu.Unlock()
|
||||
|
||||
return nil
|
||||
@ -668,3 +693,56 @@ func (i *Indexer) GetAllTagsWithCount() []TagCount {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetBacklinks retourne la liste des notes qui référencent la note spécifiée
|
||||
func (i *Indexer) GetBacklinks(path string) []string {
|
||||
i.mu.RLock()
|
||||
defer i.mu.RUnlock()
|
||||
|
||||
links, ok := i.backlinks[path]
|
||||
if !ok || len(links) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retourner une copie pour éviter les modifications externes
|
||||
result := make([]string, len(links))
|
||||
copy(result, links)
|
||||
return result
|
||||
}
|
||||
|
||||
// extractInternalLinks extrait tous les liens internes d'un texte Markdown/HTML
|
||||
// Format: <a ... hx-get="/api/notes/path/to/note.md" ...>
|
||||
func extractInternalLinks(body string) []string {
|
||||
// Pattern pour capturer le chemin dans hx-get="/api/notes/..."
|
||||
// On cherche: hx-get="/api/notes/ suivi de n'importe quoi jusqu'au prochain guillemet
|
||||
pattern := `hx-get="/api/notes/([^"]+)"`
|
||||
|
||||
// Compiler la regex
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Trouver tous les matches
|
||||
matches := re.FindAllStringSubmatch(body, -1)
|
||||
if len(matches) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Extraire les chemins (groupe de capture 1)
|
||||
links := make([]string, 0, len(matches))
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
for _, match := range matches {
|
||||
if len(match) > 1 {
|
||||
path := match[1]
|
||||
// Éviter les doublons
|
||||
if _, ok := seen[path]; !ok {
|
||||
seen[path] = struct{}{}
|
||||
links = append(links, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return links
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user