+35
-20
appview/db/issues.go
+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
}
+32
appview/middleware/middleware.go
+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
+
}
+7
-6
appview/pages/pages.go
+7
-6
appview/pages/pages.go
···
20
20
"tangled.sh/tangled.sh/core/appview/auth"
21
21
"tangled.sh/tangled.sh/core/appview/db"
22
22
"tangled.sh/tangled.sh/core/appview/pages/markup"
23
+
"tangled.sh/tangled.sh/core/appview/pagination"
23
24
"tangled.sh/tangled.sh/core/appview/state/userutil"
24
25
"tangled.sh/tangled.sh/core/patchutil"
25
26
"tangled.sh/tangled.sh/core/types"
···
651
652
}
652
653
653
654
type RepoIssuesParams struct {
654
-
LoggedInUser *auth.User
655
-
RepoInfo RepoInfo
656
-
Active string
657
-
Issues []db.Issue
658
-
DidHandleMap map[string]string
659
-
655
+
LoggedInUser *auth.User
656
+
RepoInfo RepoInfo
657
+
Active string
658
+
Issues []db.Issue
659
+
DidHandleMap map[string]string
660
+
Page pagination.Page
660
661
FilteringByOpen bool
661
662
}
662
663
+38
appview/pages/templates/repo/issues/issues.html
+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
+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
+
}
+9
-1
appview/state/repo.go
+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
+1
-1
appview/state/router.go