From 965ff1758a0b6d841d66f524e21a983bc4095885 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Sun, 28 Sep 2025 13:11:05 +0000 Subject: [PATCH] appview,knotserver: pass along a readme candidate in repo.tree responses this simplifies readme detection and improves page performance. Signed-off-by: oppiliappan --- types/repo.go | 12 +++++++----- api/tangled/repotree.go | 10 ++++++++++ appview/pages/pages.go | 20 ++++++++++++++++++++ appview/repo/index.go | 26 +++++--------------------- appview/repo/repo.go | 4 ++++ knotserver/xrpc/repo_tree.go | 24 ++++++++++++++++++++++++ lexicons/repo/tree.json | 19 +++++++++++++++++++ appview/pages/markup/format.go | 32 +++++++++++++++----------------- appview/pages/templates/repo/tree.html | 6 ++++++ appview/pages/templates/repo/fragments/readme.html | 4 ++-- 10 file(s) changed, 112 insertion(s)(+), 45 deletion(s)(-) diff --git a/types/repo.go b/types/repo.go --- a/types/repo.go +++ b/types/repo.go @@ -41,11 +41,13 @@ } type RepoTreeResponse struct { - Ref string `json:"ref,omitempty"` - Parent string `json:"parent,omitempty"` - Description string `json:"description,omitempty"` - DotDot string `json:"dotdot,omitempty"` - Files []NiceTree `json:"files,omitempty"` + Ref string `json:"ref,omitempty"` + Parent string `json:"parent,omitempty"` + Description string `json:"description,omitempty"` + DotDot string `json:"dotdot,omitempty"` + Files []NiceTree `json:"files,omitempty"` + ReadmeFileName string `json:"readme_filename,omitempty"` + Readme string `json:"readme_contents,omitempty"` } type TagReference struct { diff --git a/api/tangled/repotree.go b/api/tangled/repotree.go --- a/api/tangled/repotree.go +++ b/api/tangled/repotree.go @@ -31,8 +31,18 @@ Files []*RepoTree_TreeEntry `json:"files" cborgen:"files"` // parent: The parent path in the tree Parent *string `json:"parent,omitempty" cborgen:"parent,omitempty"` + // readme: Readme for this file tree + Readme *RepoTree_Readme `json:"readme,omitempty" cborgen:"readme,omitempty"` // ref: The git reference used Ref string `json:"ref" cborgen:"ref"` +} + +// RepoTree_Readme is a "readme" in the sh.tangled.repo.tree schema. +type RepoTree_Readme struct { + // contents: Contents of the readme file + Contents string `json:"contents" cborgen:"contents"` + // filename: Name of the readme file + Filename string `json:"filename" cborgen:"filename"` } // RepoTree_TreeEntry is a "treeEntry" in the sh.tangled.repo.tree schema. diff --git a/appview/pages/pages.go b/appview/pages/pages.go --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -714,6 +714,8 @@ Active string BreadCrumbs [][]string TreePath string + Raw bool + HTMLReadme template.HTML types.RepoTreeResponse } @@ -740,6 +742,24 @@ func (p *Pages) RepoTree(w io.Writer, params RepoTreeParams) error { params.Active = "overview" + + p.rctx.RepoInfo = params.RepoInfo + p.rctx.RepoInfo.Ref = params.Ref + p.rctx.RendererType = markup.RendererTypeRepoMarkdown + + if params.ReadmeFileName != "" { + ext := filepath.Ext(params.ReadmeFileName) + switch ext { + case ".md", ".markdown", ".mdown", ".mkdn", ".mkd": + params.Raw = false + htmlString := p.rctx.RenderMarkdown(params.Readme) + sanitized := p.rctx.SanitizeDefault(htmlString) + params.HTMLReadme = template.HTML(sanitized) + default: + params.Raw = true + } + } + return p.executeRepo("repo/tree", w, params) } diff --git a/appview/repo/index.go b/appview/repo/index.go --- a/appview/repo/index.go +++ b/appview/repo/index.go @@ -22,7 +22,6 @@ "tangled.org/core/appview/db" "tangled.org/core/appview/models" "tangled.org/core/appview/pages" - "tangled.org/core/appview/pages/markup" "tangled.org/core/appview/reporesolver" "tangled.org/core/appview/xrpcclient" "tangled.org/core/types" @@ -328,26 +327,6 @@ } }() - // readme content - wg.Add(1) - go func() { - defer wg.Done() - for _, filename := range markup.ReadmeFilenames { - blobResp, err := tangled.RepoBlob(ctx, xrpcc, filename, false, ref, repo) - if err != nil { - continue - } - - if blobResp == nil { - continue - } - - readmeContent = blobResp.Content - readmeFileName = filename - break - } - }() - wg.Wait() if errs != nil { @@ -374,6 +353,11 @@ } files = append(files, niceFile) } + } + + if treeResp != nil && treeResp.Readme != nil { + readmeFileName = treeResp.Readme.Filename + readmeContent = treeResp.Readme.Contents } result := &types.RepoIndexResponse{ diff --git a/appview/repo/repo.go b/appview/repo/repo.go --- a/appview/repo/repo.go +++ b/appview/repo/repo.go @@ -484,6 +484,10 @@ if xrpcResp.Dotdot != nil { result.DotDot = *xrpcResp.Dotdot } + if xrpcResp.Readme != nil { + result.ReadmeFileName = xrpcResp.Readme.Filename + result.Readme = xrpcResp.Readme.Contents + } // 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). diff --git a/knotserver/xrpc/repo_tree.go b/knotserver/xrpc/repo_tree.go --- a/knotserver/xrpc/repo_tree.go +++ b/knotserver/xrpc/repo_tree.go @@ -4,8 +4,10 @@ "net/http" "path/filepath" "time" + "unicode/utf8" "tangled.org/core/api/tangled" + "tangled.org/core/appview/pages/markup" "tangled.org/core/knotserver/git" xrpcerr "tangled.org/core/xrpc/errors" ) @@ -41,6 +43,24 @@ xrpcerr.WithMessage("failed to read repository tree"), ), http.StatusNotFound) return + } + + // if any of these files are a readme candidate, pass along its blob contents too + var readmeFileName string + var readmeContents string + for _, file := range files { + if markup.IsReadmeFile(file.Name) { + contents, err := gr.RawContent(filepath.Join(path, file.Name)) + if err != nil { + x.Logger.Error("failed to read contents of file", "path", path, "file", file.Name) + } + + if utf8.Valid(contents) { + readmeFileName = file.Name + readmeContents = string(contents) + break + } + } } // convert NiceTree -> tangled.RepoTree_TreeEntry @@ -83,6 +103,10 @@ Parent: parentPtr, Dotdot: dotdotPtr, Files: treeEntries, + Readme: &tangled.RepoTree_Readme{ + Filename: readmeFileName, + Contents: readmeContents, + }, } writeJson(w, response) diff --git a/lexicons/repo/tree.json b/lexicons/repo/tree.json --- a/lexicons/repo/tree.json +++ b/lexicons/repo/tree.json @@ -41,6 +41,11 @@ "type": "string", "description": "Parent directory path" }, + "readme": { + "type": "ref", + "ref": "#readme", + "description": "Readme for this file tree" + }, "files": { "type": "array", "items": { @@ -69,6 +74,20 @@ "description": "Invalid request parameters" } ] + }, + "readme": { + "type": "object", + "required": ["filename", "contents"], + "properties": { + "filename": { + "type": "string", + "description": "Name of the readme file" + }, + "contents": { + "type": "string", + "description": "Contents of the readme file" + } + } }, "treeEntry": { "type": "object", diff --git a/appview/pages/markup/format.go b/appview/pages/markup/format.go --- a/appview/pages/markup/format.go +++ b/appview/pages/markup/format.go @@ -1,6 +1,8 @@ package markup -import "strings" +import ( + "regexp" +) type Format string @@ -10,27 +12,23 @@ ) var FileTypes map[Format][]string = map[Format][]string{ - FormatMarkdown: []string{".md", ".markdown", ".mdown", ".mkdn", ".mkd"}, + FormatMarkdown: {".md", ".markdown", ".mdown", ".mkdn", ".mkd"}, } -// ReadmeFilenames contains the list of common README filenames to search for, -// in order of preference. Only includes well-supported formats. -var ReadmeFilenames = []string{ - "README.md", "readme.md", - "README", - "readme", - "README.markdown", - "readme.markdown", - "README.txt", - "readme.txt", +var FileTypePatterns = map[Format]*regexp.Regexp{ + FormatMarkdown: regexp.MustCompile(`(?i)\.(md|markdown|mdown|mkdn|mkd)$`), +} + +var ReadmePattern = regexp.MustCompile(`(?i)^readme(\.(md|markdown|txt))?$`) + +func IsReadmeFile(filename string) bool { + return ReadmePattern.MatchString(filename) } func GetFormat(filename string) Format { - for format, extensions := range FileTypes { - for _, extension := range extensions { - if strings.HasSuffix(filename, extension) { - return format - } + for format, pattern := range FileTypePatterns { + if pattern.MatchString(filename) { + return format } } // default format diff --git a/appview/pages/templates/repo/tree.html b/appview/pages/templates/repo/tree.html --- a/appview/pages/templates/repo/tree.html +++ b/appview/pages/templates/repo/tree.html @@ -88,3 +88,9 @@ {{end}} + +{{ define "repoAfter" }} + {{- if or .HTMLReadme .Readme -}} + {{ template "repo/fragments/readme" . }} + {{- end -}} +{{ end }} diff --git a/appview/pages/templates/repo/fragments/readme.html b/appview/pages/templates/repo/fragments/readme.html --- a/appview/pages/templates/repo/fragments/readme.html +++ b/appview/pages/templates/repo/fragments/readme.html @@ -1,13 +1,13 @@ {{ define "repo/fragments/readme" }}
{{- if .ReadmeFileName -}} -
+
{{ i "file-text" "w-4 h-4" "text-gray-600 dark:text-gray-400" }} {{ .ReadmeFileName }}
{{- end -}}