+10
api/tangled/repotree.go
+10
api/tangled/repotree.go
···
31
Files []*RepoTree_TreeEntry `json:"files" cborgen:"files"`
32
// parent: The parent path in the tree
33
Parent *string `json:"parent,omitempty" cborgen:"parent,omitempty"`
34
// ref: The git reference used
35
Ref string `json:"ref" cborgen:"ref"`
36
}
37
38
// RepoTree_TreeEntry is a "treeEntry" in the sh.tangled.repo.tree schema.
···
31
Files []*RepoTree_TreeEntry `json:"files" cborgen:"files"`
32
// parent: The parent path in the tree
33
Parent *string `json:"parent,omitempty" cborgen:"parent,omitempty"`
34
+
// readme: Readme for this file tree
35
+
Readme *RepoTree_Readme `json:"readme,omitempty" cborgen:"readme,omitempty"`
36
// ref: The git reference used
37
Ref string `json:"ref" cborgen:"ref"`
38
+
}
39
+
40
+
// RepoTree_Readme is a "readme" in the sh.tangled.repo.tree schema.
41
+
type RepoTree_Readme struct {
42
+
// contents: Contents of the readme file
43
+
Contents string `json:"contents" cborgen:"contents"`
44
+
// filename: Name of the readme file
45
+
Filename string `json:"filename" cborgen:"filename"`
46
}
47
48
// RepoTree_TreeEntry is a "treeEntry" in the sh.tangled.repo.tree schema.
+15
-17
appview/pages/markup/format.go
+15
-17
appview/pages/markup/format.go
···
1
package markup
2
3
-
import "strings"
4
5
type Format string
6
···
10
)
11
12
var FileTypes map[Format][]string = map[Format][]string{
13
-
FormatMarkdown: []string{".md", ".markdown", ".mdown", ".mkdn", ".mkd"},
14
}
15
16
-
// ReadmeFilenames contains the list of common README filenames to search for,
17
-
// in order of preference. Only includes well-supported formats.
18
-
var ReadmeFilenames = []string{
19
-
"README.md", "readme.md",
20
-
"README",
21
-
"readme",
22
-
"README.markdown",
23
-
"readme.markdown",
24
-
"README.txt",
25
-
"readme.txt",
26
}
27
28
func GetFormat(filename string) Format {
29
-
for format, extensions := range FileTypes {
30
-
for _, extension := range extensions {
31
-
if strings.HasSuffix(filename, extension) {
32
-
return format
33
-
}
34
}
35
}
36
// default format
···
1
package markup
2
3
+
import (
4
+
"regexp"
5
+
)
6
7
type Format string
8
···
12
)
13
14
var FileTypes map[Format][]string = map[Format][]string{
15
+
FormatMarkdown: {".md", ".markdown", ".mdown", ".mkdn", ".mkd"},
16
}
17
18
+
var FileTypePatterns = map[Format]*regexp.Regexp{
19
+
FormatMarkdown: regexp.MustCompile(`(?i)\.(md|markdown|mdown|mkdn|mkd)$`),
20
+
}
21
+
22
+
var ReadmePattern = regexp.MustCompile(`(?i)^readme(\.(md|markdown|txt))?$`)
23
+
24
+
func IsReadmeFile(filename string) bool {
25
+
return ReadmePattern.MatchString(filename)
26
}
27
28
func GetFormat(filename string) Format {
29
+
for format, pattern := range FileTypePatterns {
30
+
if pattern.MatchString(filename) {
31
+
return format
32
}
33
}
34
// default format
+20
appview/pages/pages.go
+20
appview/pages/pages.go
···
714
Active string
715
BreadCrumbs [][]string
716
TreePath string
717
+
Raw bool
718
+
HTMLReadme template.HTML
719
types.RepoTreeResponse
720
}
721
···
742
743
func (p *Pages) RepoTree(w io.Writer, params RepoTreeParams) error {
744
params.Active = "overview"
745
+
746
+
p.rctx.RepoInfo = params.RepoInfo
747
+
p.rctx.RepoInfo.Ref = params.Ref
748
+
p.rctx.RendererType = markup.RendererTypeRepoMarkdown
749
+
750
+
if params.ReadmeFileName != "" {
751
+
ext := filepath.Ext(params.ReadmeFileName)
752
+
switch ext {
753
+
case ".md", ".markdown", ".mdown", ".mkdn", ".mkd":
754
+
params.Raw = false
755
+
htmlString := p.rctx.RenderMarkdown(params.Readme)
756
+
sanitized := p.rctx.SanitizeDefault(htmlString)
757
+
params.HTMLReadme = template.HTML(sanitized)
758
+
default:
759
+
params.Raw = true
760
+
}
761
+
}
762
+
763
return p.executeRepo("repo/tree", w, params)
764
}
765
+2
-2
appview/pages/templates/repo/fragments/readme.html
+2
-2
appview/pages/templates/repo/fragments/readme.html
···
1
{{ define "repo/fragments/readme" }}
2
<div class="mt-4 rounded bg-white dark:bg-gray-800 drop-shadow-sm w-full mx-auto overflow-hidden">
3
{{- if .ReadmeFileName -}}
4
-
<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">
5
{{ i "file-text" "w-4 h-4" "text-gray-600 dark:text-gray-400" }}
6
<span class="font-mono text-sm text-gray-800 dark:text-gray-200">{{ .ReadmeFileName }}</span>
7
</div>
8
{{- end -}}
9
<section
10
-
class="p-6 overflow-auto {{ if not .Raw }}
11
prose dark:prose-invert dark:[&_pre]:bg-gray-900
12
dark:[&_code]:text-gray-300 dark:[&_pre_code]:bg-gray-900
13
dark:[&_pre]:border dark:[&_pre]:border-gray-700
···
1
{{ define "repo/fragments/readme" }}
2
<div class="mt-4 rounded bg-white dark:bg-gray-800 drop-shadow-sm w-full mx-auto overflow-hidden">
3
{{- if .ReadmeFileName -}}
4
+
<div class="px-4 py-2 border-b border-gray-200 dark:border-gray-600 flex items-center gap-2">
5
{{ i "file-text" "w-4 h-4" "text-gray-600 dark:text-gray-400" }}
6
<span class="font-mono text-sm text-gray-800 dark:text-gray-200">{{ .ReadmeFileName }}</span>
7
</div>
8
{{- end -}}
9
<section
10
+
class="px-6 pb-6 overflow-auto {{ if not .Raw }}
11
prose dark:prose-invert dark:[&_pre]:bg-gray-900
12
dark:[&_code]:text-gray-300 dark:[&_pre_code]:bg-gray-900
13
dark:[&_pre]:border dark:[&_pre]:border-gray-700
+6
appview/pages/templates/repo/tree.html
+6
appview/pages/templates/repo/tree.html
+5
-21
appview/repo/index.go
+5
-21
appview/repo/index.go
···
22
"tangled.org/core/appview/db"
23
"tangled.org/core/appview/models"
24
"tangled.org/core/appview/pages"
25
-
"tangled.org/core/appview/pages/markup"
26
"tangled.org/core/appview/reporesolver"
27
"tangled.org/core/appview/xrpcclient"
28
"tangled.org/core/types"
···
328
}
329
}()
330
331
-
// readme content
332
-
wg.Add(1)
333
-
go func() {
334
-
defer wg.Done()
335
-
for _, filename := range markup.ReadmeFilenames {
336
-
blobResp, err := tangled.RepoBlob(ctx, xrpcc, filename, false, ref, repo)
337
-
if err != nil {
338
-
continue
339
-
}
340
-
341
-
if blobResp == nil {
342
-
continue
343
-
}
344
-
345
-
readmeContent = blobResp.Content
346
-
readmeFileName = filename
347
-
break
348
-
}
349
-
}()
350
-
351
wg.Wait()
352
353
if errs != nil {
···
374
}
375
files = append(files, niceFile)
376
}
377
}
378
379
result := &types.RepoIndexResponse{
···
22
"tangled.org/core/appview/db"
23
"tangled.org/core/appview/models"
24
"tangled.org/core/appview/pages"
25
"tangled.org/core/appview/reporesolver"
26
"tangled.org/core/appview/xrpcclient"
27
"tangled.org/core/types"
···
327
}
328
}()
329
330
wg.Wait()
331
332
if errs != nil {
···
353
}
354
files = append(files, niceFile)
355
}
356
+
}
357
+
358
+
if treeResp != nil && treeResp.Readme != nil {
359
+
readmeFileName = treeResp.Readme.Filename
360
+
readmeContent = treeResp.Readme.Contents
361
}
362
363
result := &types.RepoIndexResponse{
+4
appview/repo/repo.go
+4
appview/repo/repo.go
···
484
if xrpcResp.Dotdot != nil {
485
result.DotDot = *xrpcResp.Dotdot
486
}
487
+
if xrpcResp.Readme != nil {
488
+
result.ReadmeFileName = xrpcResp.Readme.Filename
489
+
result.Readme = xrpcResp.Readme.Contents
490
+
}
491
492
// redirects tree paths trying to access a blob; in this case the result.Files is unpopulated,
493
// so we can safely redirect to the "parent" (which is the same file).
+24
knotserver/xrpc/repo_tree.go
+24
knotserver/xrpc/repo_tree.go
···
4
"net/http"
5
"path/filepath"
6
"time"
7
8
"tangled.org/core/api/tangled"
9
"tangled.org/core/knotserver/git"
10
xrpcerr "tangled.org/core/xrpc/errors"
11
)
···
43
return
44
}
45
46
// convert NiceTree -> tangled.RepoTree_TreeEntry
47
treeEntries := make([]*tangled.RepoTree_TreeEntry, len(files))
48
for i, file := range files {
···
83
Parent: parentPtr,
84
Dotdot: dotdotPtr,
85
Files: treeEntries,
86
}
87
88
writeJson(w, response)
···
4
"net/http"
5
"path/filepath"
6
"time"
7
+
"unicode/utf8"
8
9
"tangled.org/core/api/tangled"
10
+
"tangled.org/core/appview/pages/markup"
11
"tangled.org/core/knotserver/git"
12
xrpcerr "tangled.org/core/xrpc/errors"
13
)
···
45
return
46
}
47
48
+
// if any of these files are a readme candidate, pass along its blob contents too
49
+
var readmeFileName string
50
+
var readmeContents string
51
+
for _, file := range files {
52
+
if markup.IsReadmeFile(file.Name) {
53
+
contents, err := gr.RawContent(filepath.Join(path, file.Name))
54
+
if err != nil {
55
+
x.Logger.Error("failed to read contents of file", "path", path, "file", file.Name)
56
+
}
57
+
58
+
if utf8.Valid(contents) {
59
+
readmeFileName = file.Name
60
+
readmeContents = string(contents)
61
+
break
62
+
}
63
+
}
64
+
}
65
+
66
// convert NiceTree -> tangled.RepoTree_TreeEntry
67
treeEntries := make([]*tangled.RepoTree_TreeEntry, len(files))
68
for i, file := range files {
···
103
Parent: parentPtr,
104
Dotdot: dotdotPtr,
105
Files: treeEntries,
106
+
Readme: &tangled.RepoTree_Readme{
107
+
Filename: readmeFileName,
108
+
Contents: readmeContents,
109
+
},
110
}
111
112
writeJson(w, response)
+19
lexicons/repo/tree.json
+19
lexicons/repo/tree.json
···
41
"type": "string",
42
"description": "Parent directory path"
43
},
44
+
"readme": {
45
+
"type": "ref",
46
+
"ref": "#readme",
47
+
"description": "Readme for this file tree"
48
+
},
49
"files": {
50
"type": "array",
51
"items": {
···
74
"description": "Invalid request parameters"
75
}
76
]
77
+
},
78
+
"readme": {
79
+
"type": "object",
80
+
"required": ["filename", "contents"],
81
+
"properties": {
82
+
"filename": {
83
+
"type": "string",
84
+
"description": "Name of the readme file"
85
+
},
86
+
"contents": {
87
+
"type": "string",
88
+
"description": "Contents of the readme file"
89
+
}
90
+
}
91
},
92
"treeEntry": {
93
"type": "object",
+7
-5
types/repo.go
+7
-5
types/repo.go
···
41
}
42
43
type RepoTreeResponse struct {
44
-
Ref string `json:"ref,omitempty"`
45
-
Parent string `json:"parent,omitempty"`
46
-
Description string `json:"description,omitempty"`
47
-
DotDot string `json:"dotdot,omitempty"`
48
-
Files []NiceTree `json:"files,omitempty"`
49
}
50
51
type TagReference struct {
···
41
}
42
43
type RepoTreeResponse struct {
44
+
Ref string `json:"ref,omitempty"`
45
+
Parent string `json:"parent,omitempty"`
46
+
Description string `json:"description,omitempty"`
47
+
DotDot string `json:"dotdot,omitempty"`
48
+
Files []NiceTree `json:"files,omitempty"`
49
+
ReadmeFileName string `json:"readme_filename,omitempty"`
50
+
Readme string `json:"readme_contents,omitempty"`
51
}
52
53
type TagReference struct {