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

Compare changes

Choose any two refs to compare.

Changed files
+408 -86
appview
db
filetree
middleware
pages
pagination
state
patchutil
types
+35 -20
appview/db/issues.go
··· 5 5 "time" 6 6 7 7 "github.com/bluesky-social/indigo/atproto/syntax" 8 + "tangled.sh/tangled.sh/core/appview/pagination" 8 9 ) 9 10 10 11 type Issue struct { ··· 102 103 return ownerDid, err 103 104 } 104 105 105 - func GetIssues(e Execer, repoAt syntax.ATURI, isOpen bool) ([]Issue, error) { 106 + func GetIssues(e Execer, repoAt syntax.ATURI, isOpen bool, page pagination.Page) ([]Issue, error) { 106 107 var issues []Issue 107 108 openValue := 0 108 109 if isOpen { ··· 110 111 } 111 112 112 113 rows, err := e.Query( 113 - `select 114 - i.owner_did, 115 - i.issue_id, 116 - i.created, 117 - i.title, 118 - i.body, 119 - i.open, 120 - count(c.id) 121 - from 122 - issues i 123 - left join 124 - comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id 125 - where 126 - i.repo_at = ? and i.open = ? 127 - group by 128 - i.id, i.owner_did, i.issue_id, i.created, i.title, i.body, i.open 129 - order by 130 - i.created desc`, 131 - repoAt, openValue) 114 + ` 115 + with numbered_issue as ( 116 + select 117 + i.owner_did, 118 + i.issue_id, 119 + i.created, 120 + i.title, 121 + i.body, 122 + i.open, 123 + count(c.id) as comment_count, 124 + row_number() over (order by i.created desc) as row_num 125 + from 126 + issues i 127 + left join 128 + comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id 129 + where 130 + i.repo_at = ? and i.open = ? 131 + group by 132 + i.id, i.owner_did, i.issue_id, i.created, i.title, i.body, i.open 133 + ) 134 + select 135 + owner_did, 136 + issue_id, 137 + created, 138 + title, 139 + body, 140 + open, 141 + comment_count 142 + from 143 + numbered_issue 144 + where 145 + row_num between ? and ?`, 146 + repoAt, openValue, page.Offset+1, page.Offset+page.Limit) 132 147 if err != nil { 133 148 return nil, err 134 149 }
+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 + }
+32
appview/middleware/middleware.go
··· 1 1 package middleware 2 2 3 3 import ( 4 + "context" 4 5 "log" 5 6 "net/http" 7 + "strconv" 6 8 "time" 7 9 8 10 comatproto "github.com/bluesky-social/indigo/api/atproto" 9 11 "github.com/bluesky-social/indigo/xrpc" 10 12 "tangled.sh/tangled.sh/core/appview" 11 13 "tangled.sh/tangled.sh/core/appview/auth" 14 + "tangled.sh/tangled.sh/core/appview/pagination" 12 15 ) 13 16 14 17 type Middleware func(http.Handler) http.Handler ··· 92 95 }) 93 96 } 94 97 } 98 + 99 + func Paginate(next http.Handler) http.Handler { 100 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 101 + page := pagination.FirstPage() 102 + 103 + offsetVal := r.URL.Query().Get("offset") 104 + if offsetVal != "" { 105 + offset, err := strconv.Atoi(offsetVal) 106 + if err != nil { 107 + log.Println("invalid offset") 108 + } else { 109 + page.Offset = offset 110 + } 111 + } 112 + 113 + limitVal := r.URL.Query().Get("limit") 114 + if limitVal != "" { 115 + limit, err := strconv.Atoi(limitVal) 116 + if err != nil { 117 + log.Println("invalid limit") 118 + } else { 119 + page.Limit = limit 120 + } 121 + } 122 + 123 + ctx := context.WithValue(r.Context(), "page", page) 124 + next.ServeHTTP(w, r.WithContext(ctx)) 125 + }) 126 + }
+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
+129 -37
appview/pages/pages.go
··· 11 11 "io/fs" 12 12 "log" 13 13 "net/http" 14 + "os" 14 15 "path" 15 16 "path/filepath" 16 17 "slices" ··· 19 20 "tangled.sh/tangled.sh/core/appview/auth" 20 21 "tangled.sh/tangled.sh/core/appview/db" 21 22 "tangled.sh/tangled.sh/core/appview/pages/markup" 23 + "tangled.sh/tangled.sh/core/appview/pagination" 22 24 "tangled.sh/tangled.sh/core/appview/state/userutil" 23 25 "tangled.sh/tangled.sh/core/patchutil" 24 26 "tangled.sh/tangled.sh/core/types" ··· 35 37 var Files embed.FS 36 38 37 39 type Pages struct { 38 - t map[string]*template.Template 40 + t map[string]*template.Template 41 + dev bool 42 + embedFS embed.FS 43 + templateDir string // Path to templates on disk for dev mode 39 44 } 40 45 41 - func NewPages() *Pages { 42 - templates := make(map[string]*template.Template) 46 + func NewPages(dev bool) *Pages { 47 + p := &Pages{ 48 + t: make(map[string]*template.Template), 49 + dev: dev, 50 + embedFS: Files, 51 + templateDir: "appview/pages", 52 + } 43 53 54 + // Initial load of all templates 55 + p.loadAllTemplates() 56 + 57 + return p 58 + } 59 + 60 + func (p *Pages) loadAllTemplates() { 61 + templates := make(map[string]*template.Template) 44 62 var fragmentPaths []string 63 + 64 + // Use embedded FS for initial loading 45 65 // First, collect all fragment paths 46 - err := fs.WalkDir(Files, "templates", func(path string, d fs.DirEntry, err error) error { 66 + err := fs.WalkDir(p.embedFS, "templates", func(path string, d fs.DirEntry, err error) error { 47 67 if err != nil { 48 68 return err 49 69 } 50 - 51 70 if d.IsDir() { 52 71 return nil 53 72 } 54 - 55 73 if !strings.HasSuffix(path, ".html") { 56 74 return nil 57 75 } 58 - 59 76 if !strings.Contains(path, "fragments/") { 60 77 return nil 61 78 } 62 - 63 79 name := strings.TrimPrefix(path, "templates/") 64 80 name = strings.TrimSuffix(name, ".html") 65 - 66 81 tmpl, err := template.New(name). 67 82 Funcs(funcMap()). 68 - ParseFS(Files, path) 83 + ParseFS(p.embedFS, path) 69 84 if err != nil { 70 85 log.Fatalf("setting up fragment: %v", err) 71 86 } 72 - 73 87 templates[name] = tmpl 74 88 fragmentPaths = append(fragmentPaths, path) 75 89 log.Printf("loaded fragment: %s", name) ··· 80 94 } 81 95 82 96 // Then walk through and setup the rest of the templates 83 - err = fs.WalkDir(Files, "templates", func(path string, d fs.DirEntry, err error) error { 97 + err = fs.WalkDir(p.embedFS, "templates", func(path string, d fs.DirEntry, err error) error { 84 98 if err != nil { 85 99 return err 86 100 } 87 - 88 101 if d.IsDir() { 89 102 return nil 90 103 } 91 - 92 104 if !strings.HasSuffix(path, "html") { 93 105 return nil 94 106 } 95 - 96 107 // Skip fragments as they've already been loaded 97 108 if strings.Contains(path, "fragments/") { 98 109 return nil 99 110 } 100 - 101 111 // Skip layouts 102 112 if strings.Contains(path, "layouts/") { 103 113 return nil 104 114 } 105 - 106 115 name := strings.TrimPrefix(path, "templates/") 107 116 name = strings.TrimSuffix(name, ".html") 108 - 109 117 // Add the page template on top of the base 110 118 allPaths := []string{} 111 119 allPaths = append(allPaths, "templates/layouts/*.html") ··· 113 121 allPaths = append(allPaths, path) 114 122 tmpl, err := template.New(name). 115 123 Funcs(funcMap()). 116 - ParseFS(Files, allPaths...) 124 + ParseFS(p.embedFS, allPaths...) 117 125 if err != nil { 118 126 return fmt.Errorf("setting up template: %w", err) 119 127 } 120 - 121 128 templates[name] = tmpl 122 129 log.Printf("loaded template: %s", name) 123 130 return nil ··· 127 134 } 128 135 129 136 log.Printf("total templates loaded: %d", len(templates)) 137 + p.t = templates 138 + } 130 139 131 - return &Pages{ 132 - t: templates, 140 + // loadTemplateFromDisk loads a template from the filesystem in dev mode 141 + func (p *Pages) loadTemplateFromDisk(name string) error { 142 + if !p.dev { 143 + return nil 144 + } 145 + 146 + log.Printf("reloading template from disk: %s", name) 147 + 148 + // Find all fragments first 149 + var fragmentPaths []string 150 + err := filepath.WalkDir(filepath.Join(p.templateDir, "templates"), func(path string, d fs.DirEntry, err error) error { 151 + if err != nil { 152 + return err 153 + } 154 + if d.IsDir() { 155 + return nil 156 + } 157 + if !strings.HasSuffix(path, ".html") { 158 + return nil 159 + } 160 + if !strings.Contains(path, "fragments/") { 161 + return nil 162 + } 163 + fragmentPaths = append(fragmentPaths, path) 164 + return nil 165 + }) 166 + if err != nil { 167 + return fmt.Errorf("walking disk template dir for fragments: %w", err) 133 168 } 169 + 170 + // Find the template path on disk 171 + templatePath := filepath.Join(p.templateDir, "templates", name+".html") 172 + if _, err := os.Stat(templatePath); os.IsNotExist(err) { 173 + return fmt.Errorf("template not found on disk: %s", name) 174 + } 175 + 176 + // Create a new template 177 + tmpl := template.New(name).Funcs(funcMap()) 178 + 179 + // Parse layouts 180 + layoutGlob := filepath.Join(p.templateDir, "templates", "layouts", "*.html") 181 + layouts, err := filepath.Glob(layoutGlob) 182 + if err != nil { 183 + return fmt.Errorf("finding layout templates: %w", err) 184 + } 185 + 186 + // Create paths for parsing 187 + allFiles := append(layouts, fragmentPaths...) 188 + allFiles = append(allFiles, templatePath) 189 + 190 + // Parse all templates 191 + tmpl, err = tmpl.ParseFiles(allFiles...) 192 + if err != nil { 193 + return fmt.Errorf("parsing template files: %w", err) 194 + } 195 + 196 + // Update the template in the map 197 + p.t[name] = tmpl 198 + log.Printf("template reloaded from disk: %s", name) 199 + return nil 134 200 } 135 201 136 - type LoginParams struct { 202 + func (p *Pages) executeOrReload(templateName string, w io.Writer, base string, params any) error { 203 + // In dev mode, reload the template from disk before executing 204 + if p.dev { 205 + if err := p.loadTemplateFromDisk(templateName); err != nil { 206 + log.Printf("warning: failed to reload template %s from disk: %v", templateName, err) 207 + // Continue with the existing template 208 + } 209 + } 210 + 211 + tmpl, exists := p.t[templateName] 212 + if !exists { 213 + return fmt.Errorf("template not found: %s", templateName) 214 + } 215 + 216 + if base == "" { 217 + return tmpl.Execute(w, params) 218 + } else { 219 + return tmpl.ExecuteTemplate(w, base, params) 220 + } 137 221 } 138 222 139 223 func (p *Pages) execute(name string, w io.Writer, params any) error { 140 - return p.t[name].ExecuteTemplate(w, "layouts/base", params) 224 + return p.executeOrReload(name, w, "layouts/base", params) 141 225 } 142 226 143 227 func (p *Pages) executePlain(name string, w io.Writer, params any) error { 144 - return p.t[name].Execute(w, params) 228 + return p.executeOrReload(name, w, "", params) 145 229 } 146 230 147 231 func (p *Pages) executeRepo(name string, w io.Writer, params any) error { 148 - return p.t[name].ExecuteTemplate(w, "layouts/repobase", params) 232 + return p.executeOrReload(name, w, "layouts/repobase", params) 233 + } 234 + 235 + type LoginParams struct { 149 236 } 150 237 151 238 func (p *Pages) Login(w io.Writer, params LoginParams) error { ··· 419 506 } 420 507 421 508 type RepoCommitParams struct { 422 - LoggedInUser *auth.User 423 - RepoInfo RepoInfo 424 - Active string 509 + LoggedInUser *auth.User 510 + RepoInfo RepoInfo 511 + Active string 512 + EmailToDidOrHandle map[string]string 513 + 425 514 types.RepoCommitResponse 426 - EmailToDidOrHandle map[string]string 427 515 } 428 516 429 517 func (p *Pages) RepoCommit(w io.Writer, params RepoCommitParams) error { ··· 564 652 } 565 653 566 654 type RepoIssuesParams struct { 567 - LoggedInUser *auth.User 568 - RepoInfo RepoInfo 569 - Active string 570 - Issues []db.Issue 571 - DidHandleMap map[string]string 572 - 655 + LoggedInUser *auth.User 656 + RepoInfo RepoInfo 657 + Active string 658 + Issues []db.Issue 659 + DidHandleMap map[string]string 660 + Page pagination.Page 573 661 FilteringByOpen bool 574 662 } 575 663 ··· 698 786 DidHandleMap map[string]string 699 787 RepoInfo RepoInfo 700 788 Pull *db.Pull 701 - Diff types.NiceDiff 789 + Diff *types.NiceDiff 702 790 Round int 703 791 Submission *db.PullSubmission 704 792 } ··· 794 882 } 795 883 796 884 func (p *Pages) Static() http.Handler { 885 + if p.dev { 886 + return http.StripPrefix("/static/", http.FileServer(http.Dir("appview/pages/static"))) 887 + } 888 + 797 889 sub, err := fs.Sub(Files, "static") 798 890 if err != nil { 799 891 log.Fatalf("no static dir found? that's crazy: %v", err)
+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
+38
appview/pages/templates/repo/issues/issues.html
··· 70 70 </div> 71 71 {{ end }} 72 72 </div> 73 + 74 + {{ block "pagination" . }} {{ end }} 75 + 76 + {{ end }} 77 + 78 + {{ define "pagination" }} 79 + <div class="flex justify-end mt-4 gap-2"> 80 + {{ $currentState := "closed" }} 81 + {{ if .FilteringByOpen }} 82 + {{ $currentState = "open" }} 83 + {{ end }} 84 + 85 + {{ if gt .Page.Offset 0 }} 86 + {{ $prev := .Page.Previous }} 87 + <a 88 + class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 89 + hx-boost="true" 90 + href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&offset={{ $prev.Offset }}&limit={{ $prev.Limit }}" 91 + > 92 + {{ i "chevron-left" "w-4 h-4" }} 93 + previous 94 + </a> 95 + {{ else }} 96 + <div></div> 97 + {{ end }} 98 + 99 + {{ if eq (len .Issues) .Page.Limit }} 100 + {{ $next := .Page.Next }} 101 + <a 102 + class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 103 + hx-boost="true" 104 + href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&offset={{ $next.Offset }}&limit={{ $next.Limit }}" 105 + > 106 + next 107 + {{ i "chevron-right" "w-4 h-4" }} 108 + </a> 109 + {{ end }} 110 + </div> 73 111 {{ end }}
+31
appview/pagination/page.go
··· 1 + package pagination 2 + 3 + type Page struct { 4 + Offset int // where to start from 5 + Limit int // number of items in a page 6 + } 7 + 8 + func FirstPage() Page { 9 + return Page{ 10 + Offset: 0, 11 + Limit: 10, 12 + } 13 + } 14 + 15 + func (p Page) Previous() Page { 16 + if p.Offset-p.Limit < 0 { 17 + return FirstPage() 18 + } else { 19 + return Page{ 20 + Offset: p.Offset - p.Limit, 21 + Limit: p.Limit, 22 + } 23 + } 24 + } 25 + 26 + func (p Page) Next() Page { 27 + return Page{ 28 + Offset: p.Offset + p.Limit, 29 + Limit: p.Limit, 30 + } 31 + }
+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 }
+9 -1
appview/state/repo.go
··· 28 28 "tangled.sh/tangled.sh/core/appview/db" 29 29 "tangled.sh/tangled.sh/core/appview/pages" 30 30 "tangled.sh/tangled.sh/core/appview/pages/markup" 31 + "tangled.sh/tangled.sh/core/appview/pagination" 31 32 "tangled.sh/tangled.sh/core/types" 32 33 33 34 comatproto "github.com/bluesky-social/indigo/api/atproto" ··· 1559 1560 isOpen = true 1560 1561 } 1561 1562 1563 + page, ok := r.Context().Value("page").(pagination.Page) 1564 + if !ok { 1565 + log.Println("failed to get page") 1566 + page = pagination.FirstPage() 1567 + } 1568 + 1562 1569 user := s.auth.GetUser(r) 1563 1570 f, err := fullyResolvedRepo(r) 1564 1571 if err != nil { ··· 1566 1573 return 1567 1574 } 1568 1575 1569 - issues, err := db.GetIssues(s.db, f.RepoAt, isOpen) 1576 + issues, err := db.GetIssues(s.db, f.RepoAt, isOpen, page) 1570 1577 if err != nil { 1571 1578 log.Println("failed to get issues", err) 1572 1579 s.pages.Notice(w, "issues", "Failed to load issues. Try again later.") ··· 1593 1600 Issues: issues, 1594 1601 DidHandleMap: didHandleMap, 1595 1602 FilteringByOpen: isOpen, 1603 + Page: page, 1596 1604 }) 1597 1605 return 1598 1606 }
+1 -1
appview/state/router.go
··· 68 68 r.Get("/blob/{ref}/raw/*", s.RepoBlobRaw) 69 69 70 70 r.Route("/issues", func(r chi.Router) { 71 - r.Get("/", s.RepoIssues) 71 + r.With(middleware.Paginate).Get("/", s.RepoIssues) 72 72 r.Get("/{issue}", s.RepoSingleIssue) 73 73 74 74 r.Group(func(r chi.Router) {
+1 -1
appview/state/state.go
··· 55 55 56 56 clock := syntax.NewTIDClock(0) 57 57 58 - pgs := pages.NewPages() 58 + pgs := pages.NewPages(config.Dev) 59 59 60 60 resolver := appview.NewResolver() 61 61
+10 -1
flake.nix
··· 173 173 ${pkgs.air}/bin/air -c /dev/null \ 174 174 -build.cmd "${pkgs.tailwindcss}/bin/tailwindcss -i input.css -o ./appview/pages/static/tw.css && ${pkgs.go}/bin/go build -o ./out/${name}.out ./cmd/${name}/main.go" \ 175 175 -build.bin "./out/${name}.out" \ 176 - -build.include_ext "go,html,css" 176 + -build.include_ext "go" 177 + ''; 178 + tailwind-watcher = 179 + pkgs.writeShellScriptBin "run" 180 + '' 181 + ${pkgs.tailwindcss}/bin/tailwindcss -w -i input.css -o ./appview/pages/static/tw.css 177 182 ''; 178 183 in { 179 184 watch-appview = { ··· 183 188 watch-knotserver = { 184 189 type = "app"; 185 190 program = ''${air-watcher "knotserver"}/bin/run''; 191 + }; 192 + watch-tailwind = { 193 + type = "app"; 194 + program = ''${tailwind-watcher}/bin/run''; 186 195 }; 187 196 }); 188 197
+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 + }