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

`) -//line views/nav.qtpl:46 +//line views/nav.qtpl:47 } -//line views/nav.qtpl:46 +//line views/nav.qtpl:47 qw422016.N().S(` `) -//line views/nav.qtpl:47 +//line views/nav.qtpl:48 } -//line views/nav.qtpl:47 +//line views/nav.qtpl:48 func WriteSubhyphaeHTML(qq422016 qtio422016.Writer, subhyphae string) { -//line views/nav.qtpl:47 +//line views/nav.qtpl:48 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/nav.qtpl:47 +//line views/nav.qtpl:48 StreamSubhyphaeHTML(qw422016, subhyphae) -//line views/nav.qtpl:47 +//line views/nav.qtpl:48 qt422016.ReleaseWriter(qw422016) -//line views/nav.qtpl:47 +//line views/nav.qtpl:48 } -//line views/nav.qtpl:47 +//line views/nav.qtpl:48 func SubhyphaeHTML(subhyphae string) string { -//line views/nav.qtpl:47 +//line views/nav.qtpl:48 qb422016 := qt422016.AcquireByteBuffer() -//line views/nav.qtpl:47 +//line views/nav.qtpl:48 WriteSubhyphaeHTML(qb422016, subhyphae) -//line views/nav.qtpl:47 +//line views/nav.qtpl:48 qs422016 := string(qb422016.B) -//line views/nav.qtpl:47 +//line views/nav.qtpl:48 qt422016.ReleaseByteBuffer(qb422016) -//line views/nav.qtpl:47 +//line views/nav.qtpl:48 return qs422016 -//line views/nav.qtpl:47 +//line views/nav.qtpl:48 } diff --git a/views/stuff.qtpl b/views/stuff.qtpl index 738fa3c..644484b 100644 --- a/views/stuff.qtpl +++ b/views/stuff.qtpl @@ -120,6 +120,21 @@ It outputs a poorly formatted JSON, but it works and is valid. } {% endfunc %} +{% func BacklinksHTML(query string, generator func(string) <-chan string) %} +
+
+

Backlinks to ‘{%s query %}’

+

Hyphae which have a link to the selected hypha are listed below.

+
+
+{% endfunc %} + {% func HelpHTML(content string) %}
diff --git a/views/stuff.qtpl.go b/views/stuff.qtpl.go index 97c573c..c24c69b 100644 --- a/views/stuff.qtpl.go +++ b/views/stuff.qtpl.go @@ -397,95 +397,161 @@ func TitleSearchJSON(query string, generator func(string) <-chan string) string } //line views/stuff.qtpl:123 -func StreamHelpHTML(qw422016 *qt422016.Writer, content string) { +func StreamBacklinksHTML(qw422016 *qt422016.Writer, query string, generator func(string) <-chan string) { //line views/stuff.qtpl:123 qw422016.N().S(`
+
+

Backlinks to ‘`) +//line views/stuff.qtpl:126 + qw422016.E().S(query) +//line views/stuff.qtpl:126 + qw422016.N().S(`’

+

Hyphae which have a link to the selected hypha are listed below.

