forked from tangled.org/core
this repo has no description
1package pages 2 3import ( 4 "errors" 5 "fmt" 6 "html" 7 "html/template" 8 "log" 9 "math" 10 "path/filepath" 11 "reflect" 12 "strings" 13 "time" 14 15 "github.com/dustin/go-humanize" 16 "tangled.sh/tangled.sh/core/appview/pages/markup" 17) 18 19func funcMap() template.FuncMap { 20 return template.FuncMap{ 21 "split": func(s string) []string { 22 return strings.Split(s, "\n") 23 }, 24 "truncateAt30": func(s string) string { 25 if len(s) <= 30 { 26 return s 27 } 28 return s[:30] + "…" 29 }, 30 "splitOn": func(s, sep string) []string { 31 return strings.Split(s, sep) 32 }, 33 "add": func(a, b int) int { 34 return a + b 35 }, 36 // the absolute state of go templates 37 "add64": func(a, b int64) int64 { 38 return a + b 39 }, 40 "sub": func(a, b int) int { 41 return a - b 42 }, 43 "cond": func(cond interface{}, a, b string) string { 44 if cond == nil { 45 return b 46 } 47 48 if boolean, ok := cond.(bool); boolean && ok { 49 return a 50 } 51 52 return b 53 }, 54 "didOrHandle": func(did, handle string) string { 55 if handle != "" { 56 return fmt.Sprintf("@%s", handle) 57 } else { 58 return did 59 } 60 }, 61 "assoc": func(values ...string) ([][]string, error) { 62 if len(values)%2 != 0 { 63 return nil, fmt.Errorf("invalid assoc call, must have an even number of arguments") 64 } 65 pairs := make([][]string, 0) 66 for i := 0; i < len(values); i += 2 { 67 pairs = append(pairs, []string{values[i], values[i+1]}) 68 } 69 return pairs, nil 70 }, 71 "append": func(s []string, values ...string) []string { 72 s = append(s, values...) 73 return s 74 }, 75 "timeFmt": humanize.Time, 76 "longTimeFmt": func(t time.Time) string { 77 return t.Format("2006-01-02 * 3:04 PM") 78 }, 79 "shortTimeFmt": func(t time.Time) string { 80 return humanize.CustomRelTime(t, time.Now(), "", "", []humanize.RelTimeMagnitude{ 81 {time.Second, "now", time.Second}, 82 {2 * time.Second, "1s %s", 1}, 83 {time.Minute, "%ds %s", time.Second}, 84 {2 * time.Minute, "1min %s", 1}, 85 {time.Hour, "%dmin %s", time.Minute}, 86 {2 * time.Hour, "1hr %s", 1}, 87 {humanize.Day, "%dhrs %s", time.Hour}, 88 {2 * humanize.Day, "1d %s", 1}, 89 {20 * humanize.Day, "%dd %s", humanize.Day}, 90 {8 * humanize.Week, "%dw %s", humanize.Week}, 91 {humanize.Year, "%dmo %s", humanize.Month}, 92 {18 * humanize.Month, "1y %s", 1}, 93 {2 * humanize.Year, "2y %s", 1}, 94 {humanize.LongTime, "%dy %s", humanize.Year}, 95 {math.MaxInt64, "a long while %s", 1}, 96 }) 97 }, 98 "byteFmt": humanize.Bytes, 99 "length": func(slice any) int { 100 v := reflect.ValueOf(slice) 101 if v.Kind() == reflect.Slice || v.Kind() == reflect.Array { 102 return v.Len() 103 } 104 return 0 105 }, 106 "splitN": func(s, sep string, n int) []string { 107 return strings.SplitN(s, sep, n) 108 }, 109 "escapeHtml": func(s string) template.HTML { 110 if s == "" { 111 return template.HTML("<br>") 112 } 113 return template.HTML(s) 114 }, 115 "unescapeHtml": func(s string) string { 116 return html.UnescapeString(s) 117 }, 118 "nl2br": func(text string) template.HTML { 119 return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "<br>", -1)) 120 }, 121 "unwrapText": func(text string) string { 122 paragraphs := strings.Split(text, "\n\n") 123 124 for i, p := range paragraphs { 125 lines := strings.Split(p, "\n") 126 paragraphs[i] = strings.Join(lines, " ") 127 } 128 129 return strings.Join(paragraphs, "\n\n") 130 }, 131 "sequence": func(n int) []struct{} { 132 return make([]struct{}, n) 133 }, 134 "subslice": func(slice any, start, end int) any { 135 v := reflect.ValueOf(slice) 136 if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { 137 return nil 138 } 139 if start < 0 || start > v.Len() || end > v.Len() || start > end { 140 return nil 141 } 142 return v.Slice(start, end).Interface() 143 }, 144 "markdown": func(text string) template.HTML { 145 return template.HTML(markup.RenderMarkdown(text)) 146 }, 147 "isNil": func(t any) bool { 148 // returns false for other "zero" values 149 return t == nil 150 }, 151 "list": func(args ...any) []any { 152 return args 153 }, 154 "dict": func(values ...any) (map[string]any, error) { 155 if len(values)%2 != 0 { 156 return nil, errors.New("invalid dict call") 157 } 158 dict := make(map[string]any, len(values)/2) 159 for i := 0; i < len(values); i += 2 { 160 key, ok := values[i].(string) 161 if !ok { 162 return nil, errors.New("dict keys must be strings") 163 } 164 dict[key] = values[i+1] 165 } 166 return dict, nil 167 }, 168 "i": func(name string, classes ...string) template.HTML { 169 data, err := icon(name, classes) 170 if err != nil { 171 log.Printf("icon %s does not exist", name) 172 data, _ = icon("airplay", classes) 173 } 174 return template.HTML(data) 175 }, 176 "cssContentHash": CssContentHash, 177 } 178} 179 180func icon(name string, classes []string) (template.HTML, error) { 181 iconPath := filepath.Join("static", "icons", name) 182 183 if filepath.Ext(name) == "" { 184 iconPath += ".svg" 185 } 186 187 data, err := Files.ReadFile(iconPath) 188 if err != nil { 189 return "", fmt.Errorf("icon %s not found: %w", name, err) 190 } 191 192 // Convert SVG data to string 193 svgStr := string(data) 194 195 svgTagEnd := strings.Index(svgStr, ">") 196 if svgTagEnd == -1 { 197 return "", fmt.Errorf("invalid SVG format for icon %s", name) 198 } 199 200 classTag := ` class="` + strings.Join(classes, " ") + `"` 201 202 modifiedSVG := svgStr[:svgTagEnd] + classTag + svgStr[svgTagEnd:] 203 return template.HTML(modifiedSVG), nil 204}