Monorepo for Tangled tangled.org

appview: pages/markup: resolve relative links

+43
appview/pages/markup/markdown.go
··· 3 3 4 4 import ( 5 5 "bytes" 6 + "fmt" 6 7 7 8 "github.com/yuin/goldmark" 9 + "github.com/yuin/goldmark/ast" 8 10 "github.com/yuin/goldmark/extension" 9 11 "github.com/yuin/goldmark/parser" 12 + "github.com/yuin/goldmark/text" 13 + "github.com/yuin/goldmark/util" 10 14 ) 11 15 12 16 func RenderMarkdown(source string) string { ··· 22 26 } 23 27 return buf.String() 24 28 } 29 + 30 + type RelativeLinkTransformer struct { 31 + User string 32 + Repo string 33 + Ref string 34 + } 35 + 36 + func (t *RelativeLinkTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) { 37 + ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) { 38 + if !entering { 39 + fmt.Printf("Node: %T\n", n) 40 + } 41 + return ast.WalkContinue, nil 42 + }) 43 + } 44 + 45 + func RenderMarkdownExtended(source, user, repo, ref string) string { 46 + md := goldmark.New( 47 + goldmark.WithExtensions(extension.GFM), 48 + goldmark.WithParserOptions(parser.WithAutoHeadingID()), 49 + goldmark.WithParser( 50 + parser.NewParser( 51 + parser.WithASTTransformers( 52 + util.Prioritized(&RelativeLinkTransformer{ 53 + User: user, 54 + Repo: repo, 55 + Ref: ref, 56 + }, 999), 57 + ), 58 + ), 59 + ), 60 + ) 61 + 62 + var buf bytes.Buffer 63 + if err := md.Convert([]byte(source), &buf); err != nil { 64 + return source 65 + } 66 + return buf.String() 67 + }
appview/pages/markup/readme.go appview/pages/markup/format.go
+13 -2
appview/pages/pages.go
··· 231 231 OwnerHandle string 232 232 Description string 233 233 Knot string 234 + Ref string 234 235 RepoAt syntax.ATURI 235 236 IsStarred bool 236 237 Stats db.RepoStats ··· 351 352 ext := filepath.Ext(params.ReadmeFileName) 352 353 switch ext { 353 354 case ".md", ".markdown", ".mdown", ".mkdn", ".mkd": 354 - htmlString = markup.RenderMarkdown(params.Readme) 355 + htmlString = markup.RenderMarkdownExtended( 356 + params.Readme, 357 + params.RepoInfo.OwnerHandle, 358 + params.RepoInfo.Name, 359 + params.RepoInfo.Ref, 360 + ) 355 361 params.Raw = false 356 362 params.HTMLReadme = template.HTML(bluemonday.UGCPolicy().Sanitize(htmlString)) 357 363 default: ··· 466 472 if params.ShowRendered { 467 473 switch markup.GetFormat(params.Path) { 468 474 case markup.FormatMarkdown: 469 - params.RenderedContents = template.HTML(markup.RenderMarkdown(params.Contents)) 475 + params.RenderedContents = template.HTML(markup.RenderMarkdownExtended( 476 + params.Contents, 477 + params.RepoInfo.OwnerHandle, 478 + params.RepoInfo.Name, 479 + params.RepoInfo.Ref, 480 + )) 470 481 } 471 482 } 472 483
+2 -2
appview/state/middleware.go
··· 144 144 http.Error(w, "Forbiden", http.StatusUnauthorized) 145 145 return 146 146 } 147 - f, err := fullyResolvedRepo(r) 147 + f, err := s.fullyResolvedRepo(r) 148 148 if err != nil { 149 149 http.Error(w, "malformed url", http.StatusBadRequest) 150 150 return ··· 225 225 func ResolvePull(s *State) Middleware { 226 226 return func(next http.Handler) http.Handler { 227 227 return http.HandlerFunc(func(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 fully resolve repo", err) 231 231 http.Error(w, "invalid repo url", http.StatusNotFound)
+14 -14
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 ··· 262 262 263 263 func (s *State) RepoPullPatch(w http.ResponseWriter, r *http.Request) { 264 264 user := s.auth.GetUser(r) 265 - f, err := fullyResolvedRepo(r) 265 + f, err := s.fullyResolvedRepo(r) 266 266 if err != nil { 267 267 log.Println("failed to get repo and knot", err) 268 268 return ··· 349 349 state = db.PullMerged 350 350 } 351 351 352 - f, err := fullyResolvedRepo(r) 352 + f, err := s.fullyResolvedRepo(r) 353 353 if err != nil { 354 354 log.Println("failed to get repo and knot", err) 355 355 return ··· 402 402 403 403 func (s *State) PullComment(w http.ResponseWriter, r *http.Request) { 404 404 user := s.auth.GetUser(r) 405 - f, err := fullyResolvedRepo(r) 405 + f, err := s.fullyResolvedRepo(r) 406 406 if err != nil { 407 407 log.Println("failed to get repo and knot", err) 408 408 return ··· 509 509 510 510 func (s *State) NewPull(w http.ResponseWriter, r *http.Request) { 511 511 user := s.auth.GetUser(r) 512 - f, err := fullyResolvedRepo(r) 512 + f, err := s.fullyResolvedRepo(r) 513 513 if err != nil { 514 514 log.Println("failed to get repo and knot", err) 515 515 return ··· 842 842 843 843 func (s *State) PatchUploadFragment(w http.ResponseWriter, r *http.Request) { 844 844 user := s.auth.GetUser(r) 845 - f, err := fullyResolvedRepo(r) 845 + f, err := s.fullyResolvedRepo(r) 846 846 if err != nil { 847 847 log.Println("failed to get repo and knot", err) 848 848 return ··· 855 855 856 856 func (s *State) CompareBranchesFragment(w http.ResponseWriter, r *http.Request) { 857 857 user := s.auth.GetUser(r) 858 - f, err := fullyResolvedRepo(r) 858 + f, err := s.fullyResolvedRepo(r) 859 859 if err != nil { 860 860 log.Println("failed to get repo and knot", err) 861 861 return ··· 895 895 896 896 func (s *State) CompareForksFragment(w http.ResponseWriter, r *http.Request) { 897 897 user := s.auth.GetUser(r) 898 - f, err := fullyResolvedRepo(r) 898 + f, err := s.fullyResolvedRepo(r) 899 899 if err != nil { 900 900 log.Println("failed to get repo and knot", err) 901 901 return ··· 916 916 func (s *State) CompareForksBranchesFragment(w http.ResponseWriter, r *http.Request) { 917 917 user := s.auth.GetUser(r) 918 918 919 - f, err := fullyResolvedRepo(r) 919 + f, err := s.fullyResolvedRepo(r) 920 920 if err != nil { 921 921 log.Println("failed to get repo and knot", err) 922 922 return ··· 994 994 995 995 func (s *State) ResubmitPull(w http.ResponseWriter, r *http.Request) { 996 996 user := s.auth.GetUser(r) 997 - f, err := fullyResolvedRepo(r) 997 + f, err := s.fullyResolvedRepo(r) 998 998 if err != nil { 999 999 log.Println("failed to get repo and knot", err) 1000 1000 return ··· 1193 1193 } 1194 1194 1195 1195 func (s *State) MergePull(w http.ResponseWriter, r *http.Request) { 1196 - f, err := fullyResolvedRepo(r) 1196 + f, err := s.fullyResolvedRepo(r) 1197 1197 if err != nil { 1198 1198 log.Println("failed to resolve repo:", err) 1199 1199 s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") ··· 1258 1258 func (s *State) ClosePull(w http.ResponseWriter, r *http.Request) { 1259 1259 user := s.auth.GetUser(r) 1260 1260 1261 - f, err := fullyResolvedRepo(r) 1261 + f, err := s.fullyResolvedRepo(r) 1262 1262 if err != nil { 1263 1263 log.Println("malformed middleware") 1264 1264 return ··· 1312 1312 func (s *State) ReopenPull(w http.ResponseWriter, r *http.Request) { 1313 1313 user := s.auth.GetUser(r) 1314 1314 1315 - f, err := fullyResolvedRepo(r) 1315 + f, err := s.fullyResolvedRepo(r) 1316 1316 if err != nil { 1317 1317 log.Println("failed to resolve repo", err) 1318 1318 s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.")
+34 -39
appview/state/repo.go
··· 34 34 35 35 func (s *State) RepoIndex(w http.ResponseWriter, r *http.Request) { 36 36 ref := chi.URLParam(r, "ref") 37 - f, err := fullyResolvedRepo(r) 37 + f, err := s.fullyResolvedRepo(r) 38 38 if err != nil { 39 39 log.Println("failed to fully resolve repo", err) 40 40 return ··· 93 93 } 94 94 95 95 func (s *State) RepoLog(w http.ResponseWriter, r *http.Request) { 96 - f, err := fullyResolvedRepo(r) 96 + f, err := s.fullyResolvedRepo(r) 97 97 if err != nil { 98 98 log.Println("failed to fully resolve repo", err) 99 99 return ··· 144 144 } 145 145 146 146 func (s *State) RepoDescriptionEdit(w http.ResponseWriter, r *http.Request) { 147 - f, err := fullyResolvedRepo(r) 147 + f, err := s.fullyResolvedRepo(r) 148 148 if err != nil { 149 149 log.Println("failed to get repo and knot", err) 150 150 w.WriteHeader(http.StatusBadRequest) ··· 159 159 } 160 160 161 161 func (s *State) RepoDescription(w http.ResponseWriter, r *http.Request) { 162 - f, err := fullyResolvedRepo(r) 162 + f, err := s.fullyResolvedRepo(r) 163 163 if err != nil { 164 164 log.Println("failed to get repo and knot", err) 165 165 w.WriteHeader(http.StatusBadRequest) ··· 238 238 } 239 239 240 240 func (s *State) RepoCommit(w http.ResponseWriter, r *http.Request) { 241 - f, err := fullyResolvedRepo(r) 241 + f, err := s.fullyResolvedRepo(r) 242 242 if err != nil { 243 243 log.Println("failed to fully resolve repo", err) 244 244 return ··· 278 278 } 279 279 280 280 func (s *State) RepoTree(w http.ResponseWriter, r *http.Request) { 281 - f, err := fullyResolvedRepo(r) 281 + f, err := s.fullyResolvedRepo(r) 282 282 if err != nil { 283 283 log.Println("failed to fully resolve repo", err) 284 284 return ··· 309 309 return 310 310 } 311 311 312 + if result.Parent == treePath { 313 + fmt.Println("here") 314 + http.Redirect(w, r, fmt.Sprintf("/%s/%s/blob/%s/%s", f.OwnerHandle(), f.RepoName, ref, treePath), http.StatusPermanentRedirect) 315 + return 316 + } 317 + 312 318 user := s.auth.GetUser(r) 313 319 314 320 var breadcrumbs [][]string ··· 334 340 } 335 341 336 342 func (s *State) RepoTags(w http.ResponseWriter, r *http.Request) { 337 - f, err := fullyResolvedRepo(r) 343 + f, err := s.fullyResolvedRepo(r) 338 344 if err != nil { 339 345 log.Println("failed to get repo and knot", err) 340 346 return ··· 374 380 } 375 381 376 382 func (s *State) RepoBranches(w http.ResponseWriter, r *http.Request) { 377 - f, err := fullyResolvedRepo(r) 383 + f, err := s.fullyResolvedRepo(r) 378 384 if err != nil { 379 385 log.Println("failed to get repo and knot", err) 380 386 return ··· 415 421 } 416 422 417 423 func (s *State) RepoBlob(w http.ResponseWriter, r *http.Request) { 418 - f, err := fullyResolvedRepo(r) 424 + f, err := s.fullyResolvedRepo(r) 419 425 if err != nil { 420 426 log.Println("failed to get repo and knot", err) 421 427 return ··· 475 481 } 476 482 477 483 func (s *State) RepoBlobRaw(w http.ResponseWriter, r *http.Request) { 478 - f, err := fullyResolvedRepo(r) 484 + f, err := s.fullyResolvedRepo(r) 479 485 if err != nil { 480 486 log.Println("failed to get repo and knot", err) 481 487 return ··· 519 525 } 520 526 521 527 func (s *State) AddCollaborator(w http.ResponseWriter, r *http.Request) { 522 - f, err := fullyResolvedRepo(r) 528 + f, err := s.fullyResolvedRepo(r) 523 529 if err != nil { 524 530 log.Println("failed to get repo and knot", err) 525 531 return ··· 610 616 func (s *State) DeleteRepo(w http.ResponseWriter, r *http.Request) { 611 617 user := s.auth.GetUser(r) 612 618 613 - f, err := fullyResolvedRepo(r) 619 + f, err := s.fullyResolvedRepo(r) 614 620 if err != nil { 615 621 log.Println("failed to get repo and knot", err) 616 622 return ··· 714 720 } 715 721 716 722 func (s *State) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { 717 - f, err := fullyResolvedRepo(r) 723 + f, err := s.fullyResolvedRepo(r) 718 724 if err != nil { 719 725 log.Println("failed to get repo and knot", err) 720 726 return ··· 753 759 } 754 760 755 761 func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) { 756 - f, err := fullyResolvedRepo(r) 762 + f, err := s.fullyResolvedRepo(r) 757 763 if err != nil { 758 764 log.Println("failed to get repo and knot", err) 759 765 return ··· 804 810 } 805 811 } 806 812 807 - resp, err = us.DefaultBranch(f.OwnerDid(), f.RepoName) 813 + result, err := us.DefaultBranch(f.OwnerDid(), f.RepoName) 808 814 if err != nil { 809 815 log.Println("failed to reach knotserver", err) 810 816 } else { 811 - defer resp.Body.Close() 812 - 813 - body, err := io.ReadAll(resp.Body) 814 - if err != nil { 815 - log.Printf("Error reading response body: %v", err) 816 - } else { 817 - var result types.RepoDefaultBranchResponse 818 - err = json.Unmarshal(body, &result) 819 - if err != nil { 820 - log.Println("failed to parse response:", err) 821 - } else { 822 - defaultBranch = result.Branch 823 - } 824 - } 817 + defaultBranch = result.Branch 825 818 } 826 819 } 827 820 ··· 840 833 Knot string 841 834 OwnerId identity.Identity 842 835 RepoName string 836 + Ref string 843 837 RepoAt syntax.ATURI 844 838 Description string 845 839 AddedAt string ··· 984 978 Description: f.Description, 985 979 IsStarred: isStarred, 986 980 Knot: knot, 981 + Ref: f.Ref, 987 982 Roles: RolesInRepo(s, u, f), 988 983 Stats: db.RepoStats{ 989 984 StarCount: starCount, ··· 1003 998 1004 999 func (s *State) RepoSingleIssue(w http.ResponseWriter, r *http.Request) { 1005 1000 user := s.auth.GetUser(r) 1006 - f, err := fullyResolvedRepo(r) 1001 + f, err := s.fullyResolvedRepo(r) 1007 1002 if err != nil { 1008 1003 log.Println("failed to get repo and knot", err) 1009 1004 return ··· 1057 1052 1058 1053 func (s *State) CloseIssue(w http.ResponseWriter, r *http.Request) { 1059 1054 user := s.auth.GetUser(r) 1060 - f, err := fullyResolvedRepo(r) 1055 + f, err := s.fullyResolvedRepo(r) 1061 1056 if err != nil { 1062 1057 log.Println("failed to get repo and knot", err) 1063 1058 return ··· 1129 1124 1130 1125 func (s *State) ReopenIssue(w http.ResponseWriter, r *http.Request) { 1131 1126 user := s.auth.GetUser(r) 1132 - f, err := fullyResolvedRepo(r) 1127 + f, err := s.fullyResolvedRepo(r) 1133 1128 if err != nil { 1134 1129 log.Println("failed to get repo and knot", err) 1135 1130 return ··· 1177 1172 1178 1173 func (s *State) NewIssueComment(w http.ResponseWriter, r *http.Request) { 1179 1174 user := s.auth.GetUser(r) 1180 - f, err := fullyResolvedRepo(r) 1175 + f, err := s.fullyResolvedRepo(r) 1181 1176 if err != nil { 1182 1177 log.Println("failed to get repo and knot", err) 1183 1178 return ··· 1256 1251 1257 1252 func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) { 1258 1253 user := s.auth.GetUser(r) 1259 - f, err := fullyResolvedRepo(r) 1254 + f, err := s.fullyResolvedRepo(r) 1260 1255 if err != nil { 1261 1256 log.Println("failed to get repo and knot", err) 1262 1257 return ··· 1315 1310 1316 1311 func (s *State) EditIssueComment(w http.ResponseWriter, r *http.Request) { 1317 1312 user := s.auth.GetUser(r) 1318 - f, err := fullyResolvedRepo(r) 1313 + f, err := s.fullyResolvedRepo(r) 1319 1314 if err != nil { 1320 1315 log.Println("failed to get repo and knot", err) 1321 1316 return ··· 1440 1435 1441 1436 func (s *State) DeleteIssueComment(w http.ResponseWriter, r *http.Request) { 1442 1437 user := s.auth.GetUser(r) 1443 - f, err := fullyResolvedRepo(r) 1438 + f, err := s.fullyResolvedRepo(r) 1444 1439 if err != nil { 1445 1440 log.Println("failed to get repo and knot", err) 1446 1441 return ··· 1539 1534 } 1540 1535 1541 1536 user := s.auth.GetUser(r) 1542 - f, err := fullyResolvedRepo(r) 1537 + f, err := s.fullyResolvedRepo(r) 1543 1538 if err != nil { 1544 1539 log.Println("failed to get repo and knot", err) 1545 1540 return ··· 1579 1574 func (s *State) NewIssue(w http.ResponseWriter, r *http.Request) { 1580 1575 user := s.auth.GetUser(r) 1581 1576 1582 - f, err := fullyResolvedRepo(r) 1577 + f, err := s.fullyResolvedRepo(r) 1583 1578 if err != nil { 1584 1579 log.Println("failed to get repo and knot", err) 1585 1580 return ··· 1661 1656 1662 1657 func (s *State) ForkRepo(w http.ResponseWriter, r *http.Request) { 1663 1658 user := s.auth.GetUser(r) 1664 - f, err := fullyResolvedRepo(r) 1659 + f, err := s.fullyResolvedRepo(r) 1665 1660 if err != nil { 1666 1661 log.Printf("failed to resolve source repo: %v", err) 1667 1662 return
+19 -2
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 + ref := chi.URLParam(r, "ref") 23 + 22 24 knot, ok := r.Context().Value("knot").(string) 23 25 if !ok { 24 26 log.Println("malformed middleware") ··· 36 38 return nil, fmt.Errorf("malformed middleware") 37 39 } 38 40 41 + if ref == "" { 42 + us, err := NewUnsignedClient(knot, s.config.Dev) 43 + if err != nil { 44 + return nil, fmt.Errorf("failed to setup new signed client: %w", err) 45 + } 46 + 47 + def, err := us.DefaultBranch(id.DID.String(), repoName) 48 + if err != nil { 49 + return nil, fmt.Errorf("failed to get default branch: %w", err) 50 + } 51 + 52 + ref = def.Branch 53 + } 54 + 39 55 parsedRepoAt, err := syntax.ParseATURI(repoAt) 40 56 if err != nil { 41 57 log.Println("malformed repo at-uri") 42 - return nil, fmt.Errorf("malformed middleware") 58 + return nil, fmt.Errorf("malformed middleware: %w", err) 43 59 } 44 60 45 61 // pass through values from the middleware ··· 53 69 RepoAt: parsedRepoAt, 54 70 Description: description, 55 71 AddedAt: addedAt, 72 + Ref: ref, 56 73 }, nil 57 74 } 58 75
+13 -2
appview/state/signer.go
··· 336 336 return us.client.Do(req) 337 337 } 338 338 339 - func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*http.Response, error) { 339 + func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*types.RepoDefaultBranchResponse, error) { 340 340 const ( 341 341 Method = "GET" 342 342 ) ··· 348 348 return nil, err 349 349 } 350 350 351 - return us.client.Do(req) 351 + resp, err := us.client.Do(req) 352 + if err != nil { 353 + return nil, err 354 + } 355 + defer resp.Body.Close() 356 + 357 + var branchResponse types.RepoDefaultBranchResponse 358 + if err := json.NewDecoder(resp.Body).Decode(&branchResponse); err != nil { 359 + return nil, err 360 + } 361 + 362 + return &branchResponse, nil 352 363 } 353 364 354 365 func (us *UnsignedClient) Capabilities() (*types.Capabilities, error) {
+1 -1
go.mod
··· 25 25 github.com/resend/resend-go/v2 v2.15.0 26 26 github.com/sethvargo/go-envconfig v1.1.0 27 27 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e 28 - github.com/yuin/goldmark v1.4.13 28 + github.com/yuin/goldmark v1.7.8 29 29 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 30 30 ) 31 31
+2 -1
go.sum
··· 262 262 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 263 263 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 264 264 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 265 - github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= 266 265 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 266 + github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= 267 + github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= 267 268 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= 268 269 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 269 270 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q=