forked from tangled.org/core
Monorepo for Tangled

appview: diff: organize changed-files into file-tree

introduces the filetree package.

eventually we will have a sticky side-panel style layout for any page
displaying diffs (probably makes most sense when we have split diffs),
and this file-tree will move there.

authored by oppi.li and committed by Tangled 3646ff6d d3fb0e59

Changed files
+128 -30
appview
filetree
pages
state
patchutil
types
+62
appview/filetree/filetree.go
··· 1 + package filetree 2 + 3 + import ( 4 + "path/filepath" 5 + "sort" 6 + "strings" 7 + ) 8 + 9 + type FileTreeNode struct { 10 + Name string 11 + Path string 12 + IsDirectory bool 13 + Children map[string]*FileTreeNode 14 + } 15 + 16 + // NewNode creates a new node 17 + func newNode(name, path string, isDir bool) *FileTreeNode { 18 + return &FileTreeNode{ 19 + Name: name, 20 + Path: path, 21 + IsDirectory: isDir, 22 + Children: make(map[string]*FileTreeNode), 23 + } 24 + } 25 + 26 + func FileTree(files []string) *FileTreeNode { 27 + rootNode := newNode("", "", true) 28 + 29 + sort.Strings(files) 30 + 31 + for _, file := range files { 32 + if file == "" { 33 + continue 34 + } 35 + 36 + parts := strings.Split(filepath.Clean(file), "/") 37 + if len(parts) == 0 { 38 + continue 39 + } 40 + 41 + currentNode := rootNode 42 + currentPath := "" 43 + 44 + for i, part := range parts { 45 + if currentPath == "" { 46 + currentPath = part 47 + } else { 48 + currentPath = filepath.Join(currentPath, part) 49 + } 50 + 51 + isDir := i < len(parts)-1 52 + 53 + if _, exists := currentNode.Children[part]; !exists { 54 + currentNode.Children[part] = newNode(part, currentPath, isDir) 55 + } 56 + 57 + currentNode = currentNode.Children[part] 58 + } 59 + } 60 + 61 + return rootNode 62 + }
+2
appview/pages/funcmap.go
··· 13 13 "time" 14 14 15 15 "github.com/dustin/go-humanize" 16 + "tangled.sh/tangled.sh/core/appview/filetree" 16 17 "tangled.sh/tangled.sh/core/appview/pages/markup" 17 18 ) 18 19 ··· 174 175 return template.HTML(data) 175 176 }, 176 177 "cssContentHash": CssContentHash, 178 + "fileTree": filetree.FileTree, 177 179 } 178 180 } 179 181
+6 -5
appview/pages/pages.go
··· 505 505 } 506 506 507 507 type RepoCommitParams struct { 508 - LoggedInUser *auth.User 509 - RepoInfo RepoInfo 510 - Active string 508 + LoggedInUser *auth.User 509 + RepoInfo RepoInfo 510 + Active string 511 + EmailToDidOrHandle map[string]string 512 + 511 513 types.RepoCommitResponse 512 - EmailToDidOrHandle map[string]string 513 514 } 514 515 515 516 func (p *Pages) RepoCommit(w io.Writer, params RepoCommitParams) error { ··· 784 785 DidHandleMap map[string]string 785 786 RepoInfo RepoInfo 786 787 Pull *db.Pull 787 - Diff types.NiceDiff 788 + Diff *types.NiceDiff 788 789 Round int 789 790 Submission *db.PullSubmission 790 791 }
+4 -17
appview/pages/templates/repo/fragments/diff.html
··· 3 3 {{ $diff := index . 1 }} 4 4 {{ $commit := $diff.Commit }} 5 5 {{ $stat := $diff.Stat }} 6 + {{ $fileTree := fileTree $diff.ChangedFiles }} 6 7 {{ $diff := $diff.Diff }} 7 8 8 9 {{ $this := $commit.This }} ··· 14 15 <strong class="text-sm uppercase dark:text-gray-200">Changed files</strong> 15 16 {{ block "statPill" $stat }} {{ end }} 16 17 </div> 17 - <div class="overflow-x-auto"> 18 - {{ range $diff }} 19 - <ul class="dark:text-gray-200"> 20 - {{ if .IsDelete }} 21 - <li><a href="#file-{{ .Name.Old }}" class="dark:hover:text-gray-300">{{ .Name.Old }}</a></li> 22 - {{ else }} 23 - <li><a href="#file-{{ .Name.New }}" class="dark:hover:text-gray-300">{{ .Name.New }}</a></li> 24 - {{ end }} 25 - </ul> 26 - {{ end }} 27 - </div> 18 + {{ block "fileTree" $fileTree }} {{ end }} 28 19 </div> 29 20 </section> 30 21 ··· 38 29 <summary class="list-none cursor-pointer sticky top-0"> 39 30 <div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between"> 40 31 <div id="left-side-items" class="p-2 flex gap-2 items-center overflow-x-auto"> 41 - <div class="flex gap-1 items-center" style="direction: ltr;"> 32 + <div class="flex gap-1 items-center"> 42 33 {{ $markerstyle := "diff-type p-1 mr-1 font-mono text-sm rounded select-none" }} 43 34 {{ if .IsNew }} 44 35 <span class="bg-green-100 text-green-700 dark:bg-green-800/50 dark:text-green-400 {{ $markerstyle }}">ADDED</span> ··· 55 46 {{ block "statPill" .Stats }} {{ end }} 56 47 </div> 57 48 58 - <div class="flex gap-2 items-center overflow-x-auto" style="direction: rtl;"> 49 + <div class="flex gap-2 items-center overflow-x-auto"> 59 50 {{ if .IsDelete }} 60 51 <a class="dark:text-white whitespace-nowrap overflow-x-auto" {{if $this }}href="/{{ $repo }}/blob/{{ $this }}/{{ .Name.Old }}"{{end}}> 61 52 {{ .Name.Old }} ··· 101 92 {{ else if .IsCopy }} 102 93 <p class="text-center text-gray-400 dark:text-gray-500 p-4"> 103 94 This file has been copied. 104 - </p> 105 - {{ else if .IsRename }} 106 - <p class="text-center text-gray-400 dark:text-gray-500 p-4"> 107 - This file has been renamed. 108 95 </p> 109 96 {{ else if .IsBinary }} 110 97 <p class="text-center text-gray-400 dark:text-gray-500 p-4">
+27
appview/pages/templates/repo/fragments/filetree.html
··· 1 + {{ define "fileTree" }} 2 + {{ if and .Name .IsDirectory }} 3 + <details open> 4 + <summary class="cursor-pointer list-none pt-1"> 5 + <span class="inline-flex items-center gap-2 "> 6 + {{ i "folder" "w-3 h-3 fill-current" }} 7 + <span class="text-black dark:text-white">{{ .Name }}</span> 8 + </span> 9 + </summary> 10 + <div class="ml-1 pl-4 border-l border-gray-200 dark:border-gray-700"> 11 + {{ range $child := .Children }} 12 + {{ block "fileTree" $child }} {{ end }} 13 + {{ end }} 14 + </div> 15 + </details> 16 + {{ else if .Name }} 17 + <div class="flex items-center gap-2 pt-1"> 18 + {{ i "file" "w-3 h-3" }} 19 + <a href="#file-{{ .Path }}" class="text-black dark:text-white no-underline hover:underline">{{ .Name }}</a> 20 + </div> 21 + {{ else }} 22 + {{ range $child := .Children }} 23 + {{ block "fileTree" $child }} {{ end }} 24 + {{ end }} 25 + {{ end }} 26 + {{ end }} 27 +
+2 -7
appview/pages/templates/repo/fragments/interdiff.html
··· 1 1 {{ define "repo/fragments/interdiff" }} 2 2 {{ $repo := index . 0 }} 3 3 {{ $x := index . 1 }} 4 + {{ $fileTree := fileTree $x.AffectedFiles }} 4 5 {{ $diff := $x.Files }} 5 6 6 7 <section class="mt-6 p-6 border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm"> ··· 8 9 <div class="flex gap-2 items-center"> 9 10 <strong class="text-sm uppercase dark:text-gray-200">files</strong> 10 11 </div> 11 - <div class="overflow-x-auto"> 12 - <ul class="dark:text-gray-200"> 13 - {{ range $diff }} 14 - <li><a href="#file-{{ .Name }}" class="dark:hover:text-gray-300">{{ .Name }}</a></li> 15 - {{ end }} 16 - </ul> 17 - </div> 12 + {{ block "fileTree" $fileTree }} {{ end }} 18 13 </div> 19 14 </section> 20 15
+3 -1
appview/state/pull.go
··· 284 284 } 285 285 } 286 286 287 + diff := pull.Submissions[roundIdInt].AsNiceDiff(pull.TargetBranch) 288 + 287 289 s.pages.RepoPullPatchPage(w, pages.RepoPullPatchParams{ 288 290 LoggedInUser: user, 289 291 DidHandleMap: didHandleMap, ··· 291 293 Pull: pull, 292 294 Round: roundIdInt, 293 295 Submission: pull.Submissions[roundIdInt], 294 - Diff: pull.Submissions[roundIdInt].AsNiceDiff(pull.TargetBranch), 296 + Diff: &diff, 295 297 }) 296 298 297 299 }
+8
patchutil/interdiff.go
··· 11 11 Files []*InterdiffFile 12 12 } 13 13 14 + func (i *InterdiffResult) AffectedFiles() []string { 15 + files := make([]string, len(i.Files)) 16 + for _, f := range i.Files { 17 + files = append(files, f.Name) 18 + } 19 + return files 20 + } 21 + 14 22 func (i *InterdiffResult) String() string { 15 23 var b strings.Builder 16 24 for _, f := range i.Files {
+14
types/diff.go
··· 59 59 Patch string `json:"patch"` 60 60 Diff []*gitdiff.File `json:"diff"` 61 61 } 62 + 63 + func (d *NiceDiff) ChangedFiles() []string { 64 + files := make([]string, len(d.Diff)) 65 + 66 + for i, f := range d.Diff { 67 + if f.IsDelete { 68 + files[i] = f.Name.Old 69 + } else { 70 + files[i] = f.Name.New 71 + } 72 + } 73 + 74 + return files 75 + }