diff --git a/api/tangled/repotree.go b/api/tangled/repotree.go
index f84e98ae..b7776815 100644
--- a/api/tangled/repotree.go
+++ b/api/tangled/repotree.go
@@ -31,10 +31,20 @@ type RepoTree_Output struct {
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.
type RepoTree_TreeEntry struct {
// is_file: Whether this entry is a file
diff --git a/appview/pages/markup/format.go b/appview/pages/markup/format.go
index ffa7b511..82e1c206 100644
--- 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 @@ const (
)
var FileTypes map[Format][]string = map[Format][]string{
- FormatMarkdown: []string{".md", ".markdown", ".mdown", ".mkdn", ".mkd"},
+ FormatMarkdown: {".md", ".markdown", ".mdown", ".mkdn", ".mkd"},
+}
+
+var FileTypePatterns = map[Format]*regexp.Regexp{
+ FormatMarkdown: regexp.MustCompile(`(?i)\.(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 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/pages.go b/appview/pages/pages.go
index 53b09fb9..ad3629eb 100644
--- a/appview/pages/pages.go
+++ b/appview/pages/pages.go
@@ -714,6 +714,8 @@ type RepoTreeParams struct {
Active string
BreadCrumbs [][]string
TreePath string
+ Raw bool
+ HTMLReadme template.HTML
types.RepoTreeResponse
}
@@ -740,6 +742,24 @@ func (r RepoTreeParams) TreeStats() RepoTreeStats {
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/pages/templates/repo/fragments/readme.html b/appview/pages/templates/repo/fragments/readme.html
index 64426307..ee9e77f7 100644
--- 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 -}}
{{end}}
+
+{{ define "repoAfter" }}
+ {{- if or .HTMLReadme .Readme -}}
+ {{ template "repo/fragments/readme" . }}
+ {{- end -}}
+{{ end }}
diff --git a/appview/repo/index.go b/appview/repo/index.go
index c5f3c2d7..db4bfcd7 100644
--- a/appview/repo/index.go
+++ b/appview/repo/index.go
@@ -22,7 +22,6 @@ import (
"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 @@ func (rp *Repo) buildIndexResponse(ctx context.Context, xrpcc *indigoxrpc.Client
}
}()
- // 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 {
@@ -376,6 +355,11 @@ func (rp *Repo) buildIndexResponse(ctx context.Context, xrpcc *indigoxrpc.Client
}
}
+ if treeResp != nil && treeResp.Readme != nil {
+ readmeFileName = treeResp.Readme.Filename
+ readmeContent = treeResp.Readme.Contents
+ }
+
result := &types.RepoIndexResponse{
IsEmpty: false,
Ref: ref,
diff --git a/appview/repo/repo.go b/appview/repo/repo.go
index f6c2c393..e82dfc80 100644
--- a/appview/repo/repo.go
+++ b/appview/repo/repo.go
@@ -484,6 +484,10 @@ func (rp *Repo) RepoTree(w http.ResponseWriter, r *http.Request) {
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
index fa7819d9..ba9ad711 100644
--- a/knotserver/xrpc/repo_tree.go
+++ b/knotserver/xrpc/repo_tree.go
@@ -4,8 +4,10 @@ import (
"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"
)
@@ -43,6 +45,24 @@ func (x *Xrpc) RepoTree(w http.ResponseWriter, r *http.Request) {
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
treeEntries := make([]*tangled.RepoTree_TreeEntry, len(files))
for i, file := range files {
@@ -83,6 +103,10 @@ func (x *Xrpc) RepoTree(w http.ResponseWriter, r *http.Request) {
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
index c3e5fe54..4d5101d0 100644
--- 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": {
@@ -70,6 +75,20 @@
}
]
},
+ "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",
"required": ["name", "mode", "size", "is_file", "is_subtree"],
diff --git a/types/repo.go b/types/repo.go
index e0d3d889..3f479089 100644
--- a/types/repo.go
+++ b/types/repo.go
@@ -41,11 +41,13 @@ type RepoFormatPatchResponse struct {
}
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 {