cerrado @ ce5185f551b707fe8dd8db8b5cbffd46e96cacc0

feat: Add per repository about page
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 6ac6d0560dddf90e88fdce766d2a0261d5a4ab2c..3759b7cc65b14ec93b21b2e862e20b57248ca111 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -42,6 +42,7 @@ 		Name        string
 		Path        string
 		Description string
 		Public      bool
+		About       string
 	}
 
 	// ConfigurationRepository represents the configuration repository (as in
@@ -214,6 +215,10 @@ 			case "public":
 				if err := setBool(d, &repository.Public); err != nil {
 					return err
 				}
+			case "about":
+				if err := setString(d, &repository.About); err != nil {
+					return err
+				}
 			}
 		}
 
@@ -249,6 +254,7 @@ 		Path:        path,
 		Name:        filepath.Base(path),
 		Description: "",
 		Public:      false,
+		About:       "README.md",
 	}
 }
 
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index 2d779c53ce9aee562d0e7d57e354b7470aa534de..8c1d27ebe8586e356314e45e6a5742766928b443 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -53,6 +53,7 @@ 						Name:        "cerrado.git",
 						Path:        "/srv/git/cerrado.git",
 						Description: "",
 						Public:      false,
+						About:       "README.md",
 					},
 				},
 			},
@@ -64,6 +65,7 @@ repository /srv/git/cerrado.git {
 	name cerrado
 	description "Single person forge"
 	public true
+	about readme.txt
 }`,
 			expectedConfig: &configuration{
 				Scan:       defaultScan(),
@@ -74,6 +76,7 @@ 						Name:        "cerrado",
 						Path:        "/srv/git/cerrado.git",
 						Description: "Single person forge",
 						Public:      true,
+						About:       "readme.txt",
 					},
 				},
 			},
@@ -111,6 +114,7 @@ repository /srv/git/cerrado.git {
 	name cerrado
 	description "Single person forge"
 	public true
+	about readme.txt
 }`,
 			expectedConfig: &configuration{
 				Scan: &scan{
@@ -124,12 +128,14 @@ 						Name:        "linux.git",
 						Path:        "/srv/git/linux.git",
 						Description: "",
 						Public:      false,
+						About:       "README.md",
 					},
 					{
 						Name:        "cerrado",
 						Path:        "/srv/git/cerrado.git",
 						Description: "Single person forge",
 						Public:      true,
+						About:       "readme.txt",
 					},
 				},
 			},
diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go
index 899f61ee30eab7f322c42d25636de59791763221..25505ba4c8ebfc520f69f9e21b87eef41752cd66 100644
--- a/pkg/handler/git/handler.go
+++ b/pkg/handler/git/handler.go
@@ -33,6 +33,7 @@ 		ListCommits(name string, ref string, count int) ([]*object.Commit, error)
 		GetHead(name string) (*plumbing.Reference, error)
 		GetTree(name, ref, path string) (*object.Tree, error)
 		GetFileContent(name, ref, path string) (string, error)
+		GetAbout(name string) (string, error)
 		ListTags(name string) ([]*plumbing.Reference, error)
 		ListBranches(name string) ([]*plumbing.Reference, error)
 	}
@@ -126,10 +127,28 @@ 	ref, err := g.gitService.GetHead(name)
 	if err != nil {
 		return err
 	}
+
+	file, err := g.gitService.GetAbout(name)
+	if err != nil {
+		return err
+	}
+
+	extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
+	p := parser.NewWithExtensions(extensions)
+	doc := p.Parse([]byte(file))
+
+	htmlFlag := markdownhtml.CommonFlags | markdownhtml.HrefTargetBlank
+	opts := markdownhtml.RendererOptions{Flags: htmlFlag}
+	renderer := markdownhtml.NewRenderer(opts)
+
+	bs := markdown.Render(doc, renderer)
+
 	gitList := &templates.GitItemPage{
-		Name:        name,
-		Ref:         ref.Name().Short(),
-		GitItemBase: &templates.GitItemAboutPage{},
+		Name: name,
+		Ref:  ref.Name().Short(),
+		GitItemBase: &templates.GitItemAboutPage{
+			About: bs,
+		},
 	}
 	templates.WritePageTemplate(w, gitList)
 	return nil
diff --git a/pkg/service/git.go b/pkg/service/git.go
index 2165abef8acdfbe13d4831c7e6e0c2556678b004..6bb6e9ebb7739ae025c124766ac402d19c429aad 100644
--- a/pkg/service/git.go
+++ b/pkg/service/git.go
@@ -128,6 +128,24 @@
 	return repo.FileContent(path)
 }
 
