diff --git a/http_readers.go b/http_readers.go
index 0146c52..3153f4d 100644
--- a/http_readers.go
+++ b/http_readers.go
@@ -110,6 +110,7 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) {
naviTitle(hyphaName),
contents,
treeHTML,
+ h.BackLinkEntriesHTML(),
prevHypha, nextHypha,
hasAmnt),
u,
diff --git a/hyphae/backlink.go b/hyphae/backlink.go
new file mode 100644
index 0000000..21c79ad
--- /dev/null
+++ b/hyphae/backlink.go
@@ -0,0 +1,72 @@
+package hyphae
+
+import (
+ "fmt"
+ "io/ioutil"
+
+ "github.com/bouncepaw/mycorrhiza/link"
+ "github.com/bouncepaw/mycorrhiza/markup"
+ "github.com/bouncepaw/mycorrhiza/util"
+)
+
+func (h *Hypha) BackLinkEntriesHTML() (html string) {
+ for _, backlinkHypha := range h.BackLinks {
+ _ = link.Link{}
+ html += fmt.Sprintf(`
+ %s`, backlinkHypha.Name, util.BeautifulName(backlinkHypha.Name))
+ }
+ return
+}
+
+func (h *Hypha) outlinksThis(oh *Hypha) bool {
+ for _, outlink := range h.OutLinks {
+ if outlink == oh {
+ return true
+ }
+ }
+ return false
+}
+
+func (h *Hypha) backlinkedBy(oh *Hypha) bool {
+ for _, backlink := range h.BackLinks {
+ if backlink == oh {
+ return true
+ }
+ }
+ return false
+}
+
+// FindAllBacklinks iterates over all hyphae that have text parts, sets their outlinks and then sets backlinks.
+func FindAllBacklinks() {
+ for h := range FilterTextHyphae(YieldExistingHyphae()) {
+ findBacklinkWorker(h)
+ }
+}
+
+func findBacklinkWorker(h *Hypha) {
+ h.Lock()
+ defer h.Unlock()
+
+ textContents, err := ioutil.ReadFile(h.TextPath)
+ if err == nil {
+ for outlink := range markup.Doc(h.Name, string(textContents)).OutLinks() {
+ outlink := outlink
+ outlinkHypha := ByName(outlink)
+ if outlinkHypha == h {
+ continue
+ }
+
+ outlinkHypha.Lock()
+ if !outlinkHypha.backlinkedBy(h) {
+ outlinkHypha.BackLinks = append(outlinkHypha.BackLinks, h)
+ outlinkHypha.InsertIfNewKeepExistence()
+ }
+ outlinkHypha.Unlock()
+
+ // Insert outlinkHypha if unique
+ if !h.outlinksThis(outlinkHypha) {
+ h.OutLinks = append(h.OutLinks, outlinkHypha)
+ }
+ }
+ }
+}
diff --git a/hyphae/hyphae.go b/hyphae/hyphae.go
index 8db893d..1c4496c 100644
--- a/hyphae/hyphae.go
+++ b/hyphae/hyphae.go
@@ -49,8 +49,8 @@ type Hypha struct {
Exists bool
TextPath string
BinaryPath string
- OutLinks []*Hypha // not used yet
- BackLinks []*Hypha // not used yet
+ OutLinks []*Hypha
+ BackLinks []*Hypha
}
var byNames = make(map[string]*Hypha)
@@ -59,17 +59,31 @@ var byNamesMutex = sync.Mutex{}
// YieldExistingHyphae iterates over all hyphae and yields all existing ones.
func YieldExistingHyphae() chan *Hypha {
ch := make(chan *Hypha)
- go func(ch chan *Hypha) {
+ go func() {
for _, h := range byNames {
if h.Exists {
ch <- h
}
}
close(ch)
- }(ch)
+ }()
return ch
}
+// FilterTextHyphae filters the source channel and yields only those hyphae than have text parts.
+func FilterTextHyphae(src chan *Hypha) chan *Hypha {
+ sink := make(chan *Hypha)
+ go func() {
+ for h := range src {
+ if h.TextPath != "" {
+ sink <- h
+ }
+ }
+ close(sink)
+ }()
+ return sink
+}
+
// Subhyphae returns slice of subhyphae.
func (h *Hypha) Subhyphae() []*Hypha {
hyphae := []*Hypha{}
@@ -138,6 +152,18 @@ func (h *Hypha) InsertIfNew() (justCreated bool) {
return false
}
+func (h *Hypha) InsertIfNewKeepExistence() {
+ hp := ByName(h.Name)
+
+ byNamesMutex.Lock()
+ defer byNamesMutex.Unlock()
+ if hp.Exists {
+ hp = h
+ } else {
+ byNames[h.Name] = h
+ }
+}
+
func (h *Hypha) delete() {
byNamesMutex.Lock()
h.Lock()
diff --git a/main.go b/main.go
index e667d8f..9e7ce22 100644
--- a/main.go
+++ b/main.go
@@ -175,6 +175,8 @@ func main() {
log.Println("Wiki storage directory is", WikiDir)
hyphae.Index(WikiDir)
log.Println("Indexed", hyphae.Count(), "hyphae")
+ hyphae.FindAllBacklinks()
+ log.Println("Found all backlinks")
history.Start(WikiDir)
hyphae.SetHeaderLinks()
diff --git a/markup/lexer.go b/markup/lexer.go
index 4516eb0..607393f 100644
--- a/markup/lexer.go
+++ b/markup/lexer.go
@@ -166,7 +166,7 @@ launchpadState:
switch {
case startsWith("=>"):
href, text, class := Rocketlink(line, state.name)
- state.buf += fmt.Sprintf(` %s`, class, href, text)
+ state.buf += fmt.Sprintf(` %s`, href, class, text)
case startsWith("```"):
state.where = "pre"
addLine(state.buf + "")
diff --git a/markup/outlink.go b/markup/outlink.go
new file mode 100644
index 0000000..0580371
--- /dev/null
+++ b/markup/outlink.go
@@ -0,0 +1,56 @@
+package markup
+
+import (
+ "regexp"
+ "strings"
+
+ "github.com/bouncepaw/mycorrhiza/link"
+)
+
+// OutLinks returns a channel of names of hyphae this mycodocument links.
+// Links include:
+// * Regular links
+// * Rocketlinks
+// * Transclusion
+// * Image galleries
+func (md *MycoDoc) OutLinks() chan string {
+ ch := make(chan string)
+ if !md.parsedAlready {
+ md.Lex(0)
+ }
+ go func() {
+ for _, line := range md.ast {
+ switch v := line.contents.(type) {
+ case string:
+ if strings.HasPrefix(v, "`)
+
+func extractLinks(html string, ch chan string) {
+ if results := reLinks.FindAllStringSubmatch(html, -1); results != nil {
+ for _, result := range results {
+ // result[0] is always present at this point and is not needed, because it is the whole matched substring (which we don't need)
+ ch <- result[1]
+ }
+ }
+}
+
+func extractImageLinks(img Img, ch chan string) {
+ for _, entry := range img.entries {
+ if entry.srclink.Kind == link.LinkLocalHypha {
+ ch <- entry.srclink.Address
+ }
+ }
+}
diff --git a/templates/common.qtpl b/templates/common.qtpl
index 1e02c6d..1109b93 100644
--- a/templates/common.qtpl
+++ b/templates/common.qtpl
@@ -58,7 +58,18 @@ var navEntries = []navEntry{
{% func relativeHyphae(relatives string) %}
{% endfunc %}
+
+{% func backlinks(backlinkEntries string) %}
+
+{% endfunc %}
diff --git a/templates/common.qtpl.go b/templates/common.qtpl.go
index 587d599..209e68e 100644
--- a/templates/common.qtpl.go
+++ b/templates/common.qtpl.go
@@ -217,7 +217,7 @@ func streamrelativeHyphae(qw422016 *qt422016.Writer, relatives string) {
//line templates/common.qtpl:59
qw422016.N().S(`