t

thomas / gistfile1.txt

Last active 3 hours ago

Like 0
go ok

t thomas revised this gist 3 hours ago · d18a7cc

2 files changed, 257 insertions, 1 deletion

gistfile1.txt (file deleted)
@@ -1 +0,0 @@
1 - OKAY
render.go (file created)
@@ -0,0 +1,257 @@
1 + package server
2 +
3 + import (
4 + gojson "encoding/json"
5 + "errors"
6 + "fmt"
7 + htmlpkg "html"
8 + "html/template"
9 + "io"
10 + "io/fs"
11 + "net/http"
12 + "net/url"
13 + "path/filepath"
14 + "regexp"
15 + "strconv"
16 + "strings"
17 + "time"
18 +
19 + "github.com/dustin/go-humanize"
20 + "github.com/labstack/echo/v4"
21 + "github.com/rs/zerolog/log"
22 + "github.com/thomiceli/opengist/internal/config"
23 + "github.com/thomiceli/opengist/internal/db"
24 + "github.com/thomiceli/opengist/internal/index"
25 + "github.com/thomiceli/opengist/internal/web/context"
26 + "github.com/thomiceli/opengist/internal/web/handlers"
27 + "github.com/thomiceli/opengist/public"
28 + "github.com/thomiceli/opengist/templates"
29 + )
30 +
31 + type Template struct {
32 + templates *template.Template
33 + // pages holds the new layout-based templates, keyed by file name (e.g. "all.html").
34 + // Each entry is a clone of the base layout with that page's "content" block parsed in.
35 + pages map[string]*template.Template
36 + }
37 +
38 + func (t *Template) Render(w io.Writer, name string, data interface{}, _ echo.Context) error {
39 + if tmpl, ok := t.pages[name]; ok {
40 + return tmpl.ExecuteTemplate(w, "base", data)
41 + }
42 + return t.templates.ExecuteTemplate(w, name, data)
43 + }
44 +
45 + var re = regexp.MustCompile("[^a-z0-9]+")
46 +
47 + func (s *Server) setFuncMap() {
48 + fm := template.FuncMap{
49 + "split": strings.Split,
50 + "indexByte": strings.IndexByte,
51 + "toInt": func(i string) int {
52 + val, _ := strconv.Atoi(i)
53 + return val
54 + },
55 + "inc": func(i int) int {
56 + return i + 1
57 + },
58 + "splitGit": func(i string) []string {
59 + return strings.FieldsFunc(i, func(r rune) bool {
60 + return r == ',' || r == ' '
61 + })
62 + },
63 + "lines": func(i string) []string {
64 + return strings.Split(i, "\n")
65 + },
66 + "isMarkdown": func(i string) bool {
67 + return strings.ToLower(filepath.Ext(i)) == ".md"
68 + },
69 + "isMermaid": func(i string) bool {
70 + return strings.ToLower(filepath.Ext(i)) == ".mmd"
71 + },
72 + "isJupyter": func(i string) bool {
73 + return strings.ToLower(filepath.Ext(i)) == ".ipynb"
74 + },
75 + "httpStatusText": http.StatusText,
76 + "loadedTime": func(startTime time.Time) string {
77 + return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
78 + },
79 + "slug": func(s string) string {
80 + return strings.Trim(re.ReplaceAllString(strings.ToLower(s), "-"), "-")
81 + },
82 + "avatarUrl": func(user *db.User, noGravatar bool) string {
83 + if user.HasUploadedAvatar() {
84 + return fmt.Sprintf("%s/avatar/%s", config.C.ExternalUrl, user.AvatarURL)
85 + }
86 +
87 + if user.AvatarURL != "" {
88 + return user.AvatarURL
89 + }
90 +
91 + if user.MD5Hash != "" && !noGravatar {
92 + return "https://www.gravatar.com/avatar/" + user.MD5Hash + "?d=identicon&s=200"
93 + }
94 +
95 + return ""
96 + },
97 + "shouldGenerateAvatar": func(user *db.User, noGravatar bool) bool {
98 + if user == nil {
99 + return true
100 + }
101 + return user.AvatarURL == "" && (user.MD5Hash == "" || noGravatar)
102 + },
103 + "asset": func(file string) string {
104 + if s.dev {
105 + return "http://localhost:16157/" + file
106 + }
107 + return config.C.ExternalUrl + "/" + context.ManifestEntries[file].File
108 + },
109 + "assetCss": func(file string) string {
110 + if s.dev {
111 + return "http://localhost:16157/" + file
112 + }
113 + return config.C.ExternalUrl + "/" + context.ManifestEntries[file].Css[0]
114 + },
115 + "custom": func(file string) string {
116 + assetpath, err := url.JoinPath("/", "assets", file)
117 + if err != nil {
118 + log.Error().Err(err).Msgf("Failed to join path for custom file %s", file)
119 + }
120 + return config.C.ExternalUrl + assetpath
121 + },
122 + "dev": func() bool {
123 + return s.dev
124 + },
125 + "visibilityStr": func(visibility db.Visibility, lowercase bool) string {
126 + s := "Public"
127 + switch visibility {
128 + case 1:
129 + s = "Unlisted"
130 + case 2:
131 + s = "Private"
132 + }
133 +
134 + if lowercase {
135 + return strings.ToLower(s)
136 + }
137 + return s
138 + },
139 + "unescape": htmlpkg.UnescapeString,
140 + "join": func(s ...string) string {
141 + return strings.Join(s, "")
142 + },
143 + "toStr": func(i interface{}) string {
144 + return fmt.Sprint(i)
145 + },
146 + "safe": func(s string) template.HTML {
147 + return template.HTML(s)
148 + },
149 + "dict": func(values ...interface{}) (map[string]interface{}, error) {
150 + if len(values)%2 != 0 {
151 + return nil, errors.New("invalid dict call")
152 + }
153 + dict := make(map[string]interface{})
154 + for i := 0; i < len(values); i += 2 {
155 + key, ok := values[i].(string)
156 + if !ok {
157 + return nil, errors.New("dict keys must be strings")
158 + }
159 + dict[key] = values[i+1]
160 + }
161 + return dict, nil
162 + },
163 + "addMetadataToSearchQuery": func(input, key, value string) string {
164 + metadata := handlers.ParseSearchQueryStr(input)
165 + // extract free-text content (stored under "all") and remove it from metadata
166 + content := metadata["all"]
167 + delete(metadata, "all")
168 +
169 + metadata[key] = value
170 +
171 + var resultBuilder strings.Builder
172 + resultBuilder.WriteString(content)
173 +
174 + for k, v := range metadata {
175 + resultBuilder.WriteString(" ")
176 + resultBuilder.WriteString(k)
177 + resultBuilder.WriteString(":")
178 + resultBuilder.WriteString(v)
179 + }
180 +
181 + return strings.TrimSpace(resultBuilder.String())
182 + },
183 + "indexEnabled": index.IndexEnabled,
184 + "isUrl": func(s string) bool {
185 + _, err := url.ParseRequestURI(s)
186 + return err == nil
187 + },
188 + "topicsToStr": func(topics []db.GistTopic) string {
189 + str := ""
190 + for i, topic := range topics {
191 + if i > 0 {
192 + str += " "
193 + }
194 + str += topic.Topic
195 + }
196 + return str
197 + },
198 + "hexToRgb": func(hex string) string {
199 + h, _ := strconv.ParseUint(strings.TrimPrefix(hex, "#"), 16, 32)
200 + return fmt.Sprintf("%d, %d, %d,", (h>>16)&0xFF, (h>>8)&0xFF, h&0xFF)
201 + },
202 + "humanTimeDiff": func(t int64) string {
203 + return humanize.Time(time.Unix(t, 0))
204 + },
205 + "humanTimeDiffStr": func(timestamp string) string {
206 + t, _ := strconv.ParseInt(timestamp, 10, 64)
207 + return humanize.Time(time.Unix(t, 0))
208 + },
209 + "humanDate": func(t int64) string {
210 + return time.Unix(t, 0).Format("02/01/2006 15:04")
211 + },
212 + "humanDateOnly": func(t int64) string {
213 + return time.Unix(t, 0).Format("02/01/2006")
214 + },
215 + "mainTheme": func(theme *db.UserStyleDTO) string {
216 + if theme == nil {
217 + return "auto"
218 + }
219 +
220 + if theme.Theme == "" {
221 + return "auto"
222 + }
223 +
224 + return theme.Theme
225 + },
226 + }
227 +
228 + base := template.Must(template.New("base").Funcs(fm).ParseFS(templates.Files, "layouts/*.html", "partials/*.html"))
229 + pagePaths, err := fs.Glob(templates.Files, "pages/*.html")
230 + if err != nil {
231 + log.Fatal().Err(err).Msg("Failed to glob new page templates")
232 + }
233 + pages := make(map[string]*template.Template, len(pagePaths))
234 + for _, p := range pagePaths {
235 + cloned := template.Must(base.Clone())
236 + pages[filepath.Base(p)] = template.Must(cloned.ParseFS(templates.Files, p))
237 + }
238 +
239 + s.echo.Renderer = &Template{
240 + templates: base,
241 + pages: pages,
242 + }
243 + }
244 +
245 + func (s *Server) parseManifestEntries() {
246 + file, err := public.Files.Open(".vite/manifest.json")
247 + if err != nil {
248 + log.Fatal().Err(err).Msg("Failed to open manifest.json")
249 + }
250 + byteValue, err := io.ReadAll(file)
251 + if err != nil {
252 + log.Fatal().Err(err).Msg("Failed to read manifest.json")
253 + }
254 + if err = gojson.Unmarshal(byteValue, &context.ManifestEntries); err != nil {
255 + log.Fatal().Err(err).Msg("Failed to unmarshal manifest.json")
256 + }
257 + }

t thomas revised this gist 3 hours ago · cafc044

1 file changed, 1 insertion

gistfile1.txt (file created)
@@ -0,0 +1 @@
1 + OKAY