+func (g *GitService) GetAbout(name string) (string, error) {
+	r := g.configRepo.GetByName(name)
+	if r == nil {
+		return "", RepositoryNotFoundErr
+	}
+
+	repo, err := git.OpenRepository(r.Path)
+	if err != nil {
+		return "", err
+	}
+	err = repo.SetRef("")
+	if err != nil {
+		return "", err
+	}
+
+	return repo.FileContent(r.About)
+}
+
 func (g *GitService) ListTags(name string) ([]*plumbing.Reference, error) {
 	r := g.configRepo.GetByName(name)
 	if r == nil {
diff --git a/scss/main.scss b/scss/main.scss
index 107a4c8231a96c66e05ffc6cc91dda311568a367..c438a906e17ffab32f1ce85550253e1b7ea74847 100644
--- a/scss/main.scss
+++ b/scss/main.scss
@@ -60,6 +60,12 @@       end: right,
       center: center,
     )
   ),
+  "margin": (
+    responsive: true,
+    property: margin,
+    class: m,
+    values: map-merge($spacers, (auto: auto))
+  ),
   "margin-end": (
     responsive: true,
     property: margin-right,
@@ -110,6 +116,11 @@   margin: 0.5rem 0;
 }
 
 .code-view {
+    display: grid;
+    overflow-x: auto;
+}
+
+pre {
     display: grid;
     overflow-x: auto;
 }
diff --git a/templates/gititemabout.qtpl b/templates/gititemabout.qtpl
index abda2fb2476cf828c66e19736aea6e857feac0fd..da9c2e963053111c905671375f028ebf95b1123b 100644
--- a/templates/gititemabout.qtpl
+++ b/templates/gititemabout.qtpl
@@ -1,10 +1,13 @@
 {% code
 type GitItemAboutPage struct {
+    About []byte
 }
 %}
 
 {% func (g *GitItemAboutPage) Nav(name, ref string) %}{%= GitItemNav(name, ref, Readme) %}{% endfunc %}
 
 {% func (g *GitItemAboutPage) GitContent(name, ref string) %}
-<h4>About</h4>
+<div class="m-2">
+{%z= g.About %}
+</div>
 {% endfunc %}
diff --git a/templates/gititemabout.qtpl.go b/templates/gititemabout.qtpl.go
index cd49d2fa25928890cb2a8e6ccda5d5057f1144e0..e1c1db423a94f90e4a9fb1d3563acfbdca7c9698 100644
--- a/templates/gititemabout.qtpl.go
+++ b/templates/gititemabout.qtpl.go
@@ -19,72 +19,79 @@ )
 
 //line gititemabout.qtpl:2
 type GitItemAboutPage struct {
+	About []byte
 }
 
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 func (g *GitItemAboutPage) StreamNav(qw422016 *qt422016.Writer, name, ref string) {
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 	StreamGitItemNav(qw422016, name, ref, Readme)
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 }
 
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 func (g *GitItemAboutPage) WriteNav(qq422016 qtio422016.Writer, name, ref string) {
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 	g.StreamNav(qw422016, name, ref)
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 	qt422016.ReleaseWriter(qw422016)
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 }
 
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 func (g *GitItemAboutPage) Nav(name, ref string) string {
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 	qb422016 := qt422016.AcquireByteBuffer()
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 	g.WriteNav(qb422016, name, ref)
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 	qs422016 := string(qb422016.B)
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 	qt422016.ReleaseByteBuffer(qb422016)
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 	return qs422016
-//line gititemabout.qtpl:6
+//line gititemabout.qtpl:7
 }
 
-//line gititemabout.qtpl:8
+//line gititemabout.qtpl:9
 func (g *GitItemAboutPage) StreamGitContent(qw422016 *qt422016.Writer, name, ref string) {
-//line gititemabout.qtpl:8
+//line gititemabout.qtpl:9
+	qw422016.N().S(`
+<div class="m-2">
+`)
+//line gititemabout.qtpl:11
+	qw422016.N().Z(g.About)
+//line gititemabout.qtpl:11
 	qw422016.N().S(`
-<h4>About</h4>
+</div>
 `)
-//line gititemabout.qtpl:10
+//line gititemabout.qtpl:13
 }
 
-//line gititemabout.qtpl:10
+//line gititemabout.qtpl:13
 func (g *GitItemAboutPage) WriteGitContent(qq422016 qtio422016.Writer, name, ref string) {
-//line gititemabout.qtpl:10
+//line gititemabout.qtpl:13
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line gititemabout.qtpl:10
+//line gititemabout.qtpl:13
 	g.StreamGitContent(qw422016, name, ref)
-//line gititemabout.qtpl:10
+//line gititemabout.qtpl:13
 	qt422016.ReleaseWriter(qw422016)
-//line gititemabout.qtpl:10
+//line gititemabout.qtpl:13
 }
 