+
+
+`) +//line views/stuff.qtpl:136 +} + +//line views/stuff.qtpl:136 +func WriteBacklinksHTML(qq422016 qtio422016.Writer, query string, generator func(string) <-chan string) { +//line views/stuff.qtpl:136 + qw422016 := qt422016.AcquireWriter(qq422016) +//line views/stuff.qtpl:136 + StreamBacklinksHTML(qw422016, query, generator) +//line views/stuff.qtpl:136 + qt422016.ReleaseWriter(qw422016) +//line views/stuff.qtpl:136 +} + +//line views/stuff.qtpl:136 +func BacklinksHTML(query string, generator func(string) <-chan string) string { +//line views/stuff.qtpl:136 + qb422016 := qt422016.AcquireByteBuffer() +//line views/stuff.qtpl:136 + WriteBacklinksHTML(qb422016, query, generator) +//line views/stuff.qtpl:136 + qs422016 := string(qb422016.B) +//line views/stuff.qtpl:136 + qt422016.ReleaseByteBuffer(qb422016) +//line views/stuff.qtpl:136 + return qs422016 +//line views/stuff.qtpl:136 +} + +//line views/stuff.qtpl:138 +func StreamHelpHTML(qw422016 *qt422016.Writer, content string) { +//line views/stuff.qtpl:138 + qw422016.N().S(` +
`) -//line views/stuff.qtpl:127 +//line views/stuff.qtpl:142 qw422016.N().S(content) -//line views/stuff.qtpl:127 +//line views/stuff.qtpl:142 qw422016.N().S(`
`) -//line views/stuff.qtpl:130 +//line views/stuff.qtpl:145 qw422016.N().S(helpTopicsHTML()) -//line views/stuff.qtpl:130 +//line views/stuff.qtpl:145 qw422016.N().S(`
`) -//line views/stuff.qtpl:132 +//line views/stuff.qtpl:147 } -//line views/stuff.qtpl:132 +//line views/stuff.qtpl:147 func WriteHelpHTML(qq422016 qtio422016.Writer, content string) { -//line views/stuff.qtpl:132 +//line views/stuff.qtpl:147 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:132 +//line views/stuff.qtpl:147 StreamHelpHTML(qw422016, content) -//line views/stuff.qtpl:132 +//line views/stuff.qtpl:147 qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:132 +//line views/stuff.qtpl:147 } -//line views/stuff.qtpl:132 +//line views/stuff.qtpl:147 func HelpHTML(content string) string { -//line views/stuff.qtpl:132 +//line views/stuff.qtpl:147 qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:132 +//line views/stuff.qtpl:147 WriteHelpHTML(qb422016, content) -//line views/stuff.qtpl:132 +//line views/stuff.qtpl:147 qs422016 := string(qb422016.B) -//line views/stuff.qtpl:132 +//line views/stuff.qtpl:147 qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:132 +//line views/stuff.qtpl:147 return qs422016 -//line views/stuff.qtpl:132 +//line views/stuff.qtpl:147 } -//line views/stuff.qtpl:134 +//line views/stuff.qtpl:149 func StreamHelpEmptyErrorHTML(qw422016 *qt422016.Writer) { -//line views/stuff.qtpl:134 +//line views/stuff.qtpl:149 qw422016.N().S(`

This entry does not exist!

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(`

List of users

`) -//line views/stuff.qtpl:184 +//line views/stuff.qtpl:199 var ( admins = make([]string, 0) moderators = make([]string, 0) @@ -617,345 +683,345 @@ func StreamUserListHTML(qw422016 *qt422016.Writer) { } } -//line views/stuff.qtpl:199 +//line views/stuff.qtpl:214 qw422016.N().S(`

Admins

    `) -//line views/stuff.qtpl:202 +//line views/stuff.qtpl:217 for _, name := range admins { -//line views/stuff.qtpl:202 +//line views/stuff.qtpl:217 qw422016.N().S(`
  1. `) -//line views/stuff.qtpl:203 +//line views/stuff.qtpl:218 qw422016.E().S(name) -//line views/stuff.qtpl:203 +//line views/stuff.qtpl:218 qw422016.N().S(`
  2. `) -//line views/stuff.qtpl:204 +//line views/stuff.qtpl:219 } -//line views/stuff.qtpl:204 +//line views/stuff.qtpl:219 qw422016.N().S(`

Moderators

    `) -//line views/stuff.qtpl:208 +//line views/stuff.qtpl:223 for _, name := range moderators { -//line views/stuff.qtpl:208 +//line views/stuff.qtpl:223 qw422016.N().S(`
  1. `) -//line views/stuff.qtpl:209 +//line views/stuff.qtpl:224 qw422016.E().S(name) -//line views/stuff.qtpl:209 +//line views/stuff.qtpl:224 qw422016.N().S(`
  2. `) -//line views/stuff.qtpl:210 +//line views/stuff.qtpl:225 } -//line views/stuff.qtpl:210 +//line views/stuff.qtpl:225 qw422016.N().S(`

Editors

    `) -//line views/stuff.qtpl:214 +//line views/stuff.qtpl:229 for _, name := range editors { -//line views/stuff.qtpl:214 +//line views/stuff.qtpl:229 qw422016.N().S(`
  1. `) -//line views/stuff.qtpl:215 +//line views/stuff.qtpl:230 qw422016.E().S(name) -//line views/stuff.qtpl:215 +//line views/stuff.qtpl:230 qw422016.N().S(`
  2. `) -//line views/stuff.qtpl:216 +//line views/stuff.qtpl:231 } -//line views/stuff.qtpl:216 +//line views/stuff.qtpl:231 qw422016.N().S(`
