-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtemplates.go
333 lines (290 loc) · 8.95 KB
/
templates.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
package helpers
import (
"encoding/json"
"fmt"
"html/template"
"net/url"
"regexp"
"sort"
"strings"
"time"
"github.com/APTrust/registry/common"
"github.com/APTrust/registry/constants"
"github.com/APTrust/registry/pgmodels"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
// https://curtisvermeeren.github.io/2017/09/14/Golang-Templates-Cheatsheet
// -------------------------------------------------------------------------
// Helper functions to be used inside of templates
//
// These are loaded in t2m.go (main) by r.SetFuncMap()
// -------------------------------------------------------------------------
// Define this here so it's not recompiled on every call to LinkifyUrls.
var reUrl = regexp.MustCompile(`((https?://)[^\s]+)`)
var printFormatter *message.Printer
func init() {
printFormatter = message.NewPrinter(language.English)
}
// Truncate truncates the value to the given length, appending
// an ellipses to the end. If value contains HTML tags, they
// will be stripped because truncating HTML can result in unclosed
// tags that will ruin the page layout.
func Truncate(value string, length int) string {
if len(value) < length {
return value
}
fmtStr := fmt.Sprintf("%%.%ds...", length)
return fmt.Sprintf(fmtStr, value)
}
// DateUS returns a date in format "Jan 2, 2006"
func DateUS(date time.Time) string {
if date.IsZero() {
return ""
}
return date.Format("Jan _2, 2006")
}
// DateUS returns a date in format "Jan 2, 2006 15:04:05"
func DateTimeUS(date time.Time) string {
if date.IsZero() {
return ""
}
return date.Format("Jan _2, 2006 15:04:05")
}
// DateISO returns a date in format "2006-01-02"
func DateISO(date time.Time) string {
if date.IsZero() {
return ""
}
return date.Format("2006-01-02")
}
// DateTimeISO returns a date in format "2006-01-02T15:04:05Z"
func DateTimeISO(date time.Time) string {
if date.IsZero() {
return ""
}
return date.Format(time.RFC3339)
}
// UnixToISO converts a Unix timestamp to ISO format.
func UnixToISO(ts int64) string {
return time.Unix(ts, 0).Format(time.RFC3339)
}
// RoleName transforms ugly DB role names into more readable ones.
func RoleName(role string) string {
switch role {
case "admin":
return "SysAdmin"
case "institutional_admin":
return "Admin"
case "institutional_user":
return "User"
default:
return role
}
}
// YesNo returns "Yes" if value is true, "No" if value is false.
func YesNo(value bool) string {
if value {
return "Yes"
}
return "No"
}
// StrEq compares the string representation of two values and returns
// true if they are equal.
func StrEq(val1, val2 interface{}) bool {
str1 := fmt.Sprintf("%v", val1)
str2 := fmt.Sprintf("%v", val2)
return str1 == str2
}
// EscapeAttr escapes an HTML attribute value.
// This helps avoid the ZgotmplZ problem.
func EscapeAttr(s string) template.HTMLAttr {
return template.HTMLAttr(s)
}
// EscapeHTML returns an escaped HTML string.
// This helps avoid the ZgotmplZ problem.
func EscapeHTML(s string) template.HTML {
return template.HTML(s)
}
// UserCan returns true if the user has the specified permission.
func UserCan(user *pgmodels.User, permission constants.Permission, instID int64) bool {
return user.HasPermission(permission, instID)
}
// HumanSize returns a number of bytes in a human-readable format.
// Note that we use base 1024, not base 1000, because AWS uses 1024
// to calculate the storage size of the objects we're reporting on.
func HumanSize(size int64) string {
return common.ToHumanSize(size, 1024)
}
// IconFor returns a FontAwesome icon for the specified string, as defined
// in helpers.IconMap. If the IconMap has no entry for the string, this
// returns helpers.IconMissing.
func IconFor(str string) template.HTML {
icon := IconMap[str]
if icon == "" {
icon = IconMissing
}
return template.HTML(icon)
}
// BadgeClass returns the css class for the specified string, where
// string is a work item status or other value defined in Constants.
func BadgeClass(str string) template.HTML {
return template.HTML(BadgeClassMap[str])
}
// TruncateStart trims str to maxLen by removing them from the
// middle of the string. It adds dots to the middle of the string
// to indicate text was trimmed.
func TruncateMiddle(str string, maxLen int) string {
if len(str) <= maxLen {
return str
}
half := (maxLen - 3) / 2
end := len(str) - half
return str[0:half] + "..." + str[end:len(str)]
}
// TruncateStart trims str to maxLen by removing them from the
// start of the string. It adds leading dots to indicate some
// text was trimmed.
func TruncateStart(str string, maxLen int) string {
if len(str) <= maxLen {
return str
}
end := (len(str) - 3) - maxLen
if end < 0 {
end = 0
}
return "..." + str[end:len(str)]
}
// Dict returns an interface map suitable for passing into
// sub templates.
func Dict(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, common.ErrInvalidParam
}
dict := make(map[string]interface{}, len(values)/2)
for i := 0; i < len(values); i += 2 {
key, ok := values[i].(string)
if !ok {
return nil, common.ErrWrongDataType
}
dict[key] = values[i+1]
}
return dict, nil
}
// DefaultString returns value if it's non-empty.
// Otherwise, it returns _default.
func DefaultString(value, _default string) string {
if len(strings.TrimSpace(value)) > 0 {
return value
}
return _default
}
// FormatFloat formats a floating point number to have scale
// digits after the decimal point.
func FormatFloat(value float64, scale int) string {
fmtString := fmt.Sprintf("%%.%df", scale)
return printFormatter.Sprintf(fmtString, value)
}
// FormatInt formats an integer value to include commas.
// 28635177 becomes 28,635,177
func FormatInt(value int) string {
return printFormatter.Sprintf("%d", value)
}
// FormatInt64 formats a 64-bit integer value to include commas.
// 28635177 becomes 28,635,177
func FormatInt64(value int64) string {
return printFormatter.Sprintf("%d", value)
}
// ToJSON converts an interface to JSON.
func ToJSON(v interface{}) template.JS {
jsonString, _ := json.Marshal(v)
return template.JS(jsonString)
}
// SortUrl returns the url to sort results by the specified column.
// This is used in table column headers on index pages.
// Note that this returns a URL path and query string only. There's
// no hostname, port, or scheme.
func SortUrl(currentUrl *url.URL, colName string) string {
newSort := fmt.Sprintf("%s__asc", colName)
vals := currentUrl.Query()
currentSort := vals.Get("sort")
if currentSort == fmt.Sprintf("%s__asc", colName) {
newSort = fmt.Sprintf("%s__desc", colName)
}
vals.Set("sort", newSort)
return fmt.Sprintf("%s?%s", currentUrl.Path, vals.Encode())
}
// SortIcon returns the name of the sort icon to display at the
// top of a table column. This will be either "keyboard_arrow_up"
// or "keyboard_arrow_down"
func SortIcon(currentUrl *url.URL, colName string) string {
vals := currentUrl.Query()
currentSort := vals.Get("sort")
icon := ""
if currentSort == fmt.Sprintf("%s__desc", colName) {
icon = "keyboard_arrow_down"
} else if currentSort == fmt.Sprintf("%s__asc", colName) {
icon = "keyboard_arrow_up"
}
return icon
}
// LinkifyUrls converts urls in text to clickable links. That is,
// it replaces https://example.com with
// <a href="https://example.com">https://example.com</a>
//
// URLs outside the current domain will open in a new tab
// (i.e. will have target="_blank").
//
// This also replaces newlines with <br/> tags.
func LinkifyUrls(text string) template.HTML {
alreadyReplaced := make(map[string]bool)
matches := reUrl.FindAllStringSubmatch(text, -1)
urls := make([]string, len(matches))
for i, _ := range matches {
urls[i] = matches[i][0]
}
// Sort matches by length, and then reverse so longest is first
sort.Sort(ByLen(urls))
reverse(urls)
// Replace urls with links. Do longest urls first, so we don't
// double replace items like "https://example.com" and
// "https://example.com/sub-page"
for _, u := range urls {
if strings.HasSuffix(u, ".") || strings.HasSuffix(u, ",") {
u = u[0 : len(u)-1]
}
if alreadyReplaced[u] {
continue
}
// Link should open in new tab, unless it's in our same domain.
link := fmt.Sprintf(`<a href="%s" target="_blank">%s</a>`, u, u)
parsedUrl, err := url.Parse(u)
if err == nil && parsedUrl.Hostname() == common.Context().Config.Cookies.Domain {
link = fmt.Sprintf(`<a href="%s">%s</a>`, u, u)
}
text = strings.Replace(text, u, link, -1)
alreadyReplaced[u] = true
}
text = strings.ReplaceAll(text, "\n", "<br/>")
return template.HTML(text)
}
// ByLen implements sorting by length
type ByLen []string
// Let returns the length of slice a.
func (a ByLen) Len() int {
return len(a)
}
// Less return true if i is less than j.
func (a ByLen) Less(i, j int) bool {
return len(a[i]) < len(a[j])
}
// Swap i and j in slice.
func (a ByLen) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
// reverse reverses order of items in slice s
func reverse(s []string) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}