-//line gititemabout.qtpl:10
+//line gititemabout.qtpl:13
 func (g *GitItemAboutPage) GitContent(name, ref string) string {
-//line gititemabout.qtpl:10
+//line gititemabout.qtpl:13
 	qb422016 := qt422016.AcquireByteBuffer()
-//line gititemabout.qtpl:10
+//line gititemabout.qtpl:13
 	g.WriteGitContent(qb422016, name, ref)
-//line gititemabout.qtpl:10
+//line gititemabout.qtpl:13
 	qs422016 := string(qb422016.B)
-//line gititemabout.qtpl:10
+//line gititemabout.qtpl:13
 	qt422016.ReleaseByteBuffer(qb422016)
-//line gititemabout.qtpl:10
+//line gititemabout.qtpl:13
 	return qs422016
-//line gititemabout.qtpl:10
+//line gititemabout.qtpl:13
 }
diff --git a/templates/gititemlog.qtpl b/templates/gititemlog.qtpl
index a28e7ad52cbd51c2cb6509b3f008116b61e7d8be..3588d32403566a2b260c68d8d50c2b176671ad22 100644
--- a/templates/gititemlog.qtpl
+++ b/templates/gititemlog.qtpl
@@ -12,6 +12,7 @@ {% func (g *GitItemLogPage) GitContent(name, ref string) %}
 <div class="event-list">
   {% for _, c := range g.Commits %}
   <div class="row event">
+
       <div class="col-xxl-2">
        {%s TimeFormat(c.Committer.When) %}
       </div>
diff --git a/templates/gititemlog.qtpl.go b/templates/gititemlog.qtpl.go
index 76c24329c241285809f56a6a9014e1e4274d47bb..b950d1550a22169e8ca085849ca1ddad5dfb4aea 100644
--- a/templates/gititemlog.qtpl.go
+++ b/templates/gititemlog.qtpl.go
@@ -69,64 +69,65 @@ 	for _, c := range g.Commits {
 //line gititemlog.qtpl:13
 		qw422016.N().S(`
   <div class="row event">
+
       <div class="col-xxl-2">
        `)
-//line gititemlog.qtpl:16
+//line gititemlog.qtpl:17
 		qw422016.E().S(TimeFormat(c.Committer.When))
-//line gititemlog.qtpl:16
+//line gititemlog.qtpl:17
 		qw422016.N().S(`
       </div>
       <div class="col-xxl-7 code-view">
        <pre>`)
-//line gititemlog.qtpl:19
+//line gititemlog.qtpl:20
 		qw422016.E().S(c.Message)
-//line gititemlog.qtpl:19
+//line gititemlog.qtpl:20
 		qw422016.N().S(`</pre>
       </div>
       <div class="col-xxl-3">
        <small>`)
-//line gititemlog.qtpl:22
+//line gititemlog.qtpl:23
 		qw422016.E().S(c.Committer.Name)
-//line gititemlog.qtpl:22
+//line gititemlog.qtpl:23
 		qw422016.N().S(` &lt;`)
-//line gititemlog.qtpl:22
+//line gititemlog.qtpl:23
 		qw422016.E().S(c.Committer.Email)
-//line gititemlog.qtpl:22
+//line gititemlog.qtpl:23
 		qw422016.N().S(`&gt;</small>
       </div>
   </div>
   `)
-//line gititemlog.qtpl:25
+//line gititemlog.qtpl:26
 	}
-//line gititemlog.qtpl:25
+//line gititemlog.qtpl:26
 	qw422016.N().S(`
 </div>
 `)
-//line gititemlog.qtpl:27
+//line gititemlog.qtpl:28
 }
 
-//line gititemlog.qtpl:27
+//line gititemlog.qtpl:28
 func (g *GitItemLogPage) WriteGitContent(qq422016 qtio422016.Writer, name, ref string) {
-//line gititemlog.qtpl:27
+//line gititemlog.qtpl:28
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line gititemlog.qtpl:27
+//line gititemlog.qtpl:28
 	g.StreamGitContent(qw422016, name, ref)
-//line gititemlog.qtpl:27
+//line gititemlog.qtpl:28
 	qt422016.ReleaseWriter(qw422016)
-//line gititemlog.qtpl:27
+//line gititemlog.qtpl:28
 }
 
-//line gititemlog.qtpl:27
+//line gititemlog.qtpl:28
 func (g *GitItemLogPage) GitContent(name, ref string) string {
-//line gititemlog.qtpl:27
+//line gititemlog.qtpl:28
 	qb422016 := qt422016.AcquireByteBuffer()
-//line gititemlog.qtpl:27
+//line gititemlog.qtpl:28
 	g.WriteGitContent(qb422016, name, ref)
-//line gititemlog.qtpl:27
+//line gititemlog.qtpl:28
 	qs422016 := string(qb422016.B)
-//line gititemlog.qtpl:27
+//line gititemlog.qtpl:28
 	qt422016.ReleaseByteBuffer(qb422016)
-//line gititemlog.qtpl:27
+//line gititemlog.qtpl:28
 	return qs422016
-//line gititemlog.qtpl:27
+//line gititemlog.qtpl:28
 }