forked from tangled.org/core
Monorepo for Tangled

appview: pages/markup: resolve relative links in markdown

anirudh.fi 9fbb3c31 11d58231

verified
Changed files
+106 -78
appview
+2 -1
appview/pages/funcmap.go
··· 143 143 return v.Slice(start, end).Interface() 144 144 }, 145 145 "markdown": func(text string) template.HTML { 146 - return template.HTML(markup.RenderMarkdown(text)) 146 + rctx := &markup.RenderContext{} 147 + return template.HTML(rctx.RenderMarkdown(text)) 147 148 }, 148 149 "isNil": func(t any) bool { 149 150 // returns false for other "zero" values
+4 -11
appview/pages/markup/markdown.go
··· 19 19 const ( 20 20 // RendererTypeRepoMarkdown is for repository documentation markdown files 21 21 RendererTypeRepoMarkdown RendererType = iota 22 - // RendererTypeIssueComment is for issue comments 23 - RendererTypeIssueComment 24 - // RendererTypePullComment is for pull request comments 25 - RendererTypePullComment 26 - // RendererTypeDefault is the default renderer with minimal transformations 27 - RendererTypeDefault 28 22 ) 29 23 30 24 // RenderContext holds the contextual data for rendering markdown. 31 - // It can be initialized empty, and that'll skip any transformations 32 - // and use the default renderer (RendererTypeDefault). 25 + // It can be initialized empty, and that'll skip any transformations. 33 26 type RenderContext struct { 34 27 Ref string 35 28 FullRepoName string ··· 73 66 74 67 switch a.rctx.RendererType { 75 68 case RendererTypeRepoMarkdown: 76 - a.rctx.relativeLinkTransformer(n.(*ast.Link)) 77 - case RendererTypeDefault: 78 - a.rctx.relativeLinkTransformer(n.(*ast.Link)) 69 + if v, ok := n.(*ast.Link); ok { 70 + a.rctx.relativeLinkTransformer(v) 71 + } 79 72 // more types here like RendererTypeIssue/Pull etc. 80 73 } 81 74
+13 -2
appview/pages/pages.go
··· 366 366 Roles RolesInRepo 367 367 Source *db.Repo 368 368 SourceHandle string 369 + Ref string 369 370 DisableFork bool 370 371 } 371 372 ··· 478 479 return p.executeRepo("repo/empty", w, params) 479 480 } 480 481 482 + rctx := markup.RenderContext{ 483 + Ref: params.RepoInfo.Ref, 484 + FullRepoName: params.RepoInfo.FullName(), 485 + } 486 + 481 487 if params.ReadmeFileName != "" { 482 488 var htmlString string 483 489 ext := filepath.Ext(params.ReadmeFileName) 484 490 switch ext { 485 491 case ".md", ".markdown", ".mdown", ".mkdn", ".mkd": 486 - htmlString = markup.RenderMarkdown(params.Readme) 492 + htmlString = rctx.RenderMarkdown(params.Readme) 487 493 params.Raw = false 488 494 params.HTMLReadme = template.HTML(bluemonday.UGCPolicy().Sanitize(htmlString)) 489 495 default: ··· 601 607 if params.ShowRendered { 602 608 switch markup.GetFormat(params.Path) { 603 609 case markup.FormatMarkdown: 604 - params.RenderedContents = template.HTML(markup.RenderMarkdown(params.Contents)) 610 + rctx := markup.RenderContext{ 611 + Ref: params.RepoInfo.Ref, 612 + FullRepoName: params.RepoInfo.FullName(), 613 + RendererType: markup.RendererTypeRepoMarkdown, 614 + } 615 + params.RenderedContents = template.HTML(rctx.RenderMarkdown(params.Contents)) 605 616 } 606 617 } 607 618
+2 -2
appview/state/middleware.go
··· 61 61 http.Error(w, "Forbiden", http.StatusUnauthorized) 62 62 return 63 63 } 64 - f, err := fullyResolvedRepo(r) 64 + f, err := s.fullyResolvedRepo(r) 65 65 if err != nil { 66 66 http.Error(w, "malformed url", http.StatusBadRequest) 67 67 return ··· 148 148 func ResolvePull(s *State) middleware.Middleware { 149 149 return func(next http.Handler) http.Handler { 150 150 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 151 - f, err := fullyResolvedRepo(r) 151 + f, err := s.fullyResolvedRepo(r) 152 152 if err != nil { 153 153 log.Println("failed to fully resolve repo", err) 154 154 http.Error(w, "invalid repo url", http.StatusNotFound)
+19 -19
appview/state/pull.go
··· 30 30 switch r.Method { 31 31 case http.MethodGet: 32 32 user := s.auth.GetUser(r) 33 - f, err := fullyResolvedRepo(r) 33 + f, err := s.fullyResolvedRepo(r) 34 34 if err != nil { 35 35 log.Println("failed to get repo and knot", err) 36 36 return ··· 74 74 75 75 func (s *State) RepoSinglePull(w http.ResponseWriter, r *http.Request) { 76 76 user := s.auth.GetUser(r) 77 - f, err := fullyResolvedRepo(r) 77 + f, err := s.fullyResolvedRepo(r) 78 78 if err != nil { 79 79 log.Println("failed to get repo and knot", err) 80 80 return ··· 251 251 252 252 func (s *State) RepoPullPatch(w http.ResponseWriter, r *http.Request) { 253 253 user := s.auth.GetUser(r) 254 - f, err := fullyResolvedRepo(r) 254 + f, err := s.fullyResolvedRepo(r) 255 255 if err != nil { 256 256 log.Println("failed to get repo and knot", err) 257 257 return ··· 300 300 func (s *State) RepoPullInterdiff(w http.ResponseWriter, r *http.Request) { 301 301 user := s.auth.GetUser(r) 302 302 303 - f, err := fullyResolvedRepo(r) 303 + f, err := s.fullyResolvedRepo(r) 304 304 if err != nil { 305 305 log.Println("failed to get repo and knot", err) 306 306 return ··· 408 408 state = db.PullMerged 409 409 } 410 410 411 - f, err := fullyResolvedRepo(r) 411 + f, err := s.fullyResolvedRepo(r) 412 412 if err != nil { 413 413 log.Println("failed to get repo and knot", err) 414 414 return ··· 462 462 463 463 func (s *State) PullComment(w http.ResponseWriter, r *http.Request) { 464 464 user := s.auth.GetUser(r) 465 - f, err := fullyResolvedRepo(r) 465 + f, err := s.fullyResolvedRepo(r) 466 466 if err != nil { 467 467 log.Println("failed to get repo and knot", err) 468 468 return ··· 569 569 570 570 func (s *State) NewPull(w http.ResponseWriter, r *http.Request) { 571 571 user := s.auth.GetUser(r) 572 - f, err := fullyResolvedRepo(r) 572 + f, err := s.fullyResolvedRepo(r) 573 573 if err != nil { 574 574 log.Println("failed to get repo and knot", err) 575 575 return ··· 904 904 } 905 905 906 906 func (s *State) ValidatePatch(w http.ResponseWriter, r *http.Request) { 907 - _, err := fullyResolvedRepo(r) 907 + _, err := s.fullyResolvedRepo(r) 908 908 if err != nil { 909 909 log.Println("failed to get repo and knot", err) 910 910 return ··· 930 930 931 931 func (s *State) PatchUploadFragment(w http.ResponseWriter, r *http.Request) { 932 932 user := s.auth.GetUser(r) 933 - f, err := fullyResolvedRepo(r) 933 + f, err := s.fullyResolvedRepo(r) 934 934 if err != nil { 935 935 log.Println("failed to get repo and knot", err) 936 936 return ··· 943 943 944 944 func (s *State) CompareBranchesFragment(w http.ResponseWriter, r *http.Request) { 945 945 user := s.auth.GetUser(r) 946 - f, err := fullyResolvedRepo(r) 946 + f, err := s.fullyResolvedRepo(r) 947 947 if err != nil { 948 948 log.Println("failed to get repo and knot", err) 949 949 return ··· 983 983 984 984 func (s *State) CompareForksFragment(w http.ResponseWriter, r *http.Request) { 985 985 user := s.auth.GetUser(r) 986 - f, err := fullyResolvedRepo(r) 986 + f, err := s.fullyResolvedRepo(r) 987 987 if err != nil { 988 988 log.Println("failed to get repo and knot", err) 989 989 return ··· 1004 1004 func (s *State) CompareForksBranchesFragment(w http.ResponseWriter, r *http.Request) { 1005 1005 user := s.auth.GetUser(r) 1006 1006 1007 - f, err := fullyResolvedRepo(r) 1007 + f, err := s.fullyResolvedRepo(r) 1008 1008 if err != nil { 1009 1009 log.Println("failed to get repo and knot", err) 1010 1010 return ··· 1082 1082 1083 1083 func (s *State) ResubmitPull(w http.ResponseWriter, r *http.Request) { 1084 1084 user := s.auth.GetUser(r) 1085 - f, err := fullyResolvedRepo(r) 1085 + f, err := s.fullyResolvedRepo(r) 1086 1086 if err != nil { 1087 1087 log.Println("failed to get repo and knot", err) 1088 1088 return ··· 1126 1126 return 1127 1127 } 1128 1128 1129 - f, err := fullyResolvedRepo(r) 1129 + f, err := s.fullyResolvedRepo(r) 1130 1130 if err != nil { 1131 1131 log.Println("failed to get repo and knot", err) 1132 1132 return ··· 1209 1209 return 1210 1210 } 1211 1211 1212 - f, err := fullyResolvedRepo(r) 1212 + f, err := s.fullyResolvedRepo(r) 1213 1213 if err != nil { 1214 1214 log.Println("failed to get repo and knot", err) 1215 1215 return ··· 1322 1322 return 1323 1323 } 1324 1324 1325 - f, err := fullyResolvedRepo(r) 1325 + f, err := s.fullyResolvedRepo(r) 1326 1326 if err != nil { 1327 1327 log.Println("failed to get repo and knot", err) 1328 1328 return ··· 1470 1470 } 1471 1471 1472 1472 func (s *State) MergePull(w http.ResponseWriter, r *http.Request) { 1473 - f, err := fullyResolvedRepo(r) 1473 + f, err := s.fullyResolvedRepo(r) 1474 1474 if err != nil { 1475 1475 log.Println("failed to resolve repo:", err) 1476 1476 s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") ··· 1535 1535 func (s *State) ClosePull(w http.ResponseWriter, r *http.Request) { 1536 1536 user := s.auth.GetUser(r) 1537 1537 1538 - f, err := fullyResolvedRepo(r) 1538 + f, err := s.fullyResolvedRepo(r) 1539 1539 if err != nil { 1540 1540 log.Println("malformed middleware") 1541 1541 return ··· 1589 1589 func (s *State) ReopenPull(w http.ResponseWriter, r *http.Request) { 1590 1590 user := s.auth.GetUser(r) 1591 1591 1592 - f, err := fullyResolvedRepo(r) 1592 + f, err := s.fullyResolvedRepo(r) 1593 1593 if err != nil { 1594 1594 log.Println("failed to resolve repo", err) 1595 1595 s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.")
+35 -40
appview/state/repo.go
··· 37 37 38 38 func (s *State) RepoIndex(w http.ResponseWriter, r *http.Request) { 39 39 ref := chi.URLParam(r, "ref") 40 - f, err := fullyResolvedRepo(r) 40 + f, err := s.fullyResolvedRepo(r) 41 41 if err != nil { 42 42 log.Println("failed to fully resolve repo", err) 43 43 return ··· 129 129 } 130 130 131 131 func (s *State) RepoLog(w http.ResponseWriter, r *http.Request) { 132 - f, err := fullyResolvedRepo(r) 132 + f, err := s.fullyResolvedRepo(r) 133 133 if err != nil { 134 134 log.Println("failed to fully resolve repo", err) 135 135 return ··· 210 210 } 211 211 212 212 func (s *State) RepoDescriptionEdit(w http.ResponseWriter, r *http.Request) { 213 - f, err := fullyResolvedRepo(r) 213 + f, err := s.fullyResolvedRepo(r) 214 214 if err != nil { 215 215 log.Println("failed to get repo and knot", err) 216 216 w.WriteHeader(http.StatusBadRequest) ··· 225 225 } 226 226 227 227 func (s *State) RepoDescription(w http.ResponseWriter, r *http.Request) { 228 - f, err := fullyResolvedRepo(r) 228 + f, err := s.fullyResolvedRepo(r) 229 229 if err != nil { 230 230 log.Println("failed to get repo and knot", err) 231 231 w.WriteHeader(http.StatusBadRequest) ··· 304 304 } 305 305 306 306 func (s *State) RepoCommit(w http.ResponseWriter, r *http.Request) { 307 - f, err := fullyResolvedRepo(r) 307 + f, err := s.fullyResolvedRepo(r) 308 308 if err != nil { 309 309 log.Println("failed to fully resolve repo", err) 310 310 return ··· 350 350 } 351 351 352 352 func (s *State) RepoTree(w http.ResponseWriter, r *http.Request) { 353 - f, err := fullyResolvedRepo(r) 353 + f, err := s.fullyResolvedRepo(r) 354 354 if err != nil { 355 355 log.Println("failed to fully resolve repo", err) 356 356 return ··· 378 378 err = json.Unmarshal(body, &result) 379 379 if err != nil { 380 380 log.Println("failed to parse response:", err) 381 + return 382 + } 383 + 384 + // redirects tree paths trying to access a blob; in this case the result.Files is unpopulated, 385 + // so we can safely redirect to the "parent" (which is the same file). 386 + if len(result.Files) == 0 && result.Parent == treePath { 387 + http.Redirect(w, r, fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), ref, result.Parent), http.StatusFound) 381 388 return 382 389 } 383 390 ··· 406 413 } 407 414 408 415 func (s *State) RepoTags(w http.ResponseWriter, r *http.Request) { 409 - f, err := fullyResolvedRepo(r) 416 + f, err := s.fullyResolvedRepo(r) 410 417 if err != nil { 411 418 log.Println("failed to get repo and knot", err) 412 419 return ··· 447 454 } 448 455 449 456 func (s *State) RepoBranches(w http.ResponseWriter, r *http.Request) { 450 - f, err := fullyResolvedRepo(r) 457 + f, err := s.fullyResolvedRepo(r) 451 458 if err != nil { 452 459 log.Println("failed to get repo and knot", err) 453 460 return ··· 502 509 } 503 510 504 511 func (s *State) RepoBlob(w http.ResponseWriter, r *http.Request) { 505 - f, err := fullyResolvedRepo(r) 512 + f, err := s.fullyResolvedRepo(r) 506 513 if err != nil { 507 514 log.Println("failed to get repo and knot", err) 508 515 return ··· 562 569 } 563 570 564 571 func (s *State) RepoBlobRaw(w http.ResponseWriter, r *http.Request) { 565 - f, err := fullyResolvedRepo(r) 572 + f, err := s.fullyResolvedRepo(r) 566 573 if err != nil { 567 574 log.Println("failed to get repo and knot", err) 568 575 return ··· 606 613 } 607 614 608 615 func (s *State) AddCollaborator(w http.ResponseWriter, r *http.Request) { 609 - f, err := fullyResolvedRepo(r) 616 + f, err := s.fullyResolvedRepo(r) 610 617 if err != nil { 611 618 log.Println("failed to get repo and knot", err) 612 619 return ··· 697 704 func (s *State) DeleteRepo(w http.ResponseWriter, r *http.Request) { 698 705 user := s.auth.GetUser(r) 699 706 700 - f, err := fullyResolvedRepo(r) 707 + f, err := s.fullyResolvedRepo(r) 701 708 if err != nil { 702 709 log.Println("failed to get repo and knot", err) 703 710 return ··· 801 808 } 802 809 803 810 func (s *State) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { 804 - f, err := fullyResolvedRepo(r) 811 + f, err := s.fullyResolvedRepo(r) 805 812 if err != nil { 806 813 log.Println("failed to get repo and knot", err) 807 814 return ··· 840 847 } 841 848 842 849 func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) { 843 - f, err := fullyResolvedRepo(r) 850 + f, err := s.fullyResolvedRepo(r) 844 851 if err != nil { 845 852 log.Println("failed to get repo and knot", err) 846 853 return ··· 891 898 } 892 899 } 893 900 894 - resp, err = us.DefaultBranch(f.OwnerDid(), f.RepoName) 901 + defaultBranchResp, err := us.DefaultBranch(f.OwnerDid(), f.RepoName) 895 902 if err != nil { 896 903 log.Println("failed to reach knotserver", err) 897 904 } else { 898 - defer resp.Body.Close() 899 - 900 - body, err := io.ReadAll(resp.Body) 901 - if err != nil { 902 - log.Printf("Error reading response body: %v", err) 903 - } else { 904 - var result types.RepoDefaultBranchResponse 905 - err = json.Unmarshal(body, &result) 906 - if err != nil { 907 - log.Println("failed to parse response:", err) 908 - } else { 909 - defaultBranch = result.Branch 910 - } 911 - } 905 + defaultBranch = defaultBranchResp.Branch 912 906 } 913 907 } 914 - 915 908 s.pages.RepoSettings(w, pages.RepoSettingsParams{ 916 909 LoggedInUser: user, 917 910 RepoInfo: f.RepoInfo(s, user), ··· 930 923 RepoAt syntax.ATURI 931 924 Description string 932 925 CreatedAt string 926 + Ref string 933 927 } 934 928 935 929 func (f *FullyResolvedRepo) OwnerDid() string { ··· 1082 1076 Name: f.RepoName, 1083 1077 RepoAt: f.RepoAt, 1084 1078 Description: f.Description, 1079 + Ref: f.Ref, 1085 1080 IsStarred: isStarred, 1086 1081 Knot: knot, 1087 1082 Roles: RolesInRepo(s, u, f), ··· 1103 1098 1104 1099 func (s *State) RepoSingleIssue(w http.ResponseWriter, r *http.Request) { 1105 1100 user := s.auth.GetUser(r) 1106 - f, err := fullyResolvedRepo(r) 1101 + f, err := s.fullyResolvedRepo(r) 1107 1102 if err != nil { 1108 1103 log.Println("failed to get repo and knot", err) 1109 1104 return ··· 1157 1152 1158 1153 func (s *State) CloseIssue(w http.ResponseWriter, r *http.Request) { 1159 1154 user := s.auth.GetUser(r) 1160 - f, err := fullyResolvedRepo(r) 1155 + f, err := s.fullyResolvedRepo(r) 1161 1156 if err != nil { 1162 1157 log.Println("failed to get repo and knot", err) 1163 1158 return ··· 1229 1224 1230 1225 func (s *State) ReopenIssue(w http.ResponseWriter, r *http.Request) { 1231 1226 user := s.auth.GetUser(r) 1232 - f, err := fullyResolvedRepo(r) 1227 + f, err := s.fullyResolvedRepo(r) 1233 1228 if err != nil { 1234 1229 log.Println("failed to get repo and knot", err) 1235 1230 return ··· 1277 1272 1278 1273 func (s *State) NewIssueComment(w http.ResponseWriter, r *http.Request) { 1279 1274 user := s.auth.GetUser(r) 1280 - f, err := fullyResolvedRepo(r) 1275 + f, err := s.fullyResolvedRepo(r) 1281 1276 if err != nil { 1282 1277 log.Println("failed to get repo and knot", err) 1283 1278 return ··· 1356 1351 1357 1352 func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) { 1358 1353 user := s.auth.GetUser(r) 1359 - f, err := fullyResolvedRepo(r) 1354 + f, err := s.fullyResolvedRepo(r) 1360 1355 if err != nil { 1361 1356 log.Println("failed to get repo and knot", err) 1362 1357 return ··· 1415 1410 1416 1411 func (s *State) EditIssueComment(w http.ResponseWriter, r *http.Request) { 1417 1412 user := s.auth.GetUser(r) 1418 - f, err := fullyResolvedRepo(r) 1413 + f, err := s.fullyResolvedRepo(r) 1419 1414 if err != nil { 1420 1415 log.Println("failed to get repo and knot", err) 1421 1416 return ··· 1540 1535 1541 1536 func (s *State) DeleteIssueComment(w http.ResponseWriter, r *http.Request) { 1542 1537 user := s.auth.GetUser(r) 1543 - f, err := fullyResolvedRepo(r) 1538 + f, err := s.fullyResolvedRepo(r) 1544 1539 if err != nil { 1545 1540 log.Println("failed to get repo and knot", err) 1546 1541 return ··· 1645 1640 } 1646 1641 1647 1642 user := s.auth.GetUser(r) 1648 - f, err := fullyResolvedRepo(r) 1643 + f, err := s.fullyResolvedRepo(r) 1649 1644 if err != nil { 1650 1645 log.Println("failed to get repo and knot", err) 1651 1646 return ··· 1686 1681 func (s *State) NewIssue(w http.ResponseWriter, r *http.Request) { 1687 1682 user := s.auth.GetUser(r) 1688 1683 1689 - f, err := fullyResolvedRepo(r) 1684 + f, err := s.fullyResolvedRepo(r) 1690 1685 if err != nil { 1691 1686 log.Println("failed to get repo and knot", err) 1692 1687 return ··· 1768 1763 1769 1764 func (s *State) ForkRepo(w http.ResponseWriter, r *http.Request) { 1770 1765 user := s.auth.GetUser(r) 1771 - f, err := fullyResolvedRepo(r) 1766 + f, err := s.fullyResolvedRepo(r) 1772 1767 if err != nil { 1773 1768 log.Printf("failed to resolve source repo: %v", err) 1774 1769 return
+18 -1
appview/state/repo_util.go
··· 17 17 "tangled.sh/tangled.sh/core/appview/pages" 18 18 ) 19 19 20 - func fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) { 20 + func (s *State) fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) { 21 21 repoName := chi.URLParam(r, "repo") 22 22 knot, ok := r.Context().Value("knot").(string) 23 23 if !ok { ··· 42 42 return nil, fmt.Errorf("malformed middleware") 43 43 } 44 44 45 + ref := chi.URLParam(r, "ref") 46 + 47 + if ref == "" { 48 + us, err := NewUnsignedClient(knot, s.config.Dev) 49 + if err != nil { 50 + return nil, err 51 + } 52 + 53 + defaultBranch, err := us.DefaultBranch(id.DID.String(), repoName) 54 + if err != nil { 55 + return nil, err 56 + } 57 + 58 + ref = defaultBranch.Branch 59 + } 60 + 45 61 // pass through values from the middleware 46 62 description, ok := r.Context().Value("repoDescription").(string) 47 63 addedAt, ok := r.Context().Value("repoAddedAt").(string) ··· 53 69 RepoAt: parsedRepoAt, 54 70 Description: description, 55 71 CreatedAt: addedAt, 72 + Ref: ref, 56 73 }, nil 57 74 } 58 75
+13 -2
appview/state/signer.go
··· 380 380 return us.client.Do(req) 381 381 } 382 382 383 - func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*http.Response, error) { 383 + func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*types.RepoDefaultBranchResponse, error) { 384 384 const ( 385 385 Method = "GET" 386 386 ) ··· 392 392 return nil, err 393 393 } 394 394 395 - return us.client.Do(req) 395 + resp, err := us.client.Do(req) 396 + if err != nil { 397 + return nil, err 398 + } 399 + defer resp.Body.Close() 400 + 401 + var defaultBranch types.RepoDefaultBranchResponse 402 + if err := json.NewDecoder(resp.Body).Decode(&defaultBranch); err != nil { 403 + return nil, err 404 + } 405 + 406 + return &defaultBranch, nil 396 407 } 397 408 398 409 func (us *UnsignedClient) Capabilities() (*types.Capabilities, error) {