+49
-8
appview/pages/markup/markdown.go
+49
-8
appview/pages/markup/markdown.go
···
3
3
4
4
import (
5
5
"bytes"
6
+
"net/url"
6
7
"path"
7
8
8
9
"github.com/yuin/goldmark"
···
11
12
"github.com/yuin/goldmark/parser"
12
13
"github.com/yuin/goldmark/text"
13
14
"github.com/yuin/goldmark/util"
15
+
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
14
16
)
15
17
16
18
// RendererType defines the type of renderer to use based on context
···
24
26
// RenderContext holds the contextual data for rendering markdown.
25
27
// It can be initialized empty, and that'll skip any transformations.
26
28
type RenderContext struct {
27
-
Ref string
28
-
FullRepoName string
29
+
repoinfo.RepoInfo
30
+
IsDev bool
29
31
RendererType RendererType
30
32
}
31
33
···
66
68
67
69
switch a.rctx.RendererType {
68
70
case RendererTypeRepoMarkdown:
69
-
if v, ok := n.(*ast.Link); ok {
70
-
a.rctx.relativeLinkTransformer(v)
71
+
switch n.(type) {
72
+
case *ast.Link:
73
+
a.rctx.relativeLinkTransformer(n.(*ast.Link))
74
+
case *ast.Image:
75
+
a.rctx.imageFromKnotTransformer(n.(*ast.Image))
71
76
}
72
77
// more types here like RendererTypeIssue/Pull etc.
73
78
}
···
79
84
func (rctx *RenderContext) relativeLinkTransformer(link *ast.Link) {
80
85
dst := string(link.Destination)
81
86
82
-
if len(dst) == 0 || dst[0] == '#' ||
83
-
bytes.Contains(link.Destination, []byte("://")) ||
84
-
bytes.HasPrefix(link.Destination, []byte("mailto:")) {
87
+
if isAbsoluteUrl(dst) {
85
88
return
86
89
}
87
90
88
-
newPath := path.Join("/", rctx.FullRepoName, "tree", rctx.Ref, dst)
91
+
newPath := path.Join("/", rctx.RepoInfo.FullName(), "tree", rctx.RepoInfo.Ref, dst)
89
92
link.Destination = []byte(newPath)
90
93
}
94
+
95
+
func (rctx *RenderContext) imageFromKnotTransformer(img *ast.Image) {
96
+
dst := string(img.Destination)
97
+
98
+
if isAbsoluteUrl(dst) {
99
+
return
100
+
}
101
+
102
+
// strip leading './'
103
+
if len(dst) >= 2 && dst[0:2] == "./" {
104
+
dst = dst[2:]
105
+
}
106
+
107
+
scheme := "https"
108
+
if rctx.IsDev {
109
+
scheme = "http"
110
+
}
111
+
parsedURL := &url.URL{
112
+
Scheme: scheme,
113
+
Host: rctx.Knot,
114
+
Path: path.Join("/",
115
+
rctx.RepoInfo.OwnerDid,
116
+
rctx.RepoInfo.Name,
117
+
"raw",
118
+
url.PathEscape(rctx.RepoInfo.Ref),
119
+
dst),
120
+
}
121
+
newPath := parsedURL.String()
122
+
img.Destination = []byte(newPath)
123
+
}
124
+
125
+
func isAbsoluteUrl(link string) bool {
126
+
parsed, err := url.Parse(link)
127
+
if err != nil {
128
+
return false
129
+
}
130
+
return parsed.IsAbs()
131
+
}
+44
-143
appview/pages/pages.go
+44
-143
appview/pages/pages.go
···
12
12
"log"
13
13
"net/http"
14
14
"os"
15
-
"path"
16
15
"path/filepath"
17
-
"slices"
18
16
"strings"
19
17
20
18
"tangled.sh/tangled.sh/core/appview/auth"
21
19
"tangled.sh/tangled.sh/core/appview/db"
22
20
"tangled.sh/tangled.sh/core/appview/pages/markup"
21
+
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
23
22
"tangled.sh/tangled.sh/core/appview/pagination"
24
-
"tangled.sh/tangled.sh/core/appview/state/userutil"
25
23
"tangled.sh/tangled.sh/core/patchutil"
26
24
"tangled.sh/tangled.sh/core/types"
27
25
···
42
40
dev bool
43
41
embedFS embed.FS
44
42
templateDir string // Path to templates on disk for dev mode
43
+
rctx *markup.RenderContext
45
44
}
46
45
47
46
func NewPages(dev bool) *Pages {
47
+
// initialized with safe defaults, can be overriden per use
48
+
rctx := &markup.RenderContext{
49
+
IsDev: dev,
50
+
}
51
+
48
52
p := &Pages{
49
53
t: make(map[string]*template.Template),
50
54
dev: dev,
51
55
embedFS: Files,
56
+
rctx: rctx,
52
57
templateDir: "appview/pages",
53
58
}
54
59
···
293
298
type ForkRepoParams struct {
294
299
LoggedInUser *auth.User
295
300
Knots []string
296
-
RepoInfo RepoInfo
301
+
RepoInfo repoinfo.RepoInfo
297
302
}
298
303
299
304
func (p *Pages) ForkRepo(w io.Writer, params ForkRepoParams) error {
···
343
348
}
344
349
345
350
type RepoDescriptionParams struct {
346
-
RepoInfo RepoInfo
351
+
RepoInfo repoinfo.RepoInfo
347
352
}
348
353
349
354
func (p *Pages) EditRepoDescriptionFragment(w io.Writer, params RepoDescriptionParams) error {
···
354
359
return p.executePlain("repo/fragments/repoDescription", w, params)
355
360
}
356
361
357
-
type RepoInfo struct {
358
-
Name string
359
-
OwnerDid string
360
-
OwnerHandle string
361
-
Description string
362
-
Knot string
363
-
RepoAt syntax.ATURI
364
-
IsStarred bool
365
-
Stats db.RepoStats
366
-
Roles RolesInRepo
367
-
Source *db.Repo
368
-
SourceHandle string
369
-
Ref string
370
-
DisableFork bool
371
-
}
372
-
373
-
type RolesInRepo struct {
374
-
Roles []string
375
-
}
376
-
377
-
func (r RolesInRepo) SettingsAllowed() bool {
378
-
return slices.Contains(r.Roles, "repo:settings")
379
-
}
380
-
381
-
func (r RolesInRepo) CollaboratorInviteAllowed() bool {
382
-
return slices.Contains(r.Roles, "repo:invite")
383
-
}
384
-
385
-
func (r RolesInRepo) RepoDeleteAllowed() bool {
386
-
return slices.Contains(r.Roles, "repo:delete")
387
-
}
388
-
389
-
func (r RolesInRepo) IsOwner() bool {
390
-
return slices.Contains(r.Roles, "repo:owner")
391
-
}
392
-
393
-
func (r RolesInRepo) IsCollaborator() bool {
394
-
return slices.Contains(r.Roles, "repo:collaborator")
395
-
}
396
-
397
-
func (r RolesInRepo) IsPushAllowed() bool {
398
-
return slices.Contains(r.Roles, "repo:push")
399
-
}
400
-
401
-
func (r RepoInfo) OwnerWithAt() string {
402
-
if r.OwnerHandle != "" {
403
-
return fmt.Sprintf("@%s", r.OwnerHandle)
404
-
} else {
405
-
return r.OwnerDid
406
-
}
407
-
}
408
-
409
-
func (r RepoInfo) FullName() string {
410
-
return path.Join(r.OwnerWithAt(), r.Name)
411
-
}
412
-
413
-
func (r RepoInfo) OwnerWithoutAt() string {
414
-
if strings.HasPrefix(r.OwnerWithAt(), "@") {
415
-
return strings.TrimPrefix(r.OwnerWithAt(), "@")
416
-
} else {
417
-
return userutil.FlattenDid(r.OwnerDid)
418
-
}
419
-
}
420
-
421
-
func (r RepoInfo) FullNameWithoutAt() string {
422
-
return path.Join(r.OwnerWithoutAt(), r.Name)
423
-
}
424
-
425
-
func (r RepoInfo) GetTabs() [][]string {
426
-
tabs := [][]string{
427
-
{"overview", "/", "square-chart-gantt"},
428
-
{"issues", "/issues", "circle-dot"},
429
-
{"pulls", "/pulls", "git-pull-request"},
430
-
}
431
-
432
-
if r.Roles.SettingsAllowed() {
433
-
tabs = append(tabs, []string{"settings", "/settings", "cog"})
434
-
}
435
-
436
-
return tabs
437
-
}
438
-
439
-
// each tab on a repo could have some metadata:
440
-
//
441
-
// issues -> number of open issues etc.
442
-
// settings -> a warning icon to setup branch protection? idk
443
-
//
444
-
// we gather these bits of info here, because go templates
445
-
// are difficult to program in
446
-
func (r RepoInfo) TabMetadata() map[string]any {
447
-
meta := make(map[string]any)
448
-
449
-
if r.Stats.PullCount.Open > 0 {
450
-
meta["pulls"] = r.Stats.PullCount.Open
451
-
}
452
-
453
-
if r.Stats.IssueCount.Open > 0 {
454
-
meta["issues"] = r.Stats.IssueCount.Open
455
-
}
456
-
457
-
// more stuff?
458
-
459
-
return meta
460
-
}
461
-
462
362
type RepoIndexParams struct {
463
363
LoggedInUser *auth.User
464
-
RepoInfo RepoInfo
364
+
RepoInfo repoinfo.RepoInfo
465
365
Active string
466
366
TagMap map[string][]string
467
367
CommitsTrunc []*object.Commit
···
479
379
return p.executeRepo("repo/empty", w, params)
480
380
}
481
381
482
-
rctx := markup.RenderContext{
483
-
Ref: params.RepoInfo.Ref,
484
-
FullRepoName: params.RepoInfo.FullName(),
382
+
p.rctx = &markup.RenderContext{
383
+
RepoInfo: params.RepoInfo,
384
+
IsDev: p.dev,
385
+
RendererType: markup.RendererTypeRepoMarkdown,
485
386
}
486
387
487
388
if params.ReadmeFileName != "" {
···
489
390
ext := filepath.Ext(params.ReadmeFileName)
490
391
switch ext {
491
392
case ".md", ".markdown", ".mdown", ".mkdn", ".mkd":
492
-
htmlString = rctx.RenderMarkdown(params.Readme)
393
+
htmlString = p.rctx.RenderMarkdown(params.Readme)
493
394
params.Raw = false
494
395
params.HTMLReadme = template.HTML(bluemonday.UGCPolicy().Sanitize(htmlString))
495
396
default:
···
504
405
505
406
type RepoLogParams struct {
506
407
LoggedInUser *auth.User
507
-
RepoInfo RepoInfo
408
+
RepoInfo repoinfo.RepoInfo
508
409
TagMap map[string][]string
509
410
types.RepoLogResponse
510
411
Active string
···
518
419
519
420
type RepoCommitParams struct {
520
421
LoggedInUser *auth.User
521
-
RepoInfo RepoInfo
422
+
RepoInfo repoinfo.RepoInfo
522
423
Active string
523
424
EmailToDidOrHandle map[string]string
524
425
···
532
433
533
434
type RepoTreeParams struct {
534
435
LoggedInUser *auth.User
535
-
RepoInfo RepoInfo
436
+
RepoInfo repoinfo.RepoInfo
536
437
Active string
537
438
BreadCrumbs [][]string
538
439
BaseTreeLink string
···
568
469
569
470
type RepoBranchesParams struct {
570
471
LoggedInUser *auth.User
571
-
RepoInfo RepoInfo
472
+
RepoInfo repoinfo.RepoInfo
572
473
Active string
573
474
types.RepoBranchesResponse
574
475
}
···
580
481
581
482
type RepoTagsParams struct {
582
483
LoggedInUser *auth.User
583
-
RepoInfo RepoInfo
484
+
RepoInfo repoinfo.RepoInfo
584
485
Active string
585
486
types.RepoTagsResponse
586
487
}
···
592
493
593
494
type RepoBlobParams struct {
594
495
LoggedInUser *auth.User
595
-
RepoInfo RepoInfo
496
+
RepoInfo repoinfo.RepoInfo
596
497
Active string
597
498
BreadCrumbs [][]string
598
499
ShowRendered bool
···
607
508
if params.ShowRendered {
608
509
switch markup.GetFormat(params.Path) {
609
510
case markup.FormatMarkdown:
610
-
rctx := markup.RenderContext{
611
-
Ref: params.RepoInfo.Ref,
612
-
FullRepoName: params.RepoInfo.FullName(),
511
+
p.rctx = &markup.RenderContext{
512
+
RepoInfo: params.RepoInfo,
513
+
IsDev: p.dev,
613
514
RendererType: markup.RendererTypeRepoMarkdown,
614
515
}
615
-
params.RenderedContents = template.HTML(rctx.RenderMarkdown(params.Contents))
516
+
params.RenderedContents = template.HTML(p.rctx.RenderMarkdown(params.Contents))
616
517
}
617
518
}
618
519
···
657
558
658
559
type RepoSettingsParams struct {
659
560
LoggedInUser *auth.User
660
-
RepoInfo RepoInfo
561
+
RepoInfo repoinfo.RepoInfo
661
562
Collaborators []Collaborator
662
563
Active string
663
564
Branches []string
···
673
574
674
575
type RepoIssuesParams struct {
675
576
LoggedInUser *auth.User
676
-
RepoInfo RepoInfo
577
+
RepoInfo repoinfo.RepoInfo
677
578
Active string
678
579
Issues []db.Issue
679
580
DidHandleMap map[string]string
···
688
589
689
590
type RepoSingleIssueParams struct {
690
591
LoggedInUser *auth.User
691
-
RepoInfo RepoInfo
592
+
RepoInfo repoinfo.RepoInfo
692
593
Active string
693
594
Issue db.Issue
694
595
Comments []db.Comment
···
710
611
711
612
type RepoNewIssueParams struct {
712
613
LoggedInUser *auth.User
713
-
RepoInfo RepoInfo
614
+
RepoInfo repoinfo.RepoInfo
714
615
Active string
715
616
}
716
617
···
721
622
722
623
type EditIssueCommentParams struct {
723
624
LoggedInUser *auth.User
724
-
RepoInfo RepoInfo
625
+
RepoInfo repoinfo.RepoInfo
725
626
Issue *db.Issue
726
627
Comment *db.Comment
727
628
}
···
733
634
type SingleIssueCommentParams struct {
734
635
LoggedInUser *auth.User
735
636
DidHandleMap map[string]string
736
-
RepoInfo RepoInfo
637
+
RepoInfo repoinfo.RepoInfo
737
638
Issue *db.Issue
738
639
Comment *db.Comment
739
640
}
···
744
645
745
646
type RepoNewPullParams struct {
746
647
LoggedInUser *auth.User
747
-
RepoInfo RepoInfo
648
+
RepoInfo repoinfo.RepoInfo
748
649
Branches []types.Branch
749
650
Active string
750
651
}
···
756
657
757
658
type RepoPullsParams struct {
758
659
LoggedInUser *auth.User
759
-
RepoInfo RepoInfo
660
+
RepoInfo repoinfo.RepoInfo
760
661
Pulls []*db.Pull
761
662
Active string
762
663
DidHandleMap map[string]string
···
788
689
789
690
type RepoSinglePullParams struct {
790
691
LoggedInUser *auth.User
791
-
RepoInfo RepoInfo
692
+
RepoInfo repoinfo.RepoInfo
792
693
Active string
793
694
DidHandleMap map[string]string
794
695
Pull *db.Pull
···
804
705
type RepoPullPatchParams struct {
805
706
LoggedInUser *auth.User
806
707
DidHandleMap map[string]string
807
-
RepoInfo RepoInfo
708
+
RepoInfo repoinfo.RepoInfo
808
709
Pull *db.Pull
809
710
Diff *types.NiceDiff
810
711
Round int
···
819
720
type RepoPullInterdiffParams struct {
820
721
LoggedInUser *auth.User
821
722
DidHandleMap map[string]string
822
-
RepoInfo RepoInfo
723
+
RepoInfo repoinfo.RepoInfo
823
724
Pull *db.Pull
824
725
Round int
825
726
Interdiff *patchutil.InterdiffResult
···
831
732
}
832
733
833
734
type PullPatchUploadParams struct {
834
-
RepoInfo RepoInfo
735
+
RepoInfo repoinfo.RepoInfo
835
736
}
836
737
837
738
func (p *Pages) PullPatchUploadFragment(w io.Writer, params PullPatchUploadParams) error {
···
839
740
}
840
741
841
742
type PullCompareBranchesParams struct {
842
-
RepoInfo RepoInfo
743
+
RepoInfo repoinfo.RepoInfo
843
744
Branches []types.Branch
844
745
}
845
746
···
848
749
}
849
750
850
751
type PullCompareForkParams struct {
851
-
RepoInfo RepoInfo
752
+
RepoInfo repoinfo.RepoInfo
852
753
Forks []db.Repo
853
754
}
854
755
···
857
758
}
858
759
859
760
type PullCompareForkBranchesParams struct {
860
-
RepoInfo RepoInfo
761
+
RepoInfo repoinfo.RepoInfo
861
762
SourceBranches []types.Branch
862
763
TargetBranches []types.Branch
863
764
}
···
868
769
869
770
type PullResubmitParams struct {
870
771
LoggedInUser *auth.User
871
-
RepoInfo RepoInfo
772
+
RepoInfo repoinfo.RepoInfo
872
773
Pull *db.Pull
873
774
SubmissionId int
874
775
}
···
879
780
880
781
type PullActionsParams struct {
881
782
LoggedInUser *auth.User
882
-
RepoInfo RepoInfo
783
+
RepoInfo repoinfo.RepoInfo
883
784
Pull *db.Pull
884
785
RoundNumber int
885
786
MergeCheck types.MergeCheckResponse
···
892
793
893
794
type PullNewCommentParams struct {
894
795
LoggedInUser *auth.User
895
-
RepoInfo RepoInfo
796
+
RepoInfo repoinfo.RepoInfo
896
797
Pull *db.Pull
897
798
RoundNumber int
898
799
}
+1
-1
appview/pages/templates/repo/blob.html
+1
-1
appview/pages/templates/repo/blob.html
···
42
42
<span class="select-none px-1 md:px-2 [&:before]:content-['·']"></span>
43
43
<span>{{ byteFmt .SizeHint }}</span>
44
44
<span class="select-none px-1 md:px-2 [&:before]:content-['·']"></span>
45
-
<a href="/{{ .RepoInfo.FullName }}/blob/{{ .Ref }}/raw/{{ .Path }}">view raw</a>
45
+
<a href="/{{ .RepoInfo.FullName }}/raw/{{ .Ref }}/{{ .Path }}">view raw</a>
46
46
{{ if .RenderToggle }}
47
47
<span class="select-none px-1 md:px-2 [&:before]:content-['·']"></span>
48
48
<a
+3
-2
appview/state/repo.go
+3
-2
appview/state/repo.go
···
28
28
"tangled.sh/tangled.sh/core/appview/db"
29
29
"tangled.sh/tangled.sh/core/appview/pages"
30
30
"tangled.sh/tangled.sh/core/appview/pages/markup"
31
+
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
31
32
"tangled.sh/tangled.sh/core/appview/pagination"
32
33
"tangled.sh/tangled.sh/core/types"
33
34
···
996
997
return collaborators, nil
997
998
}
998
999
999
-
func (f *FullyResolvedRepo) RepoInfo(s *State, u *auth.User) pages.RepoInfo {
1000
+
func (f *FullyResolvedRepo) RepoInfo(s *State, u *auth.User) repoinfo.RepoInfo {
1000
1001
isStarred := false
1001
1002
if u != nil {
1002
1003
isStarred = db.GetStarStatus(s.db, u.Did, syntax.ATURI(f.RepoAt))
···
1070
1071
knot = "tangled.sh"
1071
1072
}
1072
1073
1073
-
repoInfo := pages.RepoInfo{
1074
+
repoInfo := repoinfo.RepoInfo{
1074
1075
OwnerDid: f.OwnerDid(),
1075
1076
OwnerHandle: f.OwnerHandle(),
1076
1077
Name: f.RepoName,
+4
-4
appview/state/repo_util.go
+4
-4
appview/state/repo_util.go
···
14
14
"github.com/go-git/go-git/v5/plumbing/object"
15
15
"tangled.sh/tangled.sh/core/appview/auth"
16
16
"tangled.sh/tangled.sh/core/appview/db"
17
-
"tangled.sh/tangled.sh/core/appview/pages"
17
+
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
18
18
)
19
19
20
20
func (s *State) fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) {
···
73
73
}, nil
74
74
}
75
75
76
-
func RolesInRepo(s *State, u *auth.User, f *FullyResolvedRepo) pages.RolesInRepo {
76
+
func RolesInRepo(s *State, u *auth.User, f *FullyResolvedRepo) repoinfo.RolesInRepo {
77
77
if u != nil {
78
78
r := s.enforcer.GetPermissionsInRepo(u.Did, f.Knot, f.DidSlashRepo())
79
-
return pages.RolesInRepo{r}
79
+
return repoinfo.RolesInRepo{r}
80
80
} else {
81
-
return pages.RolesInRepo{}
81
+
return repoinfo.RolesInRepo{}
82
82
}
83
83
}
84
84
+1
-1
appview/state/router.go
+1
-1
appview/state/router.go
···
65
65
r.Get("/branches", s.RepoBranches)
66
66
r.Get("/tags", s.RepoTags)
67
67
r.Get("/blob/{ref}/*", s.RepoBlob)
68
-
r.Get("/blob/{ref}/raw/*", s.RepoBlobRaw)
68
+
r.Get("/raw/{ref}/*", s.RepoBlobRaw)
69
69
70
70
r.Route("/issues", func(r chi.Router) {
71
71
r.With(middleware.Paginate).Get("/", s.RepoIssues)