Backlinks to ‘{%s query %}’
+Hyphae which have a link to the selected hypha are listed below.
+-
+ {% for hyphaName := range generator(query) %}
+
- + {%s util.BeautifulName(hyphaName) %} + + {% endfor %} +
diff --git a/hyphae/backlinks.go b/hyphae/backlinks.go
new file mode 100644
index 0000000..961bea8
--- /dev/null
+++ b/hyphae/backlinks.go
@@ -0,0 +1,153 @@
+package hyphae
+
+import (
+ "os"
+ "sync"
+
+ "github.com/bouncepaw/mycorrhiza/util"
+
+ "github.com/bouncepaw/mycomarkup"
+ "github.com/bouncepaw/mycomarkup/blocks"
+ "github.com/bouncepaw/mycomarkup/links"
+ "github.com/bouncepaw/mycomarkup/mycocontext"
+)
+
+// Using set here seems like the most appropriate solution
+type linkSet map[string]struct{}
+
+var backlinkIndex = make(map[string]linkSet)
+var backlinkIndexMutex = sync.Mutex{}
+
+// IndexBacklinks traverses all text hyphae, extracts links from them and forms an initial index
+func IndexBacklinks() {
+ // It is safe to ignore the mutex, because there is only one worker.
+ src := FilterTextHyphae(YieldExistingHyphae())
+ for h := range src {
+ fileContentsT, errT := os.ReadFile(h.TextPath)
+ if errT == nil {
+ links := ExtractHyphaLinksFromContent(h.Name, string(fileContentsT))
+ for _, link := range links {
+ if _, exists := backlinkIndex[link]; !exists {
+ backlinkIndex[link] = make(linkSet)
+ }
+ backlinkIndex[link][h.Name] = struct{}{}
+ }
+ }
+ }
+}
+
+func YieldHyphaBacklinks(query string) <-chan string {
+ hyphaName := util.CanonicalName(query)
+ out := make(chan string)
+ sorted := PathographicSort(out)
+ go func() {
+ links := backlinkIndex[hyphaName]
+ for link := range links {
+ out <- link
+ }
+ close(out)
+ }()
+ return sorted
+}
+
+// YieldHyphaLinks extracts hypha links from a desired hypha and iterates over them
+func YieldHyphaLinks(query string) <-chan string {
+ // That is merely a debug function, but it could be useful.
+ // Should we extract them into link-specific subfile? -- chekoopa
+ hyphaName := util.CanonicalName(query)
+ out := make(chan string)
+ go func() {
+ links := ExtractHyphaLinks(hyphaName)
+ for _, link := range links {
+ out <- link
+ }
+ close(out)
+ }()
+ return out
+}
+
+// ExtractHyphaLinks extracts hypha links from a desired hypha
+func ExtractHyphaLinks(hyphaName string) []string {
+ var h = ByName(hyphaName)
+ if h.Exists {
+ fileContentsT, errT := os.ReadFile(h.TextPath)
+ if errT == nil {
+ return ExtractHyphaLinksFromContent(hyphaName, string(fileContentsT))
+ }
+ }
+ return make([]string, 0)
+}
+
+// ExtractHyphaLinksFromContent extracts hypha links from a provided text
+func ExtractHyphaLinksFromContent(hyphaName string, contents string) []string {
+ ctx, _ := mycocontext.ContextFromStringInput(hyphaName, contents)
+ linkVisitor, getLinks := LinkVisitor(ctx)
+ mycomarkup.BlockTree(ctx, linkVisitor)
+ foundLinks := getLinks()
+ var result []string
+ for _, link := range foundLinks {
+ if link.OfKind(links.LinkLocalHypha) {
+ result = append(result, link.TargetHypha())
+ }
+ }
+ return result
+}
+
+// LinkVisitor creates a visitor which extracts all the links
+func LinkVisitor(ctx mycocontext.Context) (
+ visitor func(block blocks.Block),
+ result func() []links.Link,
+) {
+ var (
+ collected []links.Link
+ )
+ var extractBlock func(block blocks.Block)
+ extractBlock = func(block blocks.Block) {
+ // fmt.Println(reflect.TypeOf(block))
+ switch b := block.(type) {
+ case blocks.Paragraph:
+ extractBlock(b.Formatted)
+ case blocks.Heading:
+ extractBlock(b.GetContents())
+ case blocks.List:
+ for _, item := range b.Items {
+ for _, sub := range item.Contents {
+ extractBlock(sub)
+ }
+ }
+ case blocks.Img:
+ for _, entry := range b.Entries {
+ extractBlock(entry)
+ }
+ case blocks.ImgEntry:
+ collected = append(collected, *b.Srclink)
+ case blocks.Transclusion:
+ link := *links.From(b.Target, "", ctx.HyphaName())
+ collected = append(collected, link)
+ case blocks.LaunchPad:
+ for _, rocket := range b.Rockets {
+ extractBlock(rocket)
+ }
+ case blocks.Formatted:
+ for _, line := range b.Lines {
+ for _, span := range line {
+ switch s := span.(type) {
+ case blocks.InlineLink:
+ collected = append(collected, *s.Link)
+ }
+ }
+ }
+ case blocks.RocketLink:
+ if !b.IsEmpty {
+ collected = append(collected, b.Link)
+ }
+ }
+ }
+ visitor = func(block blocks.Block) {
+ extractBlock(block)
+ }
+ result = func() []links.Link {
+ return collected
+ }
+ return
+}
diff --git a/main.go b/main.go
index 21abf6e..49f9fc6 100644
--- a/main.go
+++ b/main.go
@@ -37,6 +37,7 @@ func main() {
// Init the subsystems:
hyphae.Index(files.HyphaeDir())
+ hyphae.IndexBacklinks()
user.InitUserDatabase()
history.Start()
history.InitGitRepo()
diff --git a/static/default.css b/static/default.css
index e98700f..125e3c7 100644
--- a/static/default.css
+++ b/static/default.css
@@ -11,10 +11,10 @@
.modal__title_small { font-size: 1.5rem; }
.modal__confirmation-msg { margin: 0 0 .5rem 0; }
-.hypha-list, .title-search__results { padding-left: 0; }
-.hypha-list__entry, .title-search__entry { list-style-type: none; }
-.hypha-list__link, .title-search__link { text-decoration: none; display: inline-block; padding: .25rem; }
-.hypha-list__link:hover, .title-search__link:hover { text-decoration: underline; }
+.hypha-list, .title-search__results, .backlinks__list { padding-left: 0; }
+.hypha-list__entry, .title-search__entry, .backlinks__entry { list-style-type: none; }
+.hypha-list__link, .title-search__link, .backlinks__link { text-decoration: none; display: inline-block; padding: .25rem; }
+.hypha-list__link:hover, .title-search__link:hover, .backlinks__link:hover { text-decoration: underline; }
.hypha-list__amnt-type { font-size: smaller; color: #999; }
/* General element positions, from small to big */
@@ -23,8 +23,6 @@
header { width: 100%; margin-bottom: 1rem; }
.layout-card li { list-style-type: none; }
-.backlinks__list { padding: 0; margin: 0; }
-.backlinks__link { text-decoration: none; display: block; padding: .25rem; padding-left: 1.25rem; }
@media screen and (max-width: 800px) {
.amnt-grid { grid-template-columns: 1fr; }
@@ -65,13 +63,9 @@ header { width: 100%; margin-bottom: 1rem; }
.layout { grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); }
.layout-card {max-width: 18rem;}
.main-width { margin: 0 auto; }
- .backlinks { grid-column: 1 / span 1; margin-right: 0; }
main { grid-column: 2 / span 1; }
.sibling-hyphae, .edit-toolbar, .help-topics { grid-column: 3 / span 1; margin-left: 0; }
.edit-toolbar__buttons { grid-template-columns: 1fr; }
-
- .backlinks__title { text-align: right; }
- .backlinks__link { text-align: right; padding-right: 1.25rem; padding-left: .25rem; }
}
@media screen and (min-width: 1400px) {
@@ -221,7 +215,7 @@ blockquote { border-left: 2px #999 solid; }
.upload-amnt { border: #eee 1px solid; }
td { border: #ddd 1px solid; }
-.sibling-hyphae__link:hover, .backlinks__link:hover { background-color: #eee; }
+.sibling-hyphae__link:hover { background-color: #eee; }
/* Dark theme! */
@media (prefers-color-scheme: dark) {
@@ -231,7 +225,7 @@ main, article, header, .layout-card { background-color: #343434; color: #ddd; }
a, .wikilink_external { color: #f1fa8c; }
a:visited, .wikilink_external:visited { color: #ffb86c; }
.wikilink_new, .wikilink_new:visited { color: #dd4444; }
-.subhyphae__link:hover, .sibling-hyphae__link:hover, .backlinks__link:hover { background-color: #444; }
+.subhyphae__link:hover, .sibling-hyphae__link:hover { background-color: #444; }
.prevnext__el, .prevnext__el:visited { color: #ddd; }
diff --git a/views/nav.qtpl b/views/nav.qtpl
index 31d6875..e30c598 100644
--- a/views/nav.qtpl
+++ b/views/nav.qtpl
@@ -22,6 +22,7 @@
{%= hyphaInfoEntry(h, u, "delete-ask", "Delete") %}
{%= hyphaInfoEntry(h, u, "text", "View markup") %}
{%= hyphaInfoEntry(h, u, "attachment", "Manage attachment") %}
+ {%= hyphaInfoEntry(h, u, "backlinks", "Backlinks") %}
{% endfunc %}
diff --git a/views/nav.qtpl.go b/views/nav.qtpl.go
index 00fa774..67ab48a 100644
--- a/views/nav.qtpl.go
+++ b/views/nav.qtpl.go
@@ -128,133 +128,138 @@ func streamhyphaInfo(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hyph
//line views/nav.qtpl:24
streamhyphaInfoEntry(qw422016, h, u, "attachment", "Manage attachment")
//line views/nav.qtpl:24
+ qw422016.N().S(`
+ `)
+//line views/nav.qtpl:25
+ streamhyphaInfoEntry(qw422016, h, u, "backlinks", "Backlinks")
+//line views/nav.qtpl:25
qw422016.N().S(`
`)
-//line views/nav.qtpl:27
+//line views/nav.qtpl:28
}
-//line views/nav.qtpl:27
+//line views/nav.qtpl:28
func writehyphaInfo(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha) {
-//line views/nav.qtpl:27
+//line views/nav.qtpl:28
qw422016 := qt422016.AcquireWriter(qq422016)
-//line views/nav.qtpl:27
+//line views/nav.qtpl:28
streamhyphaInfo(qw422016, rq, h)
-//line views/nav.qtpl:27
+//line views/nav.qtpl:28
qt422016.ReleaseWriter(qw422016)
-//line views/nav.qtpl:27
+//line views/nav.qtpl:28
}
-//line views/nav.qtpl:27
+//line views/nav.qtpl:28
func hyphaInfo(rq *http.Request, h *hyphae.Hypha) string {
-//line views/nav.qtpl:27
+//line views/nav.qtpl:28
qb422016 := qt422016.AcquireByteBuffer()
-//line views/nav.qtpl:27
+//line views/nav.qtpl:28
writehyphaInfo(qb422016, rq, h)
-//line views/nav.qtpl:27
+//line views/nav.qtpl:28
qs422016 := string(qb422016.B)
-//line views/nav.qtpl:27
+//line views/nav.qtpl:28
qt422016.ReleaseByteBuffer(qb422016)
-//line views/nav.qtpl:27
+//line views/nav.qtpl:28
return qs422016
-//line views/nav.qtpl:27
+//line views/nav.qtpl:28
}
-//line views/nav.qtpl:29
+//line views/nav.qtpl:30
func streamsiblingHyphaeHTML(qw422016 *qt422016.Writer, siblings string) {
-//line views/nav.qtpl:29
+//line views/nav.qtpl:30
qw422016.N().S(`
`)
-//line views/nav.qtpl:34
+//line views/nav.qtpl:35
}
-//line views/nav.qtpl:34
+//line views/nav.qtpl:35
func writesiblingHyphaeHTML(qq422016 qtio422016.Writer, siblings string) {
-//line views/nav.qtpl:34
+//line views/nav.qtpl:35
qw422016 := qt422016.AcquireWriter(qq422016)
-//line views/nav.qtpl:34
+//line views/nav.qtpl:35
streamsiblingHyphaeHTML(qw422016, siblings)
-//line views/nav.qtpl:34
+//line views/nav.qtpl:35
qt422016.ReleaseWriter(qw422016)
-//line views/nav.qtpl:34
+//line views/nav.qtpl:35
}
-//line views/nav.qtpl:34
+//line views/nav.qtpl:35
func siblingHyphaeHTML(siblings string) string {
-//line views/nav.qtpl:34
+//line views/nav.qtpl:35
qb422016 := qt422016.AcquireByteBuffer()
-//line views/nav.qtpl:34
+//line views/nav.qtpl:35
writesiblingHyphaeHTML(qb422016, siblings)
-//line views/nav.qtpl:34
+//line views/nav.qtpl:35
qs422016 := string(qb422016.B)
-//line views/nav.qtpl:34
+//line views/nav.qtpl:35
qt422016.ReleaseByteBuffer(qb422016)
-//line views/nav.qtpl:34
+//line views/nav.qtpl:35
return qs422016
-//line views/nav.qtpl:34
+//line views/nav.qtpl:35
}
-//line views/nav.qtpl:36
+//line views/nav.qtpl:37
func StreamSubhyphaeHTML(qw422016 *qt422016.Writer, subhyphae string) {
-//line views/nav.qtpl:36
+//line views/nav.qtpl:37
qw422016.N().S(`
`)
-//line views/nav.qtpl:37
+//line views/nav.qtpl:38
if strings.TrimSpace(subhyphae) != "" {
-//line views/nav.qtpl:37
+//line views/nav.qtpl:38
qw422016.N().S(`
Subhyphae
Hyphae which have a link to the selected hypha are listed below.
+Hyphae which have a link to the selected hypha are listed below.
+Try finding a different entry that would help you.
If you want to write this entry by yourself, consider contributing to Mycorrhiza Wiki directly.
`) -//line views/stuff.qtpl:138 +//line views/stuff.qtpl:153 } -//line views/stuff.qtpl:138 +//line views/stuff.qtpl:153 func WriteHelpEmptyErrorHTML(qq422016 qtio422016.Writer) { -//line views/stuff.qtpl:138 +//line views/stuff.qtpl:153 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:138 +//line views/stuff.qtpl:153 StreamHelpEmptyErrorHTML(qw422016) -//line views/stuff.qtpl:138 +//line views/stuff.qtpl:153 qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:138 +//line views/stuff.qtpl:153 } -//line views/stuff.qtpl:138 +//line views/stuff.qtpl:153 func HelpEmptyErrorHTML() string { -//line views/stuff.qtpl:138 +//line views/stuff.qtpl:153 qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:138 +//line views/stuff.qtpl:153 WriteHelpEmptyErrorHTML(qb422016) -//line views/stuff.qtpl:138 +//line views/stuff.qtpl:153 qs422016 := string(qb422016.B) -//line views/stuff.qtpl:138 +//line views/stuff.qtpl:153 qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:138 +//line views/stuff.qtpl:153 return qs422016 -//line views/stuff.qtpl:138 +//line views/stuff.qtpl:153 } -//line views/stuff.qtpl:140 +//line views/stuff.qtpl:155 func streamhelpTopicsHTML(qw422016 *qt422016.Writer) { -//line views/stuff.qtpl:140 +//line views/stuff.qtpl:155 qw422016.N().S(` `) -//line views/stuff.qtpl:173 +//line views/stuff.qtpl:188 } -//line views/stuff.qtpl:173 +//line views/stuff.qtpl:188 func writehelpTopicsHTML(qq422016 qtio422016.Writer) { -//line views/stuff.qtpl:173 +//line views/stuff.qtpl:188 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:173 +//line views/stuff.qtpl:188 streamhelpTopicsHTML(qw422016) -//line views/stuff.qtpl:173 +//line views/stuff.qtpl:188 qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:173 +//line views/stuff.qtpl:188 } -//line views/stuff.qtpl:173 +//line views/stuff.qtpl:188 func helpTopicsHTML() string { -//line views/stuff.qtpl:173 +//line views/stuff.qtpl:188 qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:173 +//line views/stuff.qtpl:188 writehelpTopicsHTML(qb422016) -//line views/stuff.qtpl:173 +//line views/stuff.qtpl:188 qs422016 := string(qb422016.B) -//line views/stuff.qtpl:173 +//line views/stuff.qtpl:188 qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:173 +//line views/stuff.qtpl:188 return qs422016 -//line views/stuff.qtpl:173 +//line views/stuff.qtpl:188 } -//line views/stuff.qtpl:175 +//line views/stuff.qtpl:190 func streamhelpTopicBadgeHTML(qw422016 *qt422016.Writer, lang, topic string) { -//line views/stuff.qtpl:175 +//line views/stuff.qtpl:190 qw422016.N().S(` ? `) -//line views/stuff.qtpl:177 +//line views/stuff.qtpl:192 } -//line views/stuff.qtpl:177 +//line views/stuff.qtpl:192 func writehelpTopicBadgeHTML(qq422016 qtio422016.Writer, lang, topic string) { -//line views/stuff.qtpl:177 +//line views/stuff.qtpl:192 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:177 +//line views/stuff.qtpl:192 streamhelpTopicBadgeHTML(qw422016, lang, topic) -//line views/stuff.qtpl:177 +//line views/stuff.qtpl:192 qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:177 +//line views/stuff.qtpl:192 } -//line views/stuff.qtpl:177 +//line views/stuff.qtpl:192 func helpTopicBadgeHTML(lang, topic string) string { -//line views/stuff.qtpl:177 +//line views/stuff.qtpl:192 qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:177 +//line views/stuff.qtpl:192 writehelpTopicBadgeHTML(qb422016, lang, topic) -//line views/stuff.qtpl:177 +//line views/stuff.qtpl:192 qs422016 := string(qb422016.B) -//line views/stuff.qtpl:177 +//line views/stuff.qtpl:192 qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:177 +//line views/stuff.qtpl:192 return qs422016 -//line views/stuff.qtpl:177 +//line views/stuff.qtpl:192 } -//line views/stuff.qtpl:179 +//line views/stuff.qtpl:194 func StreamUserListHTML(qw422016 *qt422016.Writer) { -//line views/stuff.qtpl:179 +//line views/stuff.qtpl:194 qw422016.N().S(`This wiki has `) -//line views/stuff.qtpl:226 +//line views/stuff.qtpl:241 qw422016.N().D(hyphae.Count()) -//line views/stuff.qtpl:226 +//line views/stuff.qtpl:241 qw422016.N().S(` hyphae.
See /list for information about hyphae on this wiki.