+2
appview/config.go
+2
appview/config.go
···
13
13
Dev bool `env:"TANGLED_DEV, default=false"`
14
14
JetstreamEndpoint string `env:"TANGLED_JETSTREAM_ENDPOINT, default=wss://jetstream1.us-east.bsky.network/subscribe"`
15
15
ResendApiKey string `env:"TANGLED_RESEND_API_KEY"`
16
+
CamoHost string `env:"TANGLED_CAMO_HOST, default=https://camo.tangled.sh"`
17
+
CamoSharedSecret string `env:"TANGLED_CAMO_SHARED_SECRET"`
16
18
}
17
19
18
20
func LoadConfig(ctx context.Context) (*Config, error) {
+1
-1
appview/pages/funcmap.go
+1
-1
appview/pages/funcmap.go
···
143
143
return v.Slice(start, end).Interface()
144
144
},
145
145
"markdown": func(text string) template.HTML {
146
-
rctx := &markup.RenderContext{}
146
+
rctx := &markup.RenderContext{RendererType: markup.RendererTypeDefault}
147
147
return template.HTML(rctx.RenderMarkdown(text))
148
148
},
149
149
"isNil": func(t any) bool {
+31
appview/pages/markup/camo.go
+31
appview/pages/markup/camo.go
···
1
+
package markup
2
+
3
+
import (
4
+
"crypto/hmac"
5
+
"crypto/sha256"
6
+
"encoding/hex"
7
+
"fmt"
8
+
9
+
"github.com/yuin/goldmark/ast"
10
+
)
11
+
12
+
func generateCamoURL(baseURL, secret, imageURL string) string {
13
+
h := hmac.New(sha256.New, []byte(secret))
14
+
h.Write([]byte(imageURL))
15
+
signature := hex.EncodeToString(h.Sum(nil))
16
+
hexURL := hex.EncodeToString([]byte(imageURL))
17
+
return fmt.Sprintf("%s/%s/%s", baseURL, signature, hexURL)
18
+
}
19
+
20
+
func (rctx *RenderContext) camoImageLinkTransformer(img *ast.Image) {
21
+
// don't camo on dev
22
+
if rctx.IsDev {
23
+
return
24
+
}
25
+
26
+
dst := string(img.Destination)
27
+
28
+
if rctx.CamoUrl != "" && rctx.CamoSecret != "" {
29
+
img.Destination = []byte(generateCamoURL(rctx.CamoUrl, rctx.CamoSecret, dst))
30
+
}
31
+
}
+12
-1
appview/pages/markup/markdown.go
+12
-1
appview/pages/markup/markdown.go
···
21
21
const (
22
22
// RendererTypeRepoMarkdown is for repository documentation markdown files
23
23
RendererTypeRepoMarkdown RendererType = iota
24
+
// RendererTypeDefault is non-repo markdown, like issues/pulls/comments.
25
+
RendererTypeDefault
24
26
)
25
27
26
28
// RenderContext holds the contextual data for rendering markdown.
27
29
// It can be initialized empty, and that'll skip any transformations.
28
30
type RenderContext struct {
31
+
CamoUrl string
32
+
CamoSecret string
29
33
repoinfo.RepoInfo
30
34
IsDev bool
31
35
RendererType RendererType
···
73
77
a.rctx.relativeLinkTransformer(n.(*ast.Link))
74
78
case *ast.Image:
75
79
a.rctx.imageFromKnotTransformer(n.(*ast.Image))
80
+
a.rctx.camoImageLinkTransformer(n.(*ast.Image))
76
81
}
77
-
// more types here like RendererTypeIssue/Pull etc.
82
+
83
+
case RendererTypeDefault:
84
+
switch n.(type) {
85
+
case *ast.Image:
86
+
a.rctx.imageFromKnotTransformer(n.(*ast.Image))
87
+
a.rctx.camoImageLinkTransformer(n.(*ast.Image))
88
+
}
78
89
}
79
90
80
91
return ast.WalkContinue, nil
+10
-13
appview/pages/pages.go
+10
-13
appview/pages/pages.go
···
15
15
"path/filepath"
16
16
"strings"
17
17
18
+
"tangled.sh/tangled.sh/core/appview"
18
19
"tangled.sh/tangled.sh/core/appview/auth"
19
20
"tangled.sh/tangled.sh/core/appview/db"
20
21
"tangled.sh/tangled.sh/core/appview/pages/markup"
···
43
44
rctx *markup.RenderContext
44
45
}
45
46
46
-
func NewPages(dev bool) *Pages {
47
+
func NewPages(config *appview.Config) *Pages {
47
48
// initialized with safe defaults, can be overriden per use
48
49
rctx := &markup.RenderContext{
49
-
IsDev: dev,
50
+
IsDev: config.Dev,
51
+
CamoUrl: config.CamoHost,
52
+
CamoSecret: config.CamoSharedSecret,
50
53
}
51
54
52
55
p := &Pages{
53
56
t: make(map[string]*template.Template),
54
-
dev: dev,
57
+
dev: config.Dev,
55
58
embedFS: Files,
56
59
rctx: rctx,
57
60
templateDir: "appview/pages",
···
379
382
return p.executeRepo("repo/empty", w, params)
380
383
}
381
384
382
-
p.rctx = &markup.RenderContext{
383
-
RepoInfo: params.RepoInfo,
384
-
IsDev: p.dev,
385
-
RendererType: markup.RendererTypeRepoMarkdown,
386
-
}
385
+
p.rctx.RepoInfo = params.RepoInfo
386
+
p.rctx.RendererType = markup.RendererTypeRepoMarkdown
387
387
388
388
if params.ReadmeFileName != "" {
389
389
var htmlString string
···
508
508
if params.ShowRendered {
509
509
switch markup.GetFormat(params.Path) {
510
510
case markup.FormatMarkdown:
511
-
p.rctx = &markup.RenderContext{
512
-
RepoInfo: params.RepoInfo,
513
-
IsDev: p.dev,
514
-
RendererType: markup.RendererTypeRepoMarkdown,
515
-
}
511
+
p.rctx.RepoInfo = params.RepoInfo
512
+
p.rctx.RendererType = markup.RendererTypeRepoMarkdown
516
513
params.RenderedContents = template.HTML(p.rctx.RenderMarkdown(params.Contents))
517
514
}
518
515
}