`) -//line views/stuff.qtpl:220 +//line views/stuff.qtpl:235 } -//line views/stuff.qtpl:220 +//line views/stuff.qtpl:235 func WriteUserListHTML(qq422016 qtio422016.Writer) { -//line views/stuff.qtpl:220 +//line views/stuff.qtpl:235 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:220 +//line views/stuff.qtpl:235 StreamUserListHTML(qw422016) -//line views/stuff.qtpl:220 +//line views/stuff.qtpl:235 qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:220 +//line views/stuff.qtpl:235 } -//line views/stuff.qtpl:220 +//line views/stuff.qtpl:235 func UserListHTML() string { -//line views/stuff.qtpl:220 +//line views/stuff.qtpl:235 qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:220 +//line views/stuff.qtpl:235 WriteUserListHTML(qb422016) -//line views/stuff.qtpl:220 +//line views/stuff.qtpl:235 qs422016 := string(qb422016.B) -//line views/stuff.qtpl:220 +//line views/stuff.qtpl:235 qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:220 +//line views/stuff.qtpl:235 return qs422016 -//line views/stuff.qtpl:220 +//line views/stuff.qtpl:235 } -//line views/stuff.qtpl:222 +//line views/stuff.qtpl:237 func StreamHyphaListHTML(qw422016 *qt422016.Writer) { -//line views/stuff.qtpl:222 +//line views/stuff.qtpl:237 qw422016.N().S(`

List of hyphae

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.

    `) -//line views/stuff.qtpl:228 +//line views/stuff.qtpl:243 for h := range hyphae.YieldExistingHyphae() { -//line views/stuff.qtpl:228 +//line views/stuff.qtpl:243 qw422016.N().S(`
  • `) -//line views/stuff.qtpl:230 +//line views/stuff.qtpl:245 qw422016.E().S(util.BeautifulName(h.Name)) -//line views/stuff.qtpl:230 +//line views/stuff.qtpl:245 qw422016.N().S(` `) -//line views/stuff.qtpl:231 +//line views/stuff.qtpl:246 if h.BinaryPath != "" { -//line views/stuff.qtpl:231 +//line views/stuff.qtpl:246 qw422016.N().S(` `) -//line views/stuff.qtpl:232 +//line views/stuff.qtpl:247 qw422016.E().S(filepath.Ext(h.BinaryPath)[1:]) -//line views/stuff.qtpl:232 +//line views/stuff.qtpl:247 qw422016.N().S(` `) -//line views/stuff.qtpl:233 +//line views/stuff.qtpl:248 } -//line views/stuff.qtpl:233 +//line views/stuff.qtpl:248 qw422016.N().S(`
  • `) -//line views/stuff.qtpl:235 +//line views/stuff.qtpl:250 } -//line views/stuff.qtpl:235 +//line views/stuff.qtpl:250 qw422016.N().S(`
`) -//line views/stuff.qtpl:239 +//line views/stuff.qtpl:254 } -//line views/stuff.qtpl:239 +//line views/stuff.qtpl:254 func WriteHyphaListHTML(qq422016 qtio422016.Writer) { -//line views/stuff.qtpl:239 +//line views/stuff.qtpl:254 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:239 +//line views/stuff.qtpl:254 StreamHyphaListHTML(qw422016) -//line views/stuff.qtpl:239 +//line views/stuff.qtpl:254 qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:239 +//line views/stuff.qtpl:254 } -//line views/stuff.qtpl:239 +//line views/stuff.qtpl:254 func HyphaListHTML() string { -//line views/stuff.qtpl:239 +//line views/stuff.qtpl:254 qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:239 +//line views/stuff.qtpl:254 WriteHyphaListHTML(qb422016) -//line views/stuff.qtpl:239 +//line views/stuff.qtpl:254 qs422016 := string(qb422016.B) -//line views/stuff.qtpl:239 +//line views/stuff.qtpl:254 qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:239 +//line views/stuff.qtpl:254 return qs422016 -//line views/stuff.qtpl:239 +//line views/stuff.qtpl:254 } -//line views/stuff.qtpl:241 +//line views/stuff.qtpl:256 func StreamAboutHTML(qw422016 *qt422016.Writer) { -//line views/stuff.qtpl:241 +//line views/stuff.qtpl:256 qw422016.N().S(`

About `) -//line views/stuff.qtpl:245 +//line views/stuff.qtpl:260 qw422016.E().S(cfg.WikiName) -//line views/stuff.qtpl:245 +//line views/stuff.qtpl:260 qw422016.N().S(`

See /list for information about hyphae on this wiki.

