forked from tangled.org/core
this repo has no description

appview,knotserver: pass along a readme candidate in repo.tree responses

this simplifies readme detection and improves page performance.

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li 965ff175 9c76503e

verified
Changed files
+112 -45
api
tangled
appview
pages
markup
templates
repo
repo
knotserver
lexicons
repo
types
+10
api/tangled/repotree.go
···
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.
+15 -17
appview/pages/markup/format.go
···
package markup
-
import "strings"
+
import (
+
"regexp"
+
)
type Format string
···
)
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
+20
appview/pages/pages.go
···
Active string
BreadCrumbs [][]string
TreePath string
+
Raw bool
+
HTMLReadme template.HTML
types.RepoTreeResponse
}
···
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)
}
+2 -2
appview/pages/templates/repo/fragments/readme.html
···
{{ define "repo/fragments/readme" }}
<div class="mt-4 rounded bg-white dark:bg-gray-800 drop-shadow-sm w-full mx-auto overflow-hidden">
{{- if .ReadmeFileName -}}
-
<div class="px-4 py-2 bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600 flex items-center gap-2">
+
<div class="px-4 py-2 border-b border-gray-200 dark:border-gray-600 flex items-center gap-2">
{{ i "file-text" "w-4 h-4" "text-gray-600 dark:text-gray-400" }}
<span class="font-mono text-sm text-gray-800 dark:text-gray-200">{{ .ReadmeFileName }}</span>
</div>
{{- end -}}
<section
-
class="p-6 overflow-auto {{ if not .Raw }}
+
class="px-6 pb-6 overflow-auto {{ if not .Raw }}
prose dark:prose-invert dark:[&_pre]:bg-gray-900
dark:[&_code]:text-gray-300 dark:[&_pre_code]:bg-gray-900
dark:[&_pre]:border dark:[&_pre]:border-gray-700
+6
appview/pages/templates/repo/tree.html
···
</div>
</main>
{{end}}
+
+
{{ define "repoAfter" }}
+
{{- if or .HTMLReadme .Readme -}}
+
{{ template "repo/fragments/readme" . }}
+
{{- end -}}
+
{{ end }}
+5 -21
appview/repo/index.go
···
"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"
···
}
}()
-
// 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 {
···
}
files = append(files, niceFile)
}
+
}
+
+
if treeResp != nil && treeResp.Readme != nil {
+
readmeFileName = treeResp.Readme.Filename
+
readmeContent = treeResp.Readme.Contents
}
result := &types.RepoIndexResponse{
+4
appview/repo/repo.go
···
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).
+24
knotserver/xrpc/repo_tree.go
···
"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"
)
···
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 {
···
Parent: parentPtr,
Dotdot: dotdotPtr,
Files: treeEntries,
+
Readme: &tangled.RepoTree_Readme{
+
Filename: readmeFileName,
+
Contents: readmeContents,
+
},
}
writeJson(w, response)
+19
lexicons/repo/tree.json
···
"type": "string",
"description": "Parent directory path"
},
+
"readme": {
+
"type": "ref",
+
"ref": "#readme",
+
"description": "Readme for this file tree"
+
},
"files": {
"type": "array",
"items": {
···
"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",
+7 -5
types/repo.go
···
}
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 {