+107
appview/pages/funcMap.go
+107
appview/pages/funcMap.go
···
1
+
package pages
2
+
3
+
import (
4
+
"fmt"
5
+
"html"
6
+
"html/template"
7
+
"reflect"
8
+
"strings"
9
+
10
+
"github.com/dustin/go-humanize"
11
+
)
12
+
13
+
func funcMap() template.FuncMap {
14
+
return template.FuncMap{
15
+
"split": func(s string) []string {
16
+
return strings.Split(s, "\n")
17
+
},
18
+
"splitOn": func(s, sep string) []string {
19
+
return strings.Split(s, sep)
20
+
},
21
+
"add": func(a, b int) int {
22
+
return a + b
23
+
},
24
+
"sub": func(a, b int) int {
25
+
return a - b
26
+
},
27
+
"cond": func(cond interface{}, a, b string) string {
28
+
if cond == nil {
29
+
return b
30
+
}
31
+
32
+
if boolean, ok := cond.(bool); boolean && ok {
33
+
return a
34
+
}
35
+
36
+
return b
37
+
},
38
+
"didOrHandle": func(did, handle string) string {
39
+
if handle != "" {
40
+
return fmt.Sprintf("@%s", handle)
41
+
} else {
42
+
return did
43
+
}
44
+
},
45
+
"assoc": func(values ...string) ([][]string, error) {
46
+
if len(values)%2 != 0 {
47
+
return nil, fmt.Errorf("invalid assoc call, must have an even number of arguments")
48
+
}
49
+
pairs := make([][]string, 0)
50
+
for i := 0; i < len(values); i += 2 {
51
+
pairs = append(pairs, []string{values[i], values[i+1]})
52
+
}
53
+
return pairs, nil
54
+
},
55
+
"append": func(s []string, values ...string) []string {
56
+
s = append(s, values...)
57
+
return s
58
+
},
59
+
"timeFmt": humanize.Time,
60
+
"byteFmt": humanize.Bytes,
61
+
"length": func(slice interface{}) int {
62
+
v := reflect.ValueOf(slice)
63
+
if v.Kind() == reflect.Slice || v.Kind() == reflect.Array {
64
+
return v.Len()
65
+
}
66
+
return 0
67
+
},
68
+
"splitN": func(s, sep string, n int) []string {
69
+
return strings.SplitN(s, sep, n)
70
+
},
71
+
"escapeHtml": func(s string) template.HTML {
72
+
if s == "" {
73
+
return template.HTML("<br>")
74
+
}
75
+
return template.HTML(s)
76
+
},
77
+
"unescapeHtml": func(s string) string {
78
+
return html.UnescapeString(s)
79
+
},
80
+
"nl2br": func(text string) template.HTML {
81
+
return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "<br>", -1))
82
+
},
83
+
"unwrapText": func(text string) string {
84
+
paragraphs := strings.Split(text, "\n\n")
85
+
86
+
for i, p := range paragraphs {
87
+
lines := strings.Split(p, "\n")
88
+
paragraphs[i] = strings.Join(lines, " ")
89
+
}
90
+
91
+
return strings.Join(paragraphs, "\n\n")
92
+
},
93
+
"sequence": func(n int) []struct{} {
94
+
return make([]struct{}, n)
95
+
},
96
+
"subslice": func(slice interface{}, start, end int) interface{} {
97
+
v := reflect.ValueOf(slice)
98
+
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
99
+
return nil
100
+
}
101
+
if start < 0 || start > v.Len() || end > v.Len() || start > end {
102
+
return nil
103
+
}
104
+
return v.Slice(start, end).Interface()
105
+
},
106
+
}
107
+
}
-84
appview/pages/pages.go
-84
appview/pages/pages.go
···
4
4
"bytes"
5
5
"embed"
6
6
"fmt"
7
-
"html"
8
7
"html/template"
9
8
"io"
10
9
"io/fs"
···
18
17
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
19
18
"github.com/alecthomas/chroma/v2/lexers"
20
19
"github.com/alecthomas/chroma/v2/styles"
21
-
"github.com/dustin/go-humanize"
22
20
"github.com/sotangled/tangled/appview/auth"
23
21
"github.com/sotangled/tangled/appview/db"
24
22
"github.com/sotangled/tangled/types"
···
29
27
30
28
type Pages struct {
31
29
t map[string]*template.Template
32
-
}
33
-
34
-
func funcMap() template.FuncMap {
35
-
return template.FuncMap{
36
-
"split": func(s string) []string {
37
-
return strings.Split(s, "\n")
38
-
},
39
-
"splitOn": func(s, sep string) []string {
40
-
return strings.Split(s, sep)
41
-
},
42
-
"add": func(a, b int) int {
43
-
return a + b
44
-
},
45
-
"sub": func(a, b int) int {
46
-
return a - b
47
-
},
48
-
"cond": func(cond interface{}, a, b string) string {
49
-
if cond == nil {
50
-
return b
51
-
}
52
-
53
-
if boolean, ok := cond.(bool); boolean && ok {
54
-
return a
55
-
}
56
-
57
-
return b
58
-
},
59
-
"didOrHandle": func(did, handle string) string {
60
-
if handle != "" {
61
-
return fmt.Sprintf("@%s", handle)
62
-
} else {
63
-
return did
64
-
}
65
-
},
66
-
"assoc": func(values ...string) ([][]string, error) {
67
-
if len(values)%2 != 0 {
68
-
return nil, fmt.Errorf("invalid assoc call, must have an even number of arguments")
69
-
}
70
-
pairs := make([][]string, 0)
71
-
for i := 0; i < len(values); i += 2 {
72
-
pairs = append(pairs, []string{values[i], values[i+1]})
73
-
}
74
-
return pairs, nil
75
-
},
76
-
"append": func(s []string, values ...string) []string {
77
-
s = append(s, values...)
78
-
return s
79
-
},
80
-
"timeFmt": humanize.Time,
81
-
"byteFmt": humanize.Bytes,
82
-
"length": func(v []string) int {
83
-
return len(v)
84
-
},
85
-
"splitN": func(s, sep string, n int) []string {
86
-
return strings.SplitN(s, sep, n)
87
-
},
88
-
"escapeHtml": func(s string) template.HTML {
89
-
if s == "" {
90
-
return template.HTML("<br>")
91
-
}
92
-
return template.HTML(s)
93
-
},
94
-
"unescapeHtml": func(s string) string {
95
-
return html.UnescapeString(s)
96
-
},
97
-
"nl2br": func(text string) template.HTML {
98
-
return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "<br>", -1))
99
-
},
100
-
"unwrapText": func(text string) string {
101
-
paragraphs := strings.Split(text, "\n\n")
102
-
103
-
for i, p := range paragraphs {
104
-
lines := strings.Split(p, "\n")
105
-
paragraphs[i] = strings.Join(lines, " ")
106
-
}
107
-
108
-
return strings.Join(paragraphs, "\n\n")
109
-
},
110
-
"sequence": func(n int) []struct{} {
111
-
return make([]struct{}, n)
112
-
},
113
-
}
114
30
}
115
31
116
32
func NewPages() *Pages {
+2
-2
appview/pages/templates/layouts/repobase.html
+2
-2
appview/pages/templates/layouts/repobase.html
···
2
2
3
3
{{ define "content" }}
4
4
<section id="repo-header" class="mb-4 p-2">
5
-
<p class="text-lg font-bold">
5
+
<p class="text-lg">
6
6
<a href="/{{ .RepoInfo.OwnerWithAt }}">{{ .RepoInfo.OwnerWithAt }}</a>
7
7
<span class="select-none">/</span>
8
-
<a href="/{{ .RepoInfo.FullName }}">{{ .RepoInfo.Name }}</a>
8
+
<a href="/{{ .RepoInfo.FullName }}" class="font-bold">{{ .RepoInfo.Name }}</a>
9
9
</p>
10
10
<span>
11
11
{{ if .RepoInfo.Description }}
+58
-64
appview/pages/templates/repo/index.html
+58
-64
appview/pages/templates/repo/index.html
···
46
46
</a>
47
47
</div>
48
48
49
-
<div class="flex gap-4">
49
+
<div class="flex gap-2">
50
50
<div id="file-tree" class="w-3/5 pr-2 border-r border-gray-200">
51
51
{{ $containerstyle := "py-1" }}
52
52
{{ $linkstyle := "no-underline hover:underline" }}
···
104
104
105
105
<div id="commit-log" class="flex-1">
106
106
{{ range .Commits }}
107
-
<div class="flex flex-row items-center">
108
-
<i
109
-
class="w-5 h-5 text-gray-400 align-top"
110
-
data-lucide="git-commit-horizontal"
111
-
></i>
112
-
<div class="relative px-4 py-4">
113
-
<div id="commit-message">
114
-
{{ $messageParts := splitN .Message "\n\n" 2 }}
115
-
<div class="text-base cursor-pointer">
116
-
<div>
117
-
<div>
118
-
<a
119
-
href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}"
120
-
class="inline no-underline hover:underline"
121
-
>{{ index $messageParts 0 }}</a
122
-
>
123
-
{{ if gt (len $messageParts) 1 }}
107
+
<div class="relative px-2 pb-8">
108
+
<div id="commit-message">
109
+
{{ $messageParts := splitN .Message "\n\n" 2 }}
110
+
<div class="text-base cursor-pointer">
111
+
<div>
112
+
<div>
113
+
<a
114
+
href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}"
115
+
class="inline no-underline hover:underline"
116
+
>{{ index $messageParts 0 }}</a
117
+
>
118
+
{{ if gt (len $messageParts) 1 }}
124
119
125
-
<button
126
-
class="py-1/2 px-1 bg-gray-200 hover:bg-gray-400 rounded"
127
-
hx-on:click="this.parentElement.nextElementSibling.classList.toggle('hidden')"
128
-
>
129
-
<i
130
-
class="w-3 h-3"
131
-
data-lucide="ellipsis"
132
-
></i>
133
-
</button>
134
-
{{ end }}
135
-
</div>
136
-
{{ if gt (len $messageParts) 1 }}
137
-
<p
138
-
class="hidden mt-1 text-sm cursor-text pb-2"
139
-
>
140
-
{{ nl2br (unwrapText (index $messageParts 1)) }}
141
-
</p>
142
-
{{ end }}
143
-
</div>
144
-
</div>
145
-
</div>
120
+
<button
121
+
class="py-1/2 px-1 bg-gray-200 hover:bg-gray-400 rounded"
122
+
hx-on:click="this.parentElement.nextElementSibling.classList.toggle('hidden')"
123
+
>
124
+
<i
125
+
class="w-3 h-3"
126
+
data-lucide="ellipsis"
127
+
></i>
128
+
</button>
129
+
{{ end }}
130
+
</div>
131
+
{{ if gt (len $messageParts) 1 }}
132
+
<p
133
+
class="hidden mt-1 text-sm cursor-text pb-2"
134
+
>
135
+
{{ nl2br (unwrapText (index $messageParts 1)) }}
136
+
</p>
137
+
{{ end }}
138
+
</div>
139
+
</div>
140
+
</div>
146
141
147
-
<div class="text-xs text-gray-500">
148
-
<span class="font-mono">
149
-
<a
150
-
href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}"
151
-
class="text-gray-500 no-underline hover:underline"
152
-
>{{ slice .Hash.String 0 8 }}</a
153
-
>
154
-
</span>
155
-
<span
156
-
class="mx-2 before:content-['·'] before:select-none"
157
-
></span>
158
-
<span>
159
-
<a
160
-
href="mailto:{{ .Author.Email }}"
161
-
class="text-gray-500 no-underline hover:underline"
162
-
>{{ .Author.Name }}</a
163
-
>
164
-
</span>
165
-
<div
166
-
class="inline-block px-1 select-none after:content-['·']"
167
-
></div>
168
-
<span>{{ timeFmt .Author.When }}</span>
169
-
</div>
170
-
</div>
171
-
</div>
142
+
<div class="text-xs text-gray-500">
143
+
<span class="font-mono">
144
+
<a
145
+
href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}"
146
+
class="text-gray-500 no-underline hover:underline"
147
+
>{{ slice .Hash.String 0 8 }}</a
148
+
>
149
+
</span>
150
+
<span
151
+
class="mx-2 before:content-['·'] before:select-none"
152
+
></span>
153
+
<span>
154
+
<a
155
+
href="mailto:{{ .Author.Email }}"
156
+
class="text-gray-500 no-underline hover:underline"
157
+
>{{ .Author.Name }}</a
158
+
>
159
+
</span>
160
+
<div
161
+
class="inline-block px-1 select-none after:content-['·']"
162
+
></div>
163
+
<span>{{ timeFmt .Author.When }}</span>
164
+
</div>
165
+
</div>
172
166
{{ end }}
173
167
</div>
174
168
</div>
+5
-3
appview/pages/templates/repo/log.html
+5
-3
appview/pages/templates/repo/log.html
···
41
41
<main>
42
42
<div id="commit-log" class="flex-1 relative">
43
43
<div class="absolute left-8 top-0 bottom-0 w-px bg-gray-300"></div>
44
-
{{ range .Commits }}
44
+
{{ $end := length .Commits }}
45
+
{{ $commits := subslice .Commits 1 $end }}
46
+
{{ range $commits }}
45
47
<div class="flex flex-row justify-between items-center">
46
48
<div
47
49
class="relative w-full px-4 py-4 mt-5 hover:bg-gray-50 rounded-sm bg-white"
···
112
114
<div class="flex justify-end mt-4 gap-2">
113
115
{{ if gt .Page 1 }}
114
116
<a
115
-
class="btn flex items-center gap-2 no-underline"
117
+
class="btn flex items-center gap-2 no-underline hover:no-underline"
116
118
hx-boost="true"
117
119
onclick="window.location.href = window.location.pathname + '?page={{ sub .Page 1 }}'"
118
120
>
···
125
127
126
128
{{ if eq $commits_len 30 }}
127
129
<a
128
-
class="btn flex items-center gap-2 no-underline"
130
+
class="btn flex items-center gap-2 no-underline hover:no-underline"
129
131
hx-boost="true"
130
132
onclick="window.location.href = window.location.pathname + '?page={{ add .Page 1 }}'"
131
133
>
+3
-3
appview/pages/templates/user/profile.html
+3
-3
appview/pages/templates/user/profile.html
···
1
1
{{ define "title" }}{{ or .UserHandle .UserDid }}{{ end }}
2
2
3
3
{{ define "content" }}
4
-
<div class="flex ">
4
+
<div class="flex">
5
5
<h1 class="pb-1">
6
6
{{ didOrHandle .UserDid .UserHandle }}
7
7
</h1>
···
26
26
<div class="inline-block px-1 select-none after:content-['·']"></div>
27
27
<span>{{ .ProfileStats.Following }} following</span>
28
28
</div>
29
-
<p class="text-xs font-bold py-2">REPOS</p>
29
+
<p class="text-sm font-bold py-2">REPOS</p>
30
30
<div id="repos" class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
31
31
{{ range .Repos }}
32
32
<div
···
49
49
<p>This user does not have any repos yet.</p>
50
50
{{ end }}
51
51
</div>
52
-
<p class="text-xs font-bold py-2">COLLABORATING ON</p>
52
+
<p class="text-sm font-bold py-2">COLLABORATING ON</p>
53
53
<div id="collaborating" class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
54
54
{{ range .CollaboratingRepos }}
55
55
<div
-3
appview/state/middleware.go
-3
appview/state/middleware.go
···
147
147
func ResolveIdent(s *State) Middleware {
148
148
return func(next http.Handler) http.Handler {
149
149
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
150
-
start := time.Now()
151
150
didOrHandle := chi.URLParam(req, "user")
152
151
153
152
id, err := s.resolver.ResolveIdent(req.Context(), didOrHandle)
···
160
159
161
160
ctx := context.WithValue(req.Context(), "resolvedId", *id)
162
161
163
-
elapsed := time.Since(start)
164
-
log.Println("Execution time:", elapsed)
165
162
next.ServeHTTP(w, req.WithContext(ctx))
166
163
})
167
164
}
+2
appview/state/repo.go
+2
appview/state/repo.go
···
207
207
baseTreeLink := path.Join(f.OwnerDid(), f.RepoName, "tree", ref, treePath)
208
208
baseBlobLink := path.Join(f.OwnerDid(), f.RepoName, "blob", ref, treePath)
209
209
210
+
log.Println(result)
211
+
210
212
s.pages.RepoTree(w, pages.RepoTreeParams{
211
213
LoggedInUser: user,
212
214
BreadCrumbs: breadcrumbs,