forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

appview: pages/markup: resolve relative links in markdown

Changed files
+106 -78
appview
+2 -1
appview/pages/funcmap.go
···
return v.Slice(start, end).Interface()
},
"markdown": func(text string) template.HTML {
-
return template.HTML(markup.RenderMarkdown(text))
+
rctx := &markup.RenderContext{}
+
return template.HTML(rctx.RenderMarkdown(text))
},
"isNil": func(t any) bool {
// returns false for other "zero" values
+4 -11
appview/pages/markup/markdown.go
···
const (
// RendererTypeRepoMarkdown is for repository documentation markdown files
RendererTypeRepoMarkdown RendererType = iota
-
// RendererTypeIssueComment is for issue comments
-
RendererTypeIssueComment
-
// RendererTypePullComment is for pull request comments
-
RendererTypePullComment
-
// RendererTypeDefault is the default renderer with minimal transformations
-
RendererTypeDefault
)
// RenderContext holds the contextual data for rendering markdown.
-
// It can be initialized empty, and that'll skip any transformations
-
// and use the default renderer (RendererTypeDefault).
+
// It can be initialized empty, and that'll skip any transformations.
type RenderContext struct {
Ref string
FullRepoName string
···
switch a.rctx.RendererType {
case RendererTypeRepoMarkdown:
-
a.rctx.relativeLinkTransformer(n.(*ast.Link))
-
case RendererTypeDefault:
-
a.rctx.relativeLinkTransformer(n.(*ast.Link))
+
if v, ok := n.(*ast.Link); ok {
+
a.rctx.relativeLinkTransformer(v)
+
}
// more types here like RendererTypeIssue/Pull etc.
}
+13 -2
appview/pages/pages.go
···
Roles RolesInRepo
Source *db.Repo
SourceHandle string
+
Ref string
DisableFork bool
}
···
return p.executeRepo("repo/empty", w, params)
}
+
rctx := markup.RenderContext{
+
Ref: params.RepoInfo.Ref,
+
FullRepoName: params.RepoInfo.FullName(),
+
}
+
if params.ReadmeFileName != "" {
var htmlString string
ext := filepath.Ext(params.ReadmeFileName)
switch ext {
case ".md", ".markdown", ".mdown", ".mkdn", ".mkd":
-
htmlString = markup.RenderMarkdown(params.Readme)
+
htmlString = rctx.RenderMarkdown(params.Readme)
params.Raw = false
params.HTMLReadme = template.HTML(bluemonday.UGCPolicy().Sanitize(htmlString))
default:
···
if params.ShowRendered {
switch markup.GetFormat(params.Path) {
case markup.FormatMarkdown:
-
params.RenderedContents = template.HTML(markup.RenderMarkdown(params.Contents))
+
rctx := markup.RenderContext{
+
Ref: params.RepoInfo.Ref,
+
FullRepoName: params.RepoInfo.FullName(),
+
RendererType: markup.RendererTypeRepoMarkdown,
+
}
+
params.RenderedContents = template.HTML(rctx.RenderMarkdown(params.Contents))
}
}
+2 -2
appview/state/middleware.go
···
http.Error(w, "Forbiden", http.StatusUnauthorized)
return
}
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
http.Error(w, "malformed url", http.StatusBadRequest)
return
···
func ResolvePull(s *State) middleware.Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to fully resolve repo", err)
http.Error(w, "invalid repo url", http.StatusNotFound)
+19 -19
appview/state/pull.go
···
switch r.Method {
case http.MethodGet:
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) RepoSinglePull(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) RepoPullPatch(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) RepoPullInterdiff(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
state = db.PullMerged
}
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) PullComment(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) NewPull(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
}
func (s *State) ValidatePatch(w http.ResponseWriter, r *http.Request) {
-
_, err := fullyResolvedRepo(r)
+
_, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) PatchUploadFragment(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) CompareBranchesFragment(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) CompareForksFragment(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) CompareForksBranchesFragment(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) ResubmitPull(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
return
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
return
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
return
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) MergePull(w http.ResponseWriter, r *http.Request) {
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to resolve repo:", err)
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
···
func (s *State) ClosePull(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("malformed middleware")
return
···
func (s *State) ReopenPull(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to resolve repo", err)
s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.")
+35 -40
appview/state/repo.go
···
func (s *State) RepoIndex(w http.ResponseWriter, r *http.Request) {
ref := chi.URLParam(r, "ref")
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to fully resolve repo", err)
return
···
}
func (s *State) RepoLog(w http.ResponseWriter, r *http.Request) {
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to fully resolve repo", err)
return
···
}
func (s *State) RepoDescriptionEdit(w http.ResponseWriter, r *http.Request) {
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
w.WriteHeader(http.StatusBadRequest)
···
}
func (s *State) RepoDescription(w http.ResponseWriter, r *http.Request) {
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
w.WriteHeader(http.StatusBadRequest)
···
}
func (s *State) RepoCommit(w http.ResponseWriter, r *http.Request) {
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to fully resolve repo", err)
return
···
}
func (s *State) RepoTree(w http.ResponseWriter, r *http.Request) {
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to fully resolve repo", err)
return
···
err = json.Unmarshal(body, &result)
if err != nil {
log.Println("failed to parse response:", err)
+
return
+
}
+
+
// redirects tree paths trying to access a blob; in this case the result.Files is unpopulated,
+
// so we can safely redirect to the "parent" (which is the same file).
+
if len(result.Files) == 0 && result.Parent == treePath {
+
http.Redirect(w, r, fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), ref, result.Parent), http.StatusFound)
return
}
···
}
func (s *State) RepoTags(w http.ResponseWriter, r *http.Request) {
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
}
func (s *State) RepoBranches(w http.ResponseWriter, r *http.Request) {
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
}
func (s *State) RepoBlob(w http.ResponseWriter, r *http.Request) {
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
}
func (s *State) RepoBlobRaw(w http.ResponseWriter, r *http.Request) {
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
}
func (s *State) AddCollaborator(w http.ResponseWriter, r *http.Request) {
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) DeleteRepo(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
}
func (s *State) SetDefaultBranch(w http.ResponseWriter, r *http.Request) {
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
}
func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) {
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
}
}
-
resp, err = us.DefaultBranch(f.OwnerDid(), f.RepoName)
+
defaultBranchResp, err := us.DefaultBranch(f.OwnerDid(), f.RepoName)
if err != nil {
log.Println("failed to reach knotserver", err)
} else {
-
defer resp.Body.Close()
-
-
body, err := io.ReadAll(resp.Body)
-
if err != nil {
-
log.Printf("Error reading response body: %v", err)
-
} else {
-
var result types.RepoDefaultBranchResponse
-
err = json.Unmarshal(body, &result)
-
if err != nil {
-
log.Println("failed to parse response:", err)
-
} else {
-
defaultBranch = result.Branch
-
}
-
}
+
defaultBranch = defaultBranchResp.Branch
}
}
-
s.pages.RepoSettings(w, pages.RepoSettingsParams{
LoggedInUser: user,
RepoInfo: f.RepoInfo(s, user),
···
RepoAt syntax.ATURI
Description string
CreatedAt string
+
Ref string
}
func (f *FullyResolvedRepo) OwnerDid() string {
···
Name: f.RepoName,
RepoAt: f.RepoAt,
Description: f.Description,
+
Ref: f.Ref,
IsStarred: isStarred,
Knot: knot,
Roles: RolesInRepo(s, u, f),
···
func (s *State) RepoSingleIssue(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) CloseIssue(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) ReopenIssue(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) NewIssueComment(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) EditIssueComment(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) DeleteIssueComment(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) NewIssue(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Println("failed to get repo and knot", err)
return
···
func (s *State) ForkRepo(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
-
f, err := fullyResolvedRepo(r)
+
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Printf("failed to resolve source repo: %v", err)
return
+18 -1
appview/state/repo_util.go
···
"tangled.sh/tangled.sh/core/appview/pages"
)
-
func fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) {
+
func (s *State) fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) {
repoName := chi.URLParam(r, "repo")
knot, ok := r.Context().Value("knot").(string)
if !ok {
···
return nil, fmt.Errorf("malformed middleware")
}
+
ref := chi.URLParam(r, "ref")
+
+
if ref == "" {
+
us, err := NewUnsignedClient(knot, s.config.Dev)
+
if err != nil {
+
return nil, err
+
}
+
+
defaultBranch, err := us.DefaultBranch(id.DID.String(), repoName)
+
if err != nil {
+
return nil, err
+
}
+
+
ref = defaultBranch.Branch
+
}
+
// pass through values from the middleware
description, ok := r.Context().Value("repoDescription").(string)
addedAt, ok := r.Context().Value("repoAddedAt").(string)
···
RepoAt: parsedRepoAt,
Description: description,
CreatedAt: addedAt,
+
Ref: ref,
}, nil
}
+13 -2
appview/state/signer.go
···
return us.client.Do(req)
}
-
func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*http.Response, error) {
+
func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*types.RepoDefaultBranchResponse, error) {
const (
Method = "GET"
)
···
return nil, err
}
-
return us.client.Do(req)
+
resp, err := us.client.Do(req)
+
if err != nil {
+
return nil, err
+
}
+
defer resp.Body.Close()
+
+
var defaultBranch types.RepoDefaultBranchResponse
+
if err := json.NewDecoder(resp.Body).Decode(&defaultBranch); err != nil {
+
return nil, err
+
}
+
+
return &defaultBranch, nil
}
func (us *UnsignedClient) Capabilities() (*types.Capabilities, error) {