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

appview: pages/markup: resolve relative links in markdown

authored by anirudh.fi and committed by

Tangled 04e2bc40 8eba71c6

+106 -78
+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 ··· 66 73 67 74 switch a.rctx.RendererType { 68 75 case RendererTypeRepoMarkdown: 69 - a.rctx.relativeLinkTransformer(n.(*ast.Link)) 70 - case RendererTypeDefault: 71 - a.rctx.relativeLinkTransformer(n.(*ast.Link)) 76 + if v, ok := n.(*ast.Link); ok { 77 + a.rctx.relativeLinkTransformer(v) 78 + } 72 79 // more types here like RendererTypeIssue/Pull etc. 73 80 } 74 81
+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 ··· 479 478 return p.executeRepo("repo/empty", w, params) 480 479 } 481 480 481 + rctx := markup.RenderContext{ 482 + Ref: params.RepoInfo.Ref, 483 + FullRepoName: params.RepoInfo.FullName(), 484 + } 485 + 482 486 if params.ReadmeFileName != "" { 483 487 var htmlString string 484 488 ext := filepath.Ext(params.ReadmeFileName) 485 489 switch ext { 486 490 case ".md", ".markdown", ".mdown", ".mkdn", ".mkd": 487 - htmlString = markup.RenderMarkdown(params.Readme) 491 + htmlString = rctx.RenderMarkdown(params.Readme) 488 492 params.Raw = false 489 493 params.HTMLReadme = template.HTML(bluemonday.UGCPolicy().Sanitize(htmlString)) 490 494 default: ··· 607 601 if params.ShowRendered { 608 602 switch markup.GetFormat(params.Path) { 609 603 case markup.FormatMarkdown: 610 - params.RenderedContents = template.HTML(markup.RenderMarkdown(params.Contents)) 604 + rctx := markup.RenderContext{ 605 + Ref: params.RepoInfo.Ref, 606 + FullRepoName: params.RepoInfo.FullName(), 607 + RendererType: markup.RendererTypeRepoMarkdown, 608 + } 609 + params.RenderedContents = template.HTML(rctx.RenderMarkdown(params.Contents)) 611 610 } 612 611 } 613 612
+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 ··· 381 381 return 382 382 } 383 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) 388 + return 389 + } 390 + 384 391 user := s.auth.GetUser(r) 385 392 386 393 var breadcrumbs [][]string ··· 413 406 } 414 407 415 408 func (s *State) RepoTags(w http.ResponseWriter, r *http.Request) { 416 - f, err := fullyResolvedRepo(r) 409 + f, err := s.fullyResolvedRepo(r) 417 410 if err != nil { 418 411 log.Println("failed to get repo and knot", err) 419 412 return ··· 454 447 } 455 448 456 449 func (s *State) RepoBranches(w http.ResponseWriter, r *http.Request) { 457 - f, err := fullyResolvedRepo(r) 450 + f, err := s.fullyResolvedRepo(r) 458 451 if err != nil { 459 452 log.Println("failed to get repo and knot", err) 460 453 return ··· 509 502 } 510 503 511 504 func (s *State) RepoBlob(w http.ResponseWriter, r *http.Request) { 512 - f, err := fullyResolvedRepo(r) 505 + f, err := s.fullyResolvedRepo(r) 513 506 if err != nil { 514 507 log.Println("failed to get repo and knot", err) 515 508 return ··· 569 562 } 570 563 571 564 func (s *State) RepoBlobRaw(w http.ResponseWriter, r *http.Request) { 572 - f, err := fullyResolvedRepo(r) 565 + f, err := s.fullyResolvedRepo(r) 573 566 if err != nil { 574 567 log.Println("failed to get repo and knot", err) 575 568 return ··· 613 606 } 614 607 615 608 func (s *State) AddCollaborator(w http.ResponseWriter, r *http.Request) { 616 - f, err := fullyResolvedRepo(r) 609 + f, err := s.fullyResolvedRepo(r) 617 610 if err != nil { 618 611 log.Println("failed to get repo and knot", err) 619 612 return ··· 704 697 func (s *State) DeleteRepo(w http.ResponseWriter, r *http.Request) { 705 698 user := s.auth.GetUser(r) 706 699 707 - f, err := fullyResolvedRepo(r) 700 + f, err := s.fullyResolvedRepo(r) 708 701 if err != nil { 709 702 log.Println("failed to get repo and knot", err) 710 703 return ··· 808 801 } 809 802 810 803 func (s *State) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { 811 - f, err := fullyResolvedRepo(r) 804 + f, err := s.fullyResolvedRepo(r) 812 805 if err != nil { 813 806 log.Println("failed to get repo and knot", err) 814 807 return ··· 847 840 } 848 841 849 842 func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) { 850 - f, err := fullyResolvedRepo(r) 843 + f, err := s.fullyResolvedRepo(r) 851 844 if err != nil { 852 845 log.Println("failed to get repo and knot", err) 853 846 return ··· 898 891 } 899 892 } 900 893 901 - resp, err = us.DefaultBranch(f.OwnerDid(), f.RepoName) 894 + defaultBranchResp, err := us.DefaultBranch(f.OwnerDid(), f.RepoName) 902 895 if err != nil { 903 896 log.Println("failed to reach knotserver", err) 904 897 } else { 905 - defer resp.Body.Close() 906 - 907 - body, err := io.ReadAll(resp.Body) 908 - if err != nil { 909 - log.Printf("Error reading response body: %v", err) 910 - } else { 911 - var result types.RepoDefaultBranchResponse 912 - err = json.Unmarshal(body, &result) 913 - if err != nil { 914 - log.Println("failed to parse response:", err) 915 - } else { 916 - defaultBranch = result.Branch 917 - } 918 - } 898 + defaultBranch = defaultBranchResp.Branch 919 899 } 920 900 } 921 - 922 901 s.pages.RepoSettings(w, pages.RepoSettingsParams{ 923 902 LoggedInUser: user, 924 903 RepoInfo: f.RepoInfo(s, user), ··· 923 930 RepoAt syntax.ATURI 924 931 Description string 925 932 CreatedAt string 933 + Ref string 926 934 } 927 935 928 936 func (f *FullyResolvedRepo) OwnerDid() string { ··· 1076 1082 Name: f.RepoName, 1077 1083 RepoAt: f.RepoAt, 1078 1084 Description: f.Description, 1085 + Ref: f.Ref, 1079 1086 IsStarred: isStarred, 1080 1087 Knot: knot, 1081 1088 Roles: RolesInRepo(s, u, f), ··· 1098 1103 1099 1104 func (s *State) RepoSingleIssue(w http.ResponseWriter, r *http.Request) { 1100 1105 user := s.auth.GetUser(r) 1101 - f, err := fullyResolvedRepo(r) 1106 + f, err := s.fullyResolvedRepo(r) 1102 1107 if err != nil { 1103 1108 log.Println("failed to get repo and knot", err) 1104 1109 return ··· 1152 1157 1153 1158 func (s *State) CloseIssue(w http.ResponseWriter, r *http.Request) { 1154 1159 user := s.auth.GetUser(r) 1155 - f, err := fullyResolvedRepo(r) 1160 + f, err := s.fullyResolvedRepo(r) 1156 1161 if err != nil { 1157 1162 log.Println("failed to get repo and knot", err) 1158 1163 return ··· 1224 1229 1225 1230 func (s *State) ReopenIssue(w http.ResponseWriter, r *http.Request) { 1226 1231 user := s.auth.GetUser(r) 1227 - f, err := fullyResolvedRepo(r) 1232 + f, err := s.fullyResolvedRepo(r) 1228 1233 if err != nil { 1229 1234 log.Println("failed to get repo and knot", err) 1230 1235 return ··· 1272 1277 1273 1278 func (s *State) NewIssueComment(w http.ResponseWriter, r *http.Request) { 1274 1279 user := s.auth.GetUser(r) 1275 - f, err := fullyResolvedRepo(r) 1280 + f, err := s.fullyResolvedRepo(r) 1276 1281 if err != nil { 1277 1282 log.Println("failed to get repo and knot", err) 1278 1283 return ··· 1351 1356 1352 1357 func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) { 1353 1358 user := s.auth.GetUser(r) 1354 - f, err := fullyResolvedRepo(r) 1359 + f, err := s.fullyResolvedRepo(r) 1355 1360 if err != nil { 1356 1361 log.Println("failed to get repo and knot", err) 1357 1362 return ··· 1410 1415 1411 1416 func (s *State) EditIssueComment(w http.ResponseWriter, r *http.Request) { 1412 1417 user := s.auth.GetUser(r) 1413 - f, err := fullyResolvedRepo(r) 1418 + f, err := s.fullyResolvedRepo(r) 1414 1419 if err != nil { 1415 1420 log.Println("failed to get repo and knot", err) 1416 1421 return ··· 1535 1540 1536 1541 func (s *State) DeleteIssueComment(w http.ResponseWriter, r *http.Request) { 1537 1542 user := s.auth.GetUser(r) 1538 - f, err := fullyResolvedRepo(r) 1543 + f, err := s.fullyResolvedRepo(r) 1539 1544 if err != nil { 1540 1545 log.Println("failed to get repo and knot", err) 1541 1546 return ··· 1640 1645 } 1641 1646 1642 1647 user := s.auth.GetUser(r) 1643 - f, err := fullyResolvedRepo(r) 1648 + f, err := s.fullyResolvedRepo(r) 1644 1649 if err != nil { 1645 1650 log.Println("failed to get repo and knot", err) 1646 1651 return ··· 1681 1686 func (s *State) NewIssue(w http.ResponseWriter, r *http.Request) { 1682 1687 user := s.auth.GetUser(r) 1683 1688 1684 - f, err := fullyResolvedRepo(r) 1689 + f, err := s.fullyResolvedRepo(r) 1685 1690 if err != nil { 1686 1691 log.Println("failed to get repo and knot", err) 1687 1692 return ··· 1763 1768 1764 1769 func (s *State) ForkRepo(w http.ResponseWriter, r *http.Request) { 1765 1770 user := s.auth.GetUser(r) 1766 - f, err := fullyResolvedRepo(r) 1771 + f, err := s.fullyResolvedRepo(r) 1767 1772 if err != nil { 1768 1773 log.Printf("failed to resolve source repo: %v", err) 1769 1774 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) ··· 69 53 RepoAt: parsedRepoAt, 70 54 Description: description, 71 55 CreatedAt: addedAt, 56 + Ref: ref, 72 57 }, nil 73 58 } 74 59
+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) {