Commit 5e5b5210b9
Verified · cmc
Layout: unified · split
internal/control/audit.go +12 −6
| @@ -54,20 +54,26 @@ func runAudit(c *Ctx, args []string) int { | ||
| 54 | 54 | if err != nil { |
| 55 | 55 | return c.fail(protocol.ExitFailure, "%v", err) |
| 56 | 56 | } |
| 57 | return c.emit(entries, func(w io.Writer) { | |
| 57 | return c.emitView(entries, func(w io.Writer) { | |
| 58 | 58 | tb := c.table(w, "WHEN", "ACTOR", "ACTION", "DATA") |
| 59 | 59 | for _, e := range entries { |
| 60 | 60 | actor := e.Actor |
| 61 | 61 | if actor == "" { |
| 62 | 62 | actor = "-" |
| 63 | 63 | } |
| 64 | data := e.Data | |
| 65 | if c.Term.Cols > 0 { | |
| 66 | data = keyValues(data) | |
| 67 | } | |
| 68 | tb.row(cAge(e.CreatedAt), cText(actor), cText(e.Action), cFlex(data)) | |
| 64 | tb.row(cAge(e.CreatedAt), cText(actor), cText(e.Action), cFlex(e.Data)) | |
| 69 | 65 | } |
| 70 | 66 | tb.flush() |
| 67 | }, func() screen { | |
| 68 | rows := make([]row, len(entries)) | |
| 69 | for i, e := range entries { | |
| 70 | actor := e.Actor | |
| 71 | if actor == "" { | |
| 72 | actor = "-" | |
| 73 | } | |
| 74 | rows[i] = rowOf(cAge(e.CreatedAt), cText(actor), cText(e.Action), cFlex(keyValues(e.Data))) | |
| 75 | } | |
| 76 | return listScreen("Audit", rows, action{"Filter", []string{"audit", "--since", "24h"}}) | |
| 71 | 77 | }) |
| 72 | 78 | } |
| 73 | 79 | |
internal/control/explore.go +15 −1
| @@ -85,12 +85,26 @@ func runExplore(c *Ctx, args []string) int { | ||
| 85 | 85 | } |
| 86 | 86 | } |
| 87 | 87 | ds, next := trimPage(p, ds, "explore", func(o out) string { return o.Path }) |
| 88 | return c.emitPage(p, ds, next, func(w io.Writer) { | |
| 88 | return c.emitPageView(p, ds, next, func(w io.Writer) { | |
| 89 | 89 | tb := c.table(w, "PATH", "DESCRIPTION") |
| 90 | 90 | for _, d := range ds { |
| 91 | 91 | tb.row(cRef(d.Path), cFlex(d.Description)) |
| 92 | 92 | } |
| 93 | 93 | tb.flush() |
| 94 | }, func() screen { | |
| 95 | rows := make([]row, len(ds)) | |
| 96 | for i, d := range ds { | |
| 97 | archived := "" | |
| 98 | if d.Archived { | |
| 99 | archived = "archived" | |
| 100 | } | |
| 101 | rows[i] = rowOf(cLink(d.Path, c.siteURL(d.Path)), cFlex(d.Description), cMeta(strings.Join(d.Topics, ", "), archived)) | |
| 102 | } | |
| 103 | s := listScreen("Explore", rows) | |
| 104 | if len(ds) > 0 { | |
| 105 | s.actions = []action{{"Read", []string{"repo", "show", ds[0].Path}}} | |
| 106 | } | |
| 107 | return s | |
| 94 | 108 | }) |
| 95 | 109 | } |
| 96 | 110 | |
internal/control/release.go +39 −27
| @@ -282,26 +282,7 @@ func runReleaseList(c *Ctx, args []string) int { | ||
| 282 | 282 | for _, r := range rels { |
| 283 | 283 | ds = append(ds, releaseToOut(r, false)) |
| 284 | 284 | } |
| 285 | return c.emitPage(p, ds, next, func(w io.Writer) { | |
| 286 | if c.Term.Cols > 0 { | |
| 287 | tb := c.table(w, "TAG", "TITLE", "ASSETS", "RELEASED") | |
| 288 | for _, d := range ds { | |
| 289 | // "v1.2.0 — the forge speaks first" reads as its | |
| 290 | // subtitle beside the tag. | |
| 291 | title := strings.TrimPrefix(strings.TrimPrefix(d.Title, d.Tag), " — ") | |
| 292 | assets := "" | |
| 293 | switch n := len(d.Assets); n { | |
| 294 | case 0: | |
| 295 | case 1: | |
| 296 | assets = "1 asset" | |
| 297 | default: | |
| 298 | assets = fmt.Sprintf("%d assets", n) | |
| 299 | } | |
| 300 | tb.row(cLink(d.Tag, c.siteURL(repo.Path(), "releases")), cFlex(title), cText(assets), cAge(d.CreatedAt)) | |
| 301 | } | |
| 302 | tb.flush() | |
| 303 | return | |
| 304 | } | |
| 285 | return c.emitPageView(p, ds, next, func(w io.Writer) { | |
| 305 | 286 | tb := c.table(w, "TAG", "TITLE", "ASSETS") |
| 306 | 287 | for _, d := range ds { |
| 307 | 288 | title := d.Title |
| @@ -311,16 +292,37 @@ func runReleaseList(c *Ctx, args []string) int { | ||
| 311 | 292 | tb.row(cRef(d.Tag), cFlex(title), cText(fmt.Sprintf("%d asset(s)", len(d.Assets)))) |
| 312 | 293 | } |
| 313 | 294 | tb.flush() |
| 295 | }, func() screen { | |
| 296 | rows := make([]row, len(ds)) | |
| 297 | for i, d := range ds { | |
| 298 | // "v1.2.0 — the forge speaks first" reads as its subtitle | |
| 299 | // beside the tag. | |
| 300 | title := strings.TrimPrefix(strings.TrimPrefix(d.Title, d.Tag), " — ") | |
| 301 | assets := "" | |
| 302 | switch n := len(d.Assets); n { | |
| 303 | case 0: | |
| 304 | case 1: | |
| 305 | assets = "1 asset" | |
| 306 | default: | |
| 307 | assets = fmt.Sprintf("%d assets", n) | |
| 308 | } | |
| 309 | rows[i] = rowOf(cLink(d.Tag, c.siteURL(repo.Path(), "releases")), cFlex(title), cMeta(assets, relAge(d.CreatedAt, termNow()))) | |
| 310 | } | |
| 311 | s := listScreen("Releases", rows) | |
| 312 | if len(ds) > 0 { | |
| 313 | s.actions = []action{{"Read", []string{"release", "show", repo.Path(), ds[0].Tag}}} | |
| 314 | } | |
| 315 | return s | |
| 314 | 316 | }) |
| 315 | 317 | } |
| 316 | 318 | |
| 317 | 319 | func runReleaseShow(c *Ctx, args []string) int { |
| 318 | _, rel, code := releaseRef(c, args, policy.CanRead) | |
| 320 | repo, rel, code := releaseRef(c, args, policy.CanRead) | |
| 319 | 321 | if code >= 0 { |
| 320 | 322 | return code |
| 321 | 323 | } |
| 322 | 324 | d := releaseToOut(rel, true) |
| 323 | return c.emit(d, func(w io.Writer) { | |
| 325 | return c.emitView(d, func(w io.Writer) { | |
| 324 | 326 | title := d.Title |
| 325 | 327 | if title == d.Tag { |
| 326 | 328 | title = "" |
| @@ -335,14 +337,24 @@ func runReleaseShow(c *Ctx, args []string) int { | ||
| 335 | 337 | v.section("assets") |
| 336 | 338 | tb := c.table(w, "NAME", "SIZE", "SHA256") |
| 337 | 339 | for _, a := range d.Assets { |
| 338 | sum := a.SHA256 | |
| 339 | if c.Term.Cols > 0 { | |
| 340 | sum = sum[:min(12, len(sum))] | |
| 341 | } | |
| 342 | tb.row(cRef(a.Name), cSize(a.Size), cFlex(sum)) | |
| 340 | tb.row(cRef(a.Name), cSize(a.Size), cFlex(a.SHA256)) | |
| 343 | 341 | } |
| 344 | 342 | tb.flush() |
| 345 | 343 | } |
| 344 | }, func() screen { | |
| 345 | assets := section{title: "Assets", n: len(d.Assets)} | |
| 346 | for _, a := range d.Assets { | |
| 347 | assets.rows = append(assets.rows, rowOf(cRef(a.Name), cSize(a.Size), cMeta(a.SHA256[:min(12, len(a.SHA256))]))) | |
| 348 | } | |
| 349 | s := screen{body: d.Notes, format: d.NotesFormat, sections: []section{assets}, fields: []field{ | |
| 350 | {"Release", []cell{cLink(d.Tag, c.siteURL(repo.Path(), "releases")), cText(d.Title)}}, | |
| 351 | {"Author", []cell{cText(d.Author), cAge(d.CreatedAt)}}, | |
| 352 | }} | |
| 353 | if len(d.Assets) > 0 { | |
| 354 | s.actions = append(s.actions, action{"Get", []string{"release", "asset", "get", repo.Path(), d.Tag, d.Assets[0].Name}}) | |
| 355 | } | |
| 356 | s.actions = append(s.actions, action{"Edit", []string{"release", "edit", repo.Path(), d.Tag, "--title", "<title>"}}) | |
| 357 | return s | |
| 346 | 358 | }) |
| 347 | 359 | } |
| 348 | 360 | |
internal/control/snippet.go +32 −2
| @@ -7,6 +7,7 @@ import ( | ||
| 7 | 7 | "fmt" |
| 8 | 8 | "io" |
| 9 | 9 | "strconv" |
| 10 | "strings" | |
| 10 | 11 | "unicode/utf8" |
| 11 | 12 | |
| 12 | 13 | "gitbay.org/gitbay/internal/policy" |
| @@ -222,7 +223,7 @@ func runSnippetShow(c *Ctx, args []string) int { | ||
| 222 | 223 | return c.fail(protocol.ExitFailure, "%v", err) |
| 223 | 224 | } |
| 224 | 225 | sn.Files = files |
| 225 | return c.emit(snippetOut(c, sn), func(w io.Writer) { | |
| 226 | return c.emitView(snippetOut(c, sn), func(w io.Writer) { | |
| 226 | 227 | v := c.view(w) |
| 227 | 228 | v.title(sn.PublicID, sn.Description, sn.Visibility) |
| 228 | 229 | v.fields( |
| @@ -238,6 +239,21 @@ func runSnippetShow(c *Ctx, args []string) int { | ||
| 238 | 239 | } |
| 239 | 240 | tb.flush() |
| 240 | 241 | } |
| 242 | }, func() screen { | |
| 243 | fs := section{title: "Files", n: len(files)} | |
| 244 | for _, f := range files { | |
| 245 | fs.rows = append(fs.rows, rowOf(cRef(f.Name), cSize(int64(f.Size)))) | |
| 246 | } | |
| 247 | s := screen{sections: []section{fs}, fields: []field{ | |
| 248 | {"Snippet", []cell{cLink(sn.PublicID, snippetURL(c, sn)), cText(sn.Description)}}, | |
| 249 | {"Owner", []cell{cText(sn.OwnerName), cState(sn.Visibility)}}, | |
| 250 | {"Updated", []cell{cAge(sn.UpdatedAt)}}, | |
| 251 | }} | |
| 252 | if len(files) > 0 { | |
| 253 | s.actions = append(s.actions, action{"Get", []string{"snippet", "file", "get", sn.PublicID, files[0].Name}}) | |
| 254 | } | |
| 255 | s.actions = append(s.actions, action{"Edit", []string{"snippet", "edit", sn.PublicID, "--description", "<text>"}}) | |
| 256 | return s | |
| 241 | 257 | }) |
| 242 | 258 | } |
| 243 | 259 | |
| @@ -270,7 +286,7 @@ func runSnippetList(c *Ctx, args []string) int { | ||
| 270 | 286 | for _, sn := range rows { |
| 271 | 287 | items = append(items, snippetOut(c, sn)) |
| 272 | 288 | } |
| 273 | return c.emitPage(p, items, next, func(w io.Writer) { | |
| 289 | return c.emitPageView(p, items, next, func(w io.Writer) { | |
| 274 | 290 | tb := c.table(w, "ID", "VISIBILITY", "FILES", "DESCRIPTION") |
| 275 | 291 | for _, sn := range rows { |
| 276 | 292 | names := "" |
| @@ -283,6 +299,20 @@ func runSnippetList(c *Ctx, args []string) int { | ||
| 283 | 299 | tb.row(cRef(sn.PublicID), cState(sn.Visibility), cText(names), cFlex(sn.Description)) |
| 284 | 300 | } |
| 285 | 301 | tb.flush() |
| 302 | }, func() screen { | |
| 303 | rs := make([]row, len(rows)) | |
| 304 | for i, sn := range rows { | |
| 305 | names := make([]string, len(sn.Files)) | |
| 306 | for j, f := range sn.Files { | |
| 307 | names[j] = f.Name | |
| 308 | } | |
| 309 | rs[i] = rowOf(cRef(sn.PublicID), cState(sn.Visibility), cFlex(sn.Description), cMeta(strings.Join(names, ", "), relAge(sn.UpdatedAt, termNow()))) | |
| 310 | } | |
| 311 | s := listScreen("Snippets", rs) | |
| 312 | if len(rows) > 0 { | |
| 313 | s.actions = []action{{"Read", []string{"snippet", "show", rows[0].PublicID}}} | |
| 314 | } | |
| 315 | return s | |
| 286 | 316 | }) |
| 287 | 317 | } |
| 288 | 318 | |
internal/control/stage3repo_test.go +4
| @@ -30,6 +30,8 @@ func dispatchIn(t *testing.T, st *store.Store, u store.User, root, stdin string, | ||
| 30 | 30 | // Fixtures write a lot as user 1; the write limiter is global to |
| 31 | 31 | // the package, so leave its budget to the tests that exercise it. |
| 32 | 32 | c.Cfg.Limits.WriteRate = -1 |
| 33 | c.Cfg.Limits.MaxSnippetBytes = 1 << 20 | |
| 34 | c.Cfg.Limits.MaxBlobBytes = 1 << 20 | |
| 33 | 35 | if code := Dispatch(c, argv); code != protocol.ExitOK { |
| 34 | 36 | t.Fatalf("%v: exit %d: %s", argv, code, errOut) |
| 35 | 37 | } |
| @@ -52,6 +54,8 @@ func atTerminalIn(t *testing.T, st *store.Store, u store.User, root string, argv | ||
| 52 | 54 | // Fixtures write a lot as user 1; the write limiter is global to |
| 53 | 55 | // the package, so leave its budget to the tests that exercise it. |
| 54 | 56 | c.Cfg.Limits.WriteRate = -1 |
| 57 | c.Cfg.Limits.MaxSnippetBytes = 1 << 20 | |
| 58 | c.Cfg.Limits.MaxBlobBytes = 1 << 20 | |
| 55 | 59 | if code := Dispatch(c, argv); code != protocol.ExitOK { |
| 56 | 60 | t.Fatalf("%v: exit %d: %s", argv, code, errOut) |
| 57 | 61 | } |
internal/control/stage3work_test.go +65
| @@ -35,10 +35,13 @@ func workFixture(t *testing.T) (*store.Store, store.Repo, store.User, string, st | ||
| 35 | 35 | } |
| 36 | 36 | os.WriteFile(filepath.Join(src, ".gitbay", "ci.yml"), []byte("jobs:\n test:\n steps:\n - go test ./...\n"), 0o644) |
| 37 | 37 | os.WriteFile(filepath.Join(src, ".gitbay", "issue-template-bug.md"), []byte("## Steps\n"), 0o644) |
| 38 | os.MkdirAll(filepath.Join(src, ".gitbay", "wiki"), 0o755) | |
| 39 | os.WriteFile(filepath.Join(src, ".gitbay", "wiki", "Home.md"), []byte("# Welcome\n\nStart here.\n"), 0o644) | |
| 38 | 40 | git(root, "init", "-q", "-b", "main", "src") |
| 39 | 41 | git(src, "add", ".") |
| 40 | 42 | git(src, "commit", "-q", "-m", "first") |
| 41 | 43 | sha := git(src, "rev-parse", "HEAD") |
| 44 | git(src, "tag", "v1.0.0") | |
| 42 | 45 | dir := RepoDir(root, repo.OwnerName, repo.Name) |
| 43 | 46 | os.MkdirAll(filepath.Dir(dir), 0o755) |
| 44 | 47 | git(root, "clone", "-q", "--bare", src, dir) |
| @@ -101,3 +104,65 @@ func TestWorkScreensA(t *testing.T) { | ||
| 101 | 104 | } |
| 102 | 105 | } |
| 103 | 106 | } |
| 107 | ||
| 108 | func TestWorkPlainPinnedB(t *testing.T) { | |
| 109 | st, repo, u, root, _ := workFixture(t) | |
| 110 | p := repo.Path() | |
| 111 | dispatchIn(t, st, u, root, "", "release", "create", p, "v1.0.0", "--title", "v1.0.0 — first", "--notes", "First release.") | |
| 112 | dispatchIn(t, st, u, root, "package main\n", "snippet", "create", "main.go", "--description", "Hello") | |
| 113 | snippets := dispatchIn(t, st, u, root, "", "snippet", "list") | |
| 114 | id, _, _ := strings.Cut(snippets, "\t") | |
| 115 | for name, argv := range map[string][]string{ | |
| 116 | "release-list": {"release", "list", p}, | |
| 117 | "release-show": {"release", "show", p, "v1.0.0"}, | |
| 118 | "snippet-list": {"snippet", "list"}, | |
| 119 | "snippet-show": {"snippet", "show", id}, | |
| 120 | "wiki-list": {"wiki", "list", p}, | |
| 121 | "wiki-show": {"wiki", "show", p, "Home"}, | |
| 122 | "explore": {"explore"}, | |
| 123 | } { | |
| 124 | got := dispatchIn(t, st, u, root, "", argv...) | |
| 125 | pinPlain(t, name, strings.ReplaceAll(got, id, "<id>")) | |
| 126 | } | |
| 127 | } | |
| 128 | ||
| 129 | func TestWorkScreensB(t *testing.T) { | |
| 130 | st, repo, u, root, _ := workFixture(t) | |
| 131 | p := repo.Path() | |
| 132 | dispatchIn(t, st, u, root, "", "release", "create", p, "v1.0.0", "--title", "v1.0.0 — first", "--notes", "First release.") | |
| 133 | dispatchIn(t, st, u, root, "package main\n", "snippet", "create", "main.go", "--description", "Hello") | |
| 134 | snippets := dispatchIn(t, st, u, root, "", "snippet", "list") | |
| 135 | id, _, _ := strings.Cut(snippets, "\t") | |
| 136 | for _, tc := range []struct { | |
| 137 | argv []string | |
| 138 | want []string | |
| 139 | }{ | |
| 140 | {[]string{"release", "list", p}, []string{"Releases (1)\n", "v1.0.0 first"}}, | |
| 141 | {[]string{"release", "show", p, "v1.0.0"}, []string{"Release:", "v1.0.0 v1.0.0 — first", "First release."}}, | |
| 142 | {[]string{"snippet", "list"}, []string{"Snippets (1)\n", "Hello", "main.go"}}, | |
| 143 | {[]string{"snippet", "show", id}, []string{"Snippet:", "Hello", "Files (1)\nmain.go"}}, | |
| 144 | {[]string{"wiki", "list", p}, []string{"Wiki (1)\nHome", "home"}}, | |
| 145 | {[]string{"wiki", "show", p, "Home"}, []string{"Page:", "Home", "Welcome", "Start here."}}, | |
| 146 | {[]string{"explore"}, []string{"Explore (1)\nalice/app"}}, | |
| 147 | } { | |
| 148 | out := atTerminalIn(t, st, u, root, tc.argv...) | |
| 149 | for _, w := range tc.want { | |
| 150 | if !strings.Contains(out, w) { | |
| 151 | t.Errorf("%v: missing %q in:\n%s", tc.argv, w, out) | |
| 152 | } | |
| 153 | } | |
| 154 | checkLegend(t, out) | |
| 155 | } | |
| 156 | } | |
| 157 | ||
| 158 | func TestAuditScreen(t *testing.T) { | |
| 159 | st, _, uid := newQueueTestRepo(t) | |
| 160 | admin := store.User{ID: uid, Username: "alice", IsAdmin: true} | |
| 161 | dispatchAs(t, st, admin, "", "admin", "user", "create", "bob") | |
| 162 | pinPlain(t, "audit", dispatchAs(t, st, admin, "", "audit")) | |
| 163 | out := atTerminal(t, st, admin, "audit") | |
| 164 | if !strings.Contains(out, "Audit (") || !strings.Contains(out, "alice") { | |
| 165 | t.Errorf("audit:\n%s", out) | |
| 166 | } | |
| 167 | checkLegend(t, out) | |
| 168 | } | |
internal/control/testdata/plain/audit.txt added +2
| @@ -0,0 +1,2 @@ | ||
| 1 | <time> alice cmd admin user create {"argv":["bob"],"source":""} | |
| 2 | <time> alice admin user.created {"user":"bob"} | |
internal/control/testdata/plain/explore.txt added +1
| @@ -0,0 +1 @@ | ||
| 1 | alice/app | |
internal/control/testdata/plain/mr-revisions.txt +1 −1
| @@ -1 +1 @@ | ||
| 1 | * v1 2bc3205597 <time> | |
| 1 | * v1 a59131b356 <time> | |
internal/control/testdata/plain/release-list.txt added +1
| @@ -0,0 +1 @@ | ||
| 1 | v1.0.0 v1.0.0 — first 0 asset(s) | |
internal/control/testdata/plain/release-show.txt added +5
| @@ -0,0 +1,5 @@ | ||
| 1 | v1.0.0 v1.0.0 — first | |
| 2 | ||
| 3 | author alice, <time> | |
| 4 | ||
| 5 | First release. | |
internal/control/testdata/plain/snippet-list.txt added +1
| @@ -0,0 +1 @@ | ||
| 1 | <id> unlisted main.go Hello | |
internal/control/testdata/plain/snippet-show.txt added +8
| @@ -0,0 +1,8 @@ | ||
| 1 | <id> Hello unlisted | |
| 2 | ||
| 3 | author alice | |
| 4 | updated <time> | |
| 5 | url /alice/-/snippets/<id> | |
| 6 | ||
| 7 | files: | |
| 8 | main.go 13 bytes | |
internal/control/testdata/plain/status-list.txt +1 −1
| @@ -1,2 +1,2 @@ | ||
| 1 | combined success 2bc3205597 | |
| 1 | combined success a59131b356 | |
| 2 | 2 | ext/test success all green |
internal/control/testdata/plain/wiki-list.txt added +1
| @@ -0,0 +1 @@ | ||
| 1 | Home | |
internal/control/testdata/plain/wiki-show.txt added +3
| @@ -0,0 +1,3 @@ | ||
| 1 | # Welcome | |
| 2 | ||
| 3 | Start here. | |
internal/control/wiki.go +26 −19
| @@ -98,12 +98,26 @@ func runWikiList(c *Ctx, args []string) int { | ||
| 98 | 98 | if d.Pages == nil { |
| 99 | 99 | d.Pages = []string{} |
| 100 | 100 | } |
| 101 | return c.emit(d, func(w io.Writer) { | |
| 101 | return c.emitView(d, func(w io.Writer) { | |
| 102 | 102 | tb := c.table(w, "PAGE") |
| 103 | 103 | for _, p := range d.Pages { |
| 104 | 104 | tb.row(cRef(p)) |
| 105 | 105 | } |
| 106 | 106 | tb.flush() |
| 107 | }, func() screen { | |
| 108 | rows := make([]row, len(d.Pages)) | |
| 109 | for i, pg := range d.Pages { | |
| 110 | home := "" | |
| 111 | if pg == d.Home { | |
| 112 | home = "home" | |
| 113 | } | |
| 114 | rows[i] = rowOf(cLink(pg, c.siteURL(repo.Path(), "wiki", pg)), cMeta(home)) | |
| 115 | } | |
| 116 | s := listScreen("Wiki", rows) | |
| 117 | if d.Home != "" { | |
| 118 | s.actions = []action{{"Read", []string{"wiki", "show", repo.Path(), d.Home}}} | |
| 119 | } | |
| 120 | return s | |
| 107 | 121 | }) |
| 108 | 122 | } |
| 109 | 123 | |
| @@ -170,27 +184,20 @@ func runWikiShow(c *Ctx, args []string) int { | ||
| 170 | 184 | if ext == ".org" { |
| 171 | 185 | format = "org" |
| 172 | 186 | } |
| 173 | return c.emit(d, func(w io.Writer) { | |
| 187 | return c.emitView(d, func(w io.Writer) { | |
| 174 | 188 | // Plain: the page source verbatim, same as any other piped |
| 175 | // file read. The title/fields/body layout is terminal-only. | |
| 176 | if c.Term.Cols == 0 { | |
| 177 | fmt.Fprint(w, d.Content) | |
| 178 | return | |
| 179 | } | |
| 180 | v := c.view(w) | |
| 181 | v.title(repo.Path(), page, "") | |
| 182 | binaryNote := "" | |
| 189 | // file read. | |
| 190 | fmt.Fprint(w, d.Content) | |
| 191 | }, func() screen { | |
| 192 | s := screen{fields: []field{ | |
| 193 | {"Page", []cell{cLink(page, c.siteURL(repo.Path(), "wiki", page)), cMeta(repo.Path(), d.File)}}, | |
| 194 | }, actions: []action{{"Read", []string{"wiki", "list", repo.Path()}}}} | |
| 183 | 195 | if binary { |
| 184 | binaryNote = fmt.Sprintf("%d bytes, binary", d.Size) | |
| 185 | } | |
| 186 | v.fields( | |
| 187 | "file", d.File, | |
| 188 | "binary", binaryNote, | |
| 189 | "url", c.siteURL(repo.Path(), "wiki", page), | |
| 190 | ) | |
| 191 | if !binary { | |
| 192 | v.body(d.Content, format) | |
| 196 | s.fields = append(s.fields, field{"Binary", []cell{cSize(int64(d.Size))}}) | |
| 197 | } else { | |
| 198 | s.body, s.format = d.Content, format | |
| 193 | 199 | } |
| 200 | return s | |
| 194 | 201 | }) |
| 195 | 202 | } |
| 196 | 203 | return c.fail(protocol.ExitNotFound, "no wiki page %q in %s", page, repo.Path()) |