+2
appview/config.go
+2
appview/config.go
···
13
Dev bool `env:"TANGLED_DEV, default=false"`
14
JetstreamEndpoint string `env:"TANGLED_JETSTREAM_ENDPOINT, default=wss://jetstream1.us-east.bsky.network/subscribe"`
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"`
18
}
19
20
func LoadConfig(ctx context.Context) (*Config, error) {
+1
-1
appview/pages/funcmap.go
+1
-1
appview/pages/funcmap.go
+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
const (
22
// RendererTypeRepoMarkdown is for repository documentation markdown files
23
RendererTypeRepoMarkdown RendererType = iota
24
)
25
26
// RenderContext holds the contextual data for rendering markdown.
27
// It can be initialized empty, and that'll skip any transformations.
28
type RenderContext struct {
29
repoinfo.RepoInfo
30
IsDev bool
31
RendererType RendererType
···
73
a.rctx.relativeLinkTransformer(n.(*ast.Link))
74
case *ast.Image:
75
a.rctx.imageFromKnotTransformer(n.(*ast.Image))
76
}
77
-
// more types here like RendererTypeIssue/Pull etc.
78
}
79
80
return ast.WalkContinue, nil
···
21
const (
22
// RendererTypeRepoMarkdown is for repository documentation markdown files
23
RendererTypeRepoMarkdown RendererType = iota
24
+
// RendererTypeDefault is non-repo markdown, like issues/pulls/comments.
25
+
RendererTypeDefault
26
)
27
28
// RenderContext holds the contextual data for rendering markdown.
29
// It can be initialized empty, and that'll skip any transformations.
30
type RenderContext struct {
31
+
CamoUrl string
32
+
CamoSecret string
33
repoinfo.RepoInfo
34
IsDev bool
35
RendererType RendererType
···
77
a.rctx.relativeLinkTransformer(n.(*ast.Link))
78
case *ast.Image:
79
a.rctx.imageFromKnotTransformer(n.(*ast.Image))
80
+
a.rctx.camoImageLinkTransformer(n.(*ast.Image))
81
}
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
+
}
89
}
90
91
return ast.WalkContinue, nil
+10
-13
appview/pages/pages.go
+10
-13
appview/pages/pages.go
···
15
"path/filepath"
16
"strings"
17
18
"tangled.sh/tangled.sh/core/appview/auth"
19
"tangled.sh/tangled.sh/core/appview/db"
20
"tangled.sh/tangled.sh/core/appview/pages/markup"
···
44
rctx *markup.RenderContext
45
}
46
47
-
func NewPages(dev bool) *Pages {
48
// initialized with safe defaults, can be overriden per use
49
rctx := &markup.RenderContext{
50
-
IsDev: dev,
51
}
52
53
p := &Pages{
54
t: make(map[string]*template.Template),
55
-
dev: dev,
56
embedFS: Files,
57
rctx: rctx,
58
templateDir: "appview/pages",
···
380
return p.executeRepo("repo/empty", w, params)
381
}
382
383
-
p.rctx = &markup.RenderContext{
384
-
RepoInfo: params.RepoInfo,
385
-
IsDev: p.dev,
386
-
RendererType: markup.RendererTypeRepoMarkdown,
387
-
}
388
389
if params.ReadmeFileName != "" {
390
var htmlString string
···
521
if params.ShowRendered {
522
switch markup.GetFormat(params.Path) {
523
case markup.FormatMarkdown:
524
-
p.rctx = &markup.RenderContext{
525
-
RepoInfo: params.RepoInfo,
526
-
IsDev: p.dev,
527
-
RendererType: markup.RendererTypeRepoMarkdown,
528
-
}
529
params.RenderedContents = template.HTML(p.rctx.RenderMarkdown(params.Contents))
530
}
531
}
···
15
"path/filepath"
16
"strings"
17
18
+
"tangled.sh/tangled.sh/core/appview"
19
"tangled.sh/tangled.sh/core/appview/auth"
20
"tangled.sh/tangled.sh/core/appview/db"
21
"tangled.sh/tangled.sh/core/appview/pages/markup"
···
45
rctx *markup.RenderContext
46
}
47
48
+
func NewPages(config *appview.Config) *Pages {
49
// initialized with safe defaults, can be overriden per use
50
rctx := &markup.RenderContext{
51
+
IsDev: config.Dev,
52
+
CamoUrl: config.CamoHost,
53
+
CamoSecret: config.CamoSharedSecret,
54
}
55
56
p := &Pages{
57
t: make(map[string]*template.Template),
58
+
dev: config.Dev,
59
embedFS: Files,
60
rctx: rctx,
61
templateDir: "appview/pages",
···
383
return p.executeRepo("repo/empty", w, params)
384
}
385
386
+
p.rctx.RepoInfo = params.RepoInfo
387
+
p.rctx.RendererType = markup.RendererTypeRepoMarkdown
388
389
if params.ReadmeFileName != "" {
390
var htmlString string
···
521
if params.ShowRendered {
522
switch markup.GetFormat(params.Path) {
523
case markup.FormatMarkdown:
524
+
p.rctx.RepoInfo = params.RepoInfo
525
+
p.rctx.RendererType = markup.RendererTypeRepoMarkdown
526
params.RenderedContents = template.HTML(p.rctx.RenderMarkdown(params.Contents))
527
}
528
}