Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

appview: pages/markup: serve relative images directly from the knot

authored by

Anirudh Oppiliappan and committed by
Tangled
e53a3a38 ba0c65a2

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