diff --git a/README.md b/README.md
index a733e95..e87ddfc 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,13 @@
-# 🍄 MycorrhizaWiki 0.8
+# 🍄 MycorrhizaWiki 0.9
A wiki engine.
+## 0.9
+This is a development branch for 0.9 version. Features I want to implement in this release:
+* [x] Recent changes page.
+* [ ] Hypha deletion.
+* [ ] Hypha renaming.
+* [ ] Support async git ops.
+
## Installation
```sh
git clone --recurse-submodules https://github.com/bouncepaw/mycorrhiza
@@ -31,5 +38,4 @@ Help is always needed. We have a [tg chat](https://t.me/mycorrhizadev) where som
* Tagging system
* Authorization
* Better history viewing
-* Recent changes page
* More markups
diff --git a/go.mod b/go.mod
index 17f2ff6..e2ffb7c 100644
--- a/go.mod
+++ b/go.mod
@@ -2,4 +2,4 @@ module github.com/bouncepaw/mycorrhiza
go 1.14
-require github.com/valyala/quicktemplate v1.6.2
+require github.com/valyala/quicktemplate v1.6.3
diff --git a/go.sum b/go.sum
index 344ceb3..a9a43a1 100644
--- a/go.sum
+++ b/go.sum
@@ -1,11 +1,11 @@
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasthttp v1.15.1/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
-github.com/valyala/quicktemplate v1.6.2 h1:k0vgK7zlmFzqAoIBIOrhrfmZ6JoTGJlLRPLbkPGr2/M=
-github.com/valyala/quicktemplate v1.6.2/go.mod h1:mtEJpQtUiBV0SHhMX6RtiJtqxncgrfmjcUy5T68X8TM=
+github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
+github.com/valyala/quicktemplate v1.6.3 h1:O7EuMwuH7Q94U2CXD6sOX8AYHqQqWtmIk690IhmpkKA=
+github.com/valyala/quicktemplate v1.6.3/go.mod h1:fwPzK2fHuYEODzJ9pkw0ipCPNHZ2tD5KW4lOuSdPKzY=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
diff --git a/history/history.go b/history/history.go
index 787b358..cbd689b 100644
--- a/history/history.go
+++ b/history/history.go
@@ -6,6 +6,7 @@ import (
"log"
"os/exec"
"strconv"
+ "strings"
"time"
"github.com/bouncepaw/mycorrhiza/util"
@@ -31,6 +32,58 @@ type Revision struct {
Message string
}
+// TimeString returns a human readable time representation.
+func (rev Revision) TimeString() string {
+ return rev.Time.Format(time.RFC822)
+}
+
+// HyphaeLinks returns a comma-separated list of hyphae that were affected by this revision as HTML string.
+func (rev Revision) HyphaeLinks() (html string) {
+ // diff-tree --no-commit-id --name-only -r
+ var (
+ // List of files affected by this revision, one per line.
+ out, err = gitsh("diff-tree", "--no-commit-id", "--name-only", "-r", rev.Hash)
+ // set is used to determine if a certain hypha has been already noted (hyphae are stored in 2 files at most).
+ set = make(map[string]bool)
+ isNewName = func(hyphaName string) bool {
+ if _, present := set[hyphaName]; present {
+ return false
+ } else {
+ set[hyphaName] = true
+ return true
+ }
+ }
+ )
+ if err != nil {
+ return ""
+ }
+ for _, filename := range strings.Split(out.String(), "\n") {
+ // If filename has an ampersand:
+ if strings.IndexRune(filename, '&') >= 0 {
+ // Remove ampersanded suffix from filename:
+ ampersandPos := strings.LastIndexByte(filename, '&')
+ hyphaName := string([]byte(filename)[0:ampersandPos]) // is it safe?
+ if isNewName(hyphaName) {
+ // Entries are separated by commas
+ if len(set) > 1 {
+ html += `, `
+ }
+ html += fmt.Sprintf(`%[2]s `, rev.Hash, hyphaName)
+ }
+ }
+ }
+ return html
+}
+
+func (rev Revision) RecentChangesEntry() (html string) {
+ return fmt.Sprintf(`
+
%s
+%s
+%s
+%s
+`, rev.TimeString(), rev.Hash, rev.HyphaeLinks(), rev.Message)
+}
+
// Path to git executable. Set at init()
var gitpath string
diff --git a/history/information.go b/history/information.go
index ff6e654..bb20e6a 100644
--- a/history/information.go
+++ b/history/information.go
@@ -6,9 +6,30 @@ import (
"fmt"
"regexp"
"strings"
- "time"
+
+ "github.com/bouncepaw/mycorrhiza/templates"
)
+func RecentChanges(n int) string {
+ var (
+ out, err = gitsh(
+ "log", "--oneline", "--no-merges",
+ "--pretty=format:\"%h\t%ce\t%ct\t%s\"",
+ )
+ revs []Revision
+ )
+ if err == nil {
+ for _, line := range strings.Split(out.String(), "\n") {
+ revs = append(revs, parseRevisionLine(line))
+ }
+ }
+ entries := make([]string, len(revs))
+ for i, rev := range revs {
+ entries[i] = rev.RecentChangesEntry()
+ }
+ return templates.RecentChangesHTML(entries, n)
+}
+
// Revisions returns slice of revisions for the given hypha name.
func Revisions(hyphaName string) ([]Revision, error) {
var (
@@ -48,7 +69,7 @@ func (rev *Revision) AsHtmlTableRow(hyphaName string) string {
%s
%s
%s
-`, rev.Time.Format(time.RFC822), rev.Hash, hyphaName, rev.Hash, rev.Message)
+`, rev.TimeString(), rev.Hash, hyphaName, rev.Hash, rev.Message)
}
// See how the file with `filepath` looked at commit with `hash`.
diff --git a/main.go b/main.go
index cb9588f..5ef555d 100644
--- a/main.go
+++ b/main.go
@@ -10,6 +10,8 @@ import (
"os"
"path/filepath"
"regexp"
+ "strconv"
+ "strings"
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/templates"
@@ -83,6 +85,20 @@ func handlerRandom(w http.ResponseWriter, rq *http.Request) {
http.Redirect(w, rq, "/page/"+randomHyphaName, http.StatusSeeOther)
}
+// Recent changes
+func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) {
+ log.Println(rq.URL)
+ var (
+ noPrefix = strings.TrimPrefix(rq.URL.String(), "/recent-changes/")
+ n, err = strconv.Atoi(noPrefix)
+ )
+ if err == nil {
+ util.HTTP200Page(w, base(strconv.Itoa(n)+" recent changes", history.RecentChanges(n)))
+ } else {
+ http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther)
+ }
+}
+
func main() {
log.Println("Running MycorrhizaWiki β")
@@ -108,6 +124,7 @@ func main() {
http.HandleFunc("/list", handlerList)
http.HandleFunc("/reindex", handlerReindex)
http.HandleFunc("/random", handlerRandom)
+ http.HandleFunc("/recent-changes/", handlerRecentChanges)
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) {
http.ServeFile(w, rq, WikiDir+"/static/favicon.ico")
})
diff --git a/metarrhiza b/metarrhiza
index bdaaab6..2c0e431 160000
--- a/metarrhiza
+++ b/metarrhiza
@@ -1 +1 @@
-Subproject commit bdaaab62574023487610d608d1e9f2f351707a7f
+Subproject commit 2c0e43199ed28f7022a38463a0eec3af3ecb03c9
diff --git a/templates/recent_changes.qtpl b/templates/recent_changes.qtpl
new file mode 100644
index 0000000..2f9f8c4
--- /dev/null
+++ b/templates/recent_changes.qtpl
@@ -0,0 +1,40 @@
+{% func RecentChangesHTML(changes []string, n int) %}
+
+ Recent Changes
+
+
+ See
+ {% for _, m := range []int{20, 0, 50, 0, 100} %}
+ {% switch m %}
+ {% case 0 %}
+ |
+ {% case n %}
+ {%d n %}
+ {% default %}
+ {%d m %}
+ {% endswitch %}
+ {% endfor %}
+ recent changes
+
+
+ {% comment %}
+ Here I am, willing to add some accesibility using ARIA. Turns out,
+ role="feed" is not supported in any screen reader as of September
+ 2020. At least web search says so. Even JAWS doesn't support it!
+ How come? I'll add the role anyway. -- bouncepaw
+ {% endcomment %}
+
+
+ {% if len(changes) == 0 %}
+ Could not find any recent changes.
+ {% else %}
+ {% for i, entry := range changes %}
+
+ {% endfor %}
+ {% endif %}
+
+
+{% endfunc %}
diff --git a/templates/recent_changes.qtpl.go b/templates/recent_changes.qtpl.go
new file mode 100644
index 0000000..3814504
--- /dev/null
+++ b/templates/recent_changes.qtpl.go
@@ -0,0 +1,156 @@
+// Code generated by qtc from "recent_changes.qtpl". DO NOT EDIT.
+// See https://github.com/valyala/quicktemplate for details.
+
+//line templates/recent_changes.qtpl:1
+package templates
+
+//line templates/recent_changes.qtpl:1
+import (
+ qtio422016 "io"
+
+ qt422016 "github.com/valyala/quicktemplate"
+)
+
+//line templates/recent_changes.qtpl:1
+var (
+ _ = qtio422016.Copy
+ _ = qt422016.AcquireByteBuffer
+)
+
+//line templates/recent_changes.qtpl:1
+func StreamRecentChangesHTML(qw422016 *qt422016.Writer, changes []string, n int) {
+//line templates/recent_changes.qtpl:1
+ qw422016.N().S(`
+
+ Recent Changes
+
+
+ See
+ `)
+//line templates/recent_changes.qtpl:7
+ for _, m := range []int{20, 0, 50, 0, 100} {
+//line templates/recent_changes.qtpl:7
+ qw422016.N().S(`
+ `)
+//line templates/recent_changes.qtpl:8
+ switch m {
+//line templates/recent_changes.qtpl:9
+ case 0:
+//line templates/recent_changes.qtpl:9
+ qw422016.N().S(`
+ |
+ `)
+//line templates/recent_changes.qtpl:11
+ case n:
+//line templates/recent_changes.qtpl:11
+ qw422016.N().S(`
+ `)
+//line templates/recent_changes.qtpl:12
+ qw422016.N().D(n)
+//line templates/recent_changes.qtpl:12
+ qw422016.N().S(`
+ `)
+//line templates/recent_changes.qtpl:13
+ default:
+//line templates/recent_changes.qtpl:13
+ qw422016.N().S(`
+ `)
+//line templates/recent_changes.qtpl:14
+ qw422016.N().D(m)
+//line templates/recent_changes.qtpl:14
+ qw422016.N().S(`
+ `)
+//line templates/recent_changes.qtpl:15
+ }
+//line templates/recent_changes.qtpl:15
+ qw422016.N().S(`
+ `)
+//line templates/recent_changes.qtpl:16
+ }
+//line templates/recent_changes.qtpl:16
+ qw422016.N().S(`
+ recent changes
+
+
+ `)
+//line templates/recent_changes.qtpl:25
+ qw422016.N().S(`
+
+
+ `)
+//line templates/recent_changes.qtpl:28
+ if len(changes) == 0 {
+//line templates/recent_changes.qtpl:28
+ qw422016.N().S(`
+ Could not find any recent changes.
+ `)
+//line templates/recent_changes.qtpl:30
+ } else {
+//line templates/recent_changes.qtpl:30
+ qw422016.N().S(`
+ `)
+//line templates/recent_changes.qtpl:31
+ for i, entry := range changes {
+//line templates/recent_changes.qtpl:31
+ qw422016.N().S(`
+
+ `)
+//line templates/recent_changes.qtpl:34
+ qw422016.N().S(entry)
+//line templates/recent_changes.qtpl:34
+ qw422016.N().S(`
+
+ `)
+//line templates/recent_changes.qtpl:36
+ }
+//line templates/recent_changes.qtpl:36
+ qw422016.N().S(`
+ `)
+//line templates/recent_changes.qtpl:37
+ }
+//line templates/recent_changes.qtpl:37
+ qw422016.N().S(`
+
+
+`)
+//line templates/recent_changes.qtpl:40
+}
+
+//line templates/recent_changes.qtpl:40
+func WriteRecentChangesHTML(qq422016 qtio422016.Writer, changes []string, n int) {
+//line templates/recent_changes.qtpl:40
+ qw422016 := qt422016.AcquireWriter(qq422016)
+//line templates/recent_changes.qtpl:40
+ StreamRecentChangesHTML(qw422016, changes, n)
+//line templates/recent_changes.qtpl:40
+ qt422016.ReleaseWriter(qw422016)
+//line templates/recent_changes.qtpl:40
+}
+
+//line templates/recent_changes.qtpl:40
+func RecentChangesHTML(changes []string, n int) string {
+//line templates/recent_changes.qtpl:40
+ qb422016 := qt422016.AcquireByteBuffer()
+//line templates/recent_changes.qtpl:40
+ WriteRecentChangesHTML(qb422016, changes, n)
+//line templates/recent_changes.qtpl:40
+ qs422016 := string(qb422016.B)
+//line templates/recent_changes.qtpl:40
+ qt422016.ReleaseByteBuffer(qb422016)
+//line templates/recent_changes.qtpl:40
+ return qs422016
+//line templates/recent_changes.qtpl:40
+}