+43
-2
appview/pages/pages.go
+43
-2
appview/pages/pages.go
···
1
1
package pages
2
2
3
3
import (
4
+
"bytes"
4
5
"embed"
5
6
"fmt"
7
+
"html"
6
8
"html/template"
7
9
"io"
8
10
"io/fs"
9
11
"log"
10
12
"net/http"
11
13
"path"
14
+
"path/filepath"
12
15
"strings"
13
16
17
+
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
18
+
"github.com/alecthomas/chroma/v2/lexers"
19
+
"github.com/alecthomas/chroma/v2/styles"
14
20
"github.com/dustin/go-humanize"
15
21
"github.com/sotangled/tangled/appview/auth"
16
22
"github.com/sotangled/tangled/appview/db"
···
78
84
"splitN": func(s, sep string, n int) []string {
79
85
return strings.SplitN(s, sep, n)
80
86
},
81
-
"escapeHtml": func(s string) string {
82
-
return template.HTMLEscapeString(s)
87
+
"escapeHtml": func(s string) template.HTML {
88
+
if s == "" {
89
+
return template.HTML("<br>")
90
+
}
91
+
return template.HTML(s)
92
+
},
93
+
"unescapeHtml": func(s string) string {
94
+
return html.UnescapeString(s)
83
95
},
84
96
"nl2br": func(text string) template.HTML {
85
97
return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "<br>", -1))
···
351
363
}
352
364
353
365
func (p *Pages) RepoBlob(w io.Writer, params RepoBlobParams) error {
366
+
if params.Lines < 5000 {
367
+
c := params.Contents
368
+
style := styles.Get("xcode")
369
+
formatter := chromahtml.New(
370
+
chromahtml.InlineCode(true),
371
+
chromahtml.WithLineNumbers(true),
372
+
chromahtml.WithLinkableLineNumbers(true, "L"),
373
+
chromahtml.Standalone(false),
374
+
)
375
+
376
+
lexer := lexers.Get(filepath.Base(params.Path))
377
+
if lexer == nil {
378
+
lexer = lexers.Fallback
379
+
}
380
+
381
+
iterator, err := lexer.Tokenise(nil, c)
382
+
if err != nil {
383
+
return fmt.Errorf("chroma tokenize: %w", err)
384
+
}
385
+
386
+
var code bytes.Buffer
387
+
err = formatter.Format(&code, style, iterator)
388
+
if err != nil {
389
+
return fmt.Errorf("chroma format: %w", err)
390
+
}
391
+
392
+
params.Contents = code.String()
393
+
}
394
+
354
395
params.Active = "overview"
355
396
return p.executeRepo("repo/blob", w, params)
356
397
}
+47
-32
appview/pages/templates/repo/blob.html
+47
-32
appview/pages/templates/repo/blob.html
···
1
1
{{ define "repoContent" }}
2
-
{{ $lines := split .Contents }}
3
-
{{ $tot_lines := len $lines }}
4
-
{{ $tot_chars := len (printf "%d" $tot_lines) }}
5
-
{{ $code_number_style := "text-gray-400 left-0 bg-white text-right mr-2 select-none" }}
6
-
{{ $linkstyle := "no-underline hover:underline" }}
7
-
<div class="pb-2 text-base">
8
-
<div class="flex justify-between">
9
-
<div id="breadcrumbs">
10
-
{{ range $idx, $value := .BreadCrumbs }}
11
-
{{ if ne $idx (sub (len $.BreadCrumbs) 1) }}
12
-
<a href="{{ index . 1}}" class="text-bold text-gray-500 {{ $linkstyle }}">{{ index . 0 }}</a> /
13
-
{{ else }}
14
-
<span class="text-bold text-gray-500">{{ index . 0 }}</span>
15
-
{{ end }}
16
-
{{ end }}
2
+
{{ $lines := split .Contents }}
3
+
{{ $tot_lines := len $lines }}
4
+
{{ $tot_chars := len (printf "%d" $tot_lines) }}
5
+
{{ $code_number_style := "text-gray-400 left-0 bg-white text-right mr-6 select-none" }}
6
+
{{ $linkstyle := "no-underline hover:underline" }}
7
+
<div class="pb-2 text-base">
8
+
<div class="flex justify-between">
9
+
<div id="breadcrumbs">
10
+
{{ range $idx, $value := .BreadCrumbs }}
11
+
{{ if ne $idx (sub (len $.BreadCrumbs) 1) }}
12
+
<a
13
+
href="{{ index . 1 }}"
14
+
class="text-bold text-gray-500 {{ $linkstyle }}"
15
+
>{{ index . 0 }}</a
16
+
>
17
+
/
18
+
{{ else }}
19
+
<span class="text-bold text-gray-500"
20
+
>{{ index . 0 }}</span
21
+
>
22
+
{{ end }}
23
+
{{ end }}
24
+
</div>
25
+
<div id="file-info">
26
+
{{ .Lines }} lines
27
+
<span class="select-none px-2 [&:before]:content-['·']"></span>
28
+
{{ byteFmt .SizeHint }}
29
+
</div>
30
+
</div>
17
31
</div>
18
-
<div id="file-info">
19
-
{{ .Lines }} lines
20
-
<span class="select-none px-2 [&:before]:content-['·']"></span>
21
-
{{ byteFmt .SizeHint }}
22
-
</div>
23
-
</div>
24
-
</div>
25
-
26
-
{{ if eq .Lines 0 }}
27
-
<p class="text-center text-gray-400">This file is empty.</p>
28
-
{{ else }}
29
-
30
-
{{ if .IsBinary }}<p class="text-center text-gray-400">This is a binary file and will not be displayed.</p>{{ else }}
31
-
<pre class="font-mono text-sm overflow-auto relative text-ellipsis"><code>{{ range $idx, $line := $lines }}<span class="flex">
32
-
<span class="{{ $code_number_style }}" style="min-width: {{$tot_chars}}ch;">{{ add $idx 1 }}</span>
33
-
<span class="whitespace-pre">{{ escapeHtml $line }}</span></span>{{ else }}<em class="text-gray-400">this file is empty</em>{{ end }}</code></pre>{{ end}}
32
+
{{ if .IsBinary }}
33
+
<p class="text-center text-gray-400">
34
+
This is a binary file and will not be displayed.
35
+
</p>
36
+
{{ else }}
37
+
<div class="overflow-auto relative text-ellipsis">
38
+
{{ range $idx, $line := $lines }}
39
+
<div class="flex">
40
+
<span
41
+
class="{{ $code_number_style }}"
42
+
style="min-width: {{ $tot_chars }}ch;"
43
+
>{{ add $idx 1 }}</span
44
+
>
45
+
<div class="whitespace-pre">{{ $line | escapeHtml }}</div>
46
+
</div>
47
+
{{ end }}
48
+
</div>
49
+
{{ end }}
34
50
{{ end }}
35
-
{{ end }}
+2
go.mod
+2
go.mod
···
30
30
github.com/Microsoft/go-winio v0.6.2 // indirect
31
31
github.com/ProtonMail/go-crypto v1.0.0 // indirect
32
32
github.com/acomagu/bufpipe v1.0.4 // indirect
33
+
github.com/alecthomas/chroma/v2 v2.15.0 // indirect
33
34
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
34
35
github.com/aymerick/douceur v0.2.0 // indirect
35
36
github.com/beorn7/perks v1.0.1 // indirect
···
39
40
github.com/cespare/xxhash/v2 v2.3.0 // indirect
40
41
github.com/cloudflare/circl v1.4.0 // indirect
41
42
github.com/davecgh/go-spew v1.1.1 // indirect
43
+
github.com/dlclark/regexp2 v1.11.5 // indirect
42
44
github.com/emirpasic/gods v1.18.1 // indirect
43
45
github.com/felixge/httpsnoop v1.0.4 // indirect
44
46
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
+4
go.sum
+4
go.sum
···
9
9
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
10
10
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
11
11
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
12
+
github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
13
+
github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
12
14
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
13
15
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
14
16
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
···
54
56
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
55
57
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
56
58
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
59
+
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
60
+
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
57
61
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
58
62
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
59
63
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+2
-2
knotserver/routes.go
+2
-2
knotserver/routes.go
···
216
216
}
217
217
218
218
bytes := []byte(contents)
219
-
safe := string(sanitize(bytes))
219
+
// safe := string(sanitize(bytes))
220
220
sizeHint := len(bytes)
221
221
222
222
resp := types.RepoBlobResponse{
223
223
Ref: ref,
224
-
Contents: string(safe),
224
+
Contents: string(bytes),
225
225
Path: treePath,
226
226
IsBinary: isBinaryFile,
227
227
SizeHint: uint64(sizeHint),