`) -//line views/stuff.qtpl:263 +//line views/stuff.qtpl:278 } -//line views/stuff.qtpl:263 +//line views/stuff.qtpl:278 func WriteAboutHTML(qq422016 qtio422016.Writer) { -//line views/stuff.qtpl:263 +//line views/stuff.qtpl:278 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:263 +//line views/stuff.qtpl:278 StreamAboutHTML(qw422016) -//line views/stuff.qtpl:263 +//line views/stuff.qtpl:278 qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:263 +//line views/stuff.qtpl:278 } -//line views/stuff.qtpl:263 +//line views/stuff.qtpl:278 func AboutHTML() string { -//line views/stuff.qtpl:263 +//line views/stuff.qtpl:278 qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:263 +//line views/stuff.qtpl:278 WriteAboutHTML(qb422016) -//line views/stuff.qtpl:263 +//line views/stuff.qtpl:278 qs422016 := string(qb422016.B) -//line views/stuff.qtpl:263 +//line views/stuff.qtpl:278 qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:263 +//line views/stuff.qtpl:278 return qs422016 -//line views/stuff.qtpl:263 +//line views/stuff.qtpl:278 } -//line views/stuff.qtpl:265 +//line views/stuff.qtpl:280 func StreamCommonScripts(qw422016 *qt422016.Writer) { -//line views/stuff.qtpl:265 +//line views/stuff.qtpl:280 qw422016.N().S(` `) -//line views/stuff.qtpl:266 +//line views/stuff.qtpl:281 for _, scriptPath := range cfg.CommonScripts { -//line views/stuff.qtpl:266 +//line views/stuff.qtpl:281 qw422016.N().S(` `) -//line views/stuff.qtpl:268 +//line views/stuff.qtpl:283 } -//line views/stuff.qtpl:268 +//line views/stuff.qtpl:283 qw422016.N().S(` `) -//line views/stuff.qtpl:269 +//line views/stuff.qtpl:284 } -//line views/stuff.qtpl:269 +//line views/stuff.qtpl:284 func WriteCommonScripts(qq422016 qtio422016.Writer) { -//line views/stuff.qtpl:269 +//line views/stuff.qtpl:284 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:269 +//line views/stuff.qtpl:284 StreamCommonScripts(qw422016) -//line views/stuff.qtpl:269 +//line views/stuff.qtpl:284 qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:269 +//line views/stuff.qtpl:284 } -//line views/stuff.qtpl:269 +//line views/stuff.qtpl:284 func CommonScripts() string { -//line views/stuff.qtpl:269 +//line views/stuff.qtpl:284 qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:269 +//line views/stuff.qtpl:284 WriteCommonScripts(qb422016) -//line views/stuff.qtpl:269 +//line views/stuff.qtpl:284 qs422016 := string(qb422016.B) -//line views/stuff.qtpl:269 +//line views/stuff.qtpl:284 qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:269 +//line views/stuff.qtpl:284 return qs422016 -//line views/stuff.qtpl:269 +//line views/stuff.qtpl:284 } diff --git a/web/backlinks.go b/web/backlinks.go new file mode 100644 index 0000000..68e5a71 --- /dev/null +++ b/web/backlinks.go @@ -0,0 +1,37 @@ +package web + +import ( + "fmt" + "io" + "net/http" + + "github.com/gorilla/mux" + + "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/user" + "github.com/bouncepaw/mycorrhiza/util" + "github.com/bouncepaw/mycorrhiza/views" +) + +func initBacklinks(r *mux.Router) { + r.PathPrefix("/backlinks/").HandlerFunc(handlerBacklinks) + r.PathPrefix("/backlinks-json/").HandlerFunc(handlerBacklinksJSON) +} + +// handlerBacklinks lists all backlinks to a hypha. +func handlerBacklinks(w http.ResponseWriter, rq *http.Request) { + hyphaName := util.HyphaNameFromRq(rq, "backlinks") + util.HTTP200Page(w, views.BaseHTML( + fmt.Sprintf("Backlinks to %s", util.BeautifulName(hyphaName)), + views.BacklinksHTML(hyphaName, hyphae.YieldHyphaBacklinks), + user.FromRequest(rq))) +} + +func handlerBacklinksJSON(w http.ResponseWriter, rq *http.Request) { + hyphaName := util.HyphaNameFromRq(rq, "backlinks") + w.WriteHeader(http.StatusOK) + _, _ = io.WriteString( + w, + views.TitleSearchJSON(hyphaName, hyphae.YieldHyphaBacklinks), + ) +} diff --git a/web/web.go b/web/web.go index a563a89..68b64c5 100644 --- a/web/web.go +++ b/web/web.go @@ -103,6 +103,7 @@ func Handler() http.Handler { initHistory(wikiRouter) initStuff(wikiRouter) initSearch(wikiRouter) + initBacklinks(wikiRouter) // Admin routes. if cfg.UseAuth {