As discussed on Discord, the header and footer now take up full width. I went with the version where the content is still capped at 1024px, like the main content. The changes are purely CSS, except for an extra div around the main content. This is needed because the grid no longer adds a minimum height to the main content, which means the footer will not be pushed to the bottom on pages with little main content. So now instead the header, content and footer are in a flex column, and the content flex-grow’s to make sure it’s at least taking up the remaining viewport space. A few redundant classes have been removed, e.g. grid properties on elements that were not grid-items. I also removed (unused/invisible) border radius and drop-shadow from the header and footer. I tried best possible to check the layout across the different views. There does not currently seem to be any specific UI test suite or similar - let me know if I missed it. Normally I would add screenshots to a PR like this, but this does not seem supported currently. I can share over Discord if you’re interested.
+4
-2
appview/config/config.go
+4
-2
appview/config/config.go
···
72
72
}
73
73
74
74
type Cloudflare struct {
75
-
ApiToken string `env:"API_TOKEN"`
76
-
ZoneId string `env:"ZONE_ID"`
75
+
ApiToken string `env:"API_TOKEN"`
76
+
ZoneId string `env:"ZONE_ID"`
77
+
TurnstileSiteKey string `env:"TURNSTILE_SITE_KEY"`
78
+
TurnstileSecretKey string `env:"TURNSTILE_SECRET_KEY"`
77
79
}
78
80
79
81
func (cfg RedisConfig) ToURL() string {
+13
-9
appview/db/email.go
+13
-9
appview/db/email.go
···
71
71
return did, nil
72
72
}
73
73
74
-
func GetEmailToDid(e Execer, ems []string, isVerifiedFilter bool) (map[string]string, error) {
75
-
if len(ems) == 0 {
74
+
func GetEmailToDid(e Execer, emails []string, isVerifiedFilter bool) (map[string]string, error) {
75
+
if len(emails) == 0 {
76
76
return make(map[string]string), nil
77
77
}
78
78
···
81
81
verifiedFilter = 1
82
82
}
83
83
84
+
assoc := make(map[string]string)
85
+
84
86
// Create placeholders for the IN clause
85
-
placeholders := make([]string, len(ems))
86
-
args := make([]any, len(ems)+1)
87
+
placeholders := make([]string, 0, len(emails))
88
+
args := make([]any, 1, len(emails)+1)
87
89
88
90
args[0] = verifiedFilter
89
-
for i, em := range ems {
90
-
placeholders[i] = "?"
91
-
args[i+1] = em
91
+
for _, email := range emails {
92
+
if strings.HasPrefix(email, "did:") {
93
+
assoc[email] = email
94
+
continue
95
+
}
96
+
placeholders = append(placeholders, "?")
97
+
args = append(args, email)
92
98
}
93
99
94
100
query := `
···
105
111
}
106
112
defer rows.Close()
107
113
108
-
assoc := make(map[string]string)
109
-
110
114
for rows.Next() {
111
115
var email, did string
112
116
if err := rows.Scan(&email, &did); err != nil {
+7
appview/pages/templates/repo/fork.html
+7
appview/pages/templates/repo/fork.html
···
6
6
</div>
7
7
<div class="p-6 bg-white dark:bg-gray-800 drop-shadow-sm rounded">
8
8
<form hx-post="/{{ .RepoInfo.FullName }}/fork" class="space-y-12" hx-swap="none" hx-indicator="#spinner">
9
+
10
+
<fieldset class="space-y-3">
11
+
<legend for="repo_name" class="dark:text-white">Repository name</legend>
12
+
<input type="text" id="repo_name" name="repo_name" value="{{ .RepoInfo.Name }}"
13
+
class="w-full p-2 border rounded bg-gray-100 dark:bg-gray-700 dark:text-white dark:border-gray-600" />
14
+
</fieldset>
15
+
9
16
<fieldset class="space-y-3">
10
17
<legend class="dark:text-white">Select a knot to fork into</legend>
11
18
<div class="space-y-2">
+11
-7
appview/repo/repo.go
+11
-7
appview/repo/repo.go
···
2129
2129
}
2130
2130
2131
2131
// choose a name for a fork
2132
-
forkName := f.Name
2132
+
forkName := r.FormValue("repo_name")
2133
+
if forkName == "" {
2134
+
rp.pages.Notice(w, "repo", "Repository name cannot be empty.")
2135
+
return
2136
+
}
2137
+
2133
2138
// this check is *only* to see if the forked repo name already exists
2134
2139
// in the user's account.
2135
2140
existingRepo, err := db.GetRepo(
2136
2141
rp.db,
2137
2142
db.FilterEq("did", user.Did),
2138
-
db.FilterEq("name", f.Name),
2143
+
db.FilterEq("name", forkName),
2139
2144
)
2140
2145
if err != nil {
2141
-
if errors.Is(err, sql.ErrNoRows) {
2142
-
// no existing repo with this name found, we can use the name as is
2143
-
} else {
2146
+
if !errors.Is(err, sql.ErrNoRows) {
2144
2147
log.Println("error fetching existing repo from db", "err", err)
2145
2148
rp.pages.Notice(w, "repo", "Failed to fork this repository. Try again later.")
2146
2149
return
2147
2150
}
2148
2151
} else if existingRepo != nil {
2149
-
// repo with this name already exists, append random string
2150
-
forkName = fmt.Sprintf("%s-%s", forkName, randomString(3))
2152
+
// repo with this name already exists
2153
+
rp.pages.Notice(w, "repo", "A repository with this name already exists.")
2154
+
return
2151
2155
}
2152
2156
l = l.With("forkName", forkName)
2153
2157
+140
appview/db/db.go
+140
appview/db/db.go
···
954
954
return err
955
955
})
956
956
957
+
// add generated at_uri column to pulls table
958
+
//
959
+
// this requires a full table recreation because stored columns
960
+
// cannot be added via alter
961
+
//
962
+
// disable foreign-keys for the next migration
963
+
conn.ExecContext(ctx, "pragma foreign_keys = off;")
964
+
runMigration(conn, "add-at-uri-to-pulls", func(tx *sql.Tx) error {
965
+
_, err := tx.Exec(`
966
+
create table if not exists pulls_new (
967
+
-- identifiers
968
+
id integer primary key autoincrement,
969
+
pull_id integer not null,
970
+
at_uri text generated always as ('at://' || owner_did || '/' || 'sh.tangled.repo.pull' || '/' || rkey) stored,
971
+
972
+
-- at identifiers
973
+
repo_at text not null,
974
+
owner_did text not null,
975
+
rkey text not null,
976
+
977
+
-- content
978
+
title text not null,
979
+
body text not null,
980
+
target_branch text not null,
981
+
state integer not null default 0 check (state in (0, 1, 2, 3)), -- closed, open, merged, deleted
982
+
983
+
-- source info
984
+
source_branch text,
985
+
source_repo_at text,
986
+
987
+
-- stacking
988
+
stack_id text,
989
+
change_id text,
990
+
parent_change_id text,
991
+
992
+
-- meta
993
+
created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
994
+
995
+
-- constraints
996
+
unique(repo_at, pull_id),
997
+
unique(at_uri),
998
+
foreign key (repo_at) references repos(at_uri) on delete cascade
999
+
);
1000
+
`)
1001
+
if err != nil {
1002
+
return err
1003
+
}
1004
+
1005
+
// transfer data
1006
+
_, err = tx.Exec(`
1007
+
insert into pulls_new (
1008
+
id, pull_id, repo_at, owner_did, rkey,
1009
+
title, body, target_branch, state,
1010
+
source_branch, source_repo_at,
1011
+
stack_id, change_id, parent_change_id,
1012
+
created
1013
+
)
1014
+
select
1015
+
id, pull_id, repo_at, owner_did, rkey,
1016
+
title, body, target_branch, state,
1017
+
source_branch, source_repo_at,
1018
+
stack_id, change_id, parent_change_id,
1019
+
created
1020
+
from pulls;
1021
+
`)
1022
+
if err != nil {
1023
+
return err
1024
+
}
1025
+
1026
+
// drop old table
1027
+
_, err = tx.Exec(`drop table pulls`)
1028
+
if err != nil {
1029
+
return err
1030
+
}
1031
+
1032
+
// rename new table
1033
+
_, err = tx.Exec(`alter table pulls_new rename to pulls`)
1034
+
return err
1035
+
})
1036
+
conn.ExecContext(ctx, "pragma foreign_keys = on;")
1037
+
1038
+
// remove repo_at and pull_id from pull_submissions and replace with pull_at
1039
+
//
1040
+
// this requires a full table recreation because stored columns
1041
+
// cannot be added via alter
1042
+
//
1043
+
// disable foreign-keys for the next migration
1044
+
conn.ExecContext(ctx, "pragma foreign_keys = off;")
1045
+
runMigration(conn, "remove-repo-at-pull-id-from-pull-submissions", func(tx *sql.Tx) error {
1046
+
_, err := tx.Exec(`
1047
+
create table if not exists pull_submissions_new (
1048
+
-- identifiers
1049
+
id integer primary key autoincrement,
1050
+
pull_at text not null,
1051
+
1052
+
-- content, these are immutable, and require a resubmission to update
1053
+
round_number integer not null default 0,
1054
+
patch text,
1055
+
source_rev text,
1056
+
1057
+
-- meta
1058
+
created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
1059
+
1060
+
-- constraints
1061
+
unique(pull_at, round_number),
1062
+
foreign key (pull_at) references pulls(at_uri) on delete cascade
1063
+
);
1064
+
`)
1065
+
if err != nil {
1066
+
return err
1067
+
}
1068
+
1069
+
// transfer data, constructing pull_at from pulls table
1070
+
_, err = tx.Exec(`
1071
+
insert into pull_submissions_new (id, pull_at, round_number, patch, created)
1072
+
select
1073
+
ps.id,
1074
+
'at://' || p.owner_did || '/sh.tangled.repo.pull/' || p.rkey,
1075
+
ps.round_number,
1076
+
ps.patch,
1077
+
ps.created
1078
+
from pull_submissions ps
1079
+
join pulls p on ps.repo_at = p.repo_at and ps.pull_id = p.pull_id;
1080
+
`)
1081
+
if err != nil {
1082
+
return err
1083
+
}
1084
+
1085
+
// drop old table
1086
+
_, err = tx.Exec(`drop table pull_submissions`)
1087
+
if err != nil {
1088
+
return err
1089
+
}
1090
+
1091
+
// rename new table
1092
+
_, err = tx.Exec(`alter table pull_submissions_new rename to pull_submissions`)
1093
+
return err
1094
+
})
1095
+
conn.ExecContext(ctx, "pragma foreign_keys = on;")
1096
+
957
1097
return &DB{db}, nil
958
1098
}
959
1099
+5
-1
appview/issues/issues.go
+5
-1
appview/issues/issues.go
···
798
798
return
799
799
}
800
800
801
-
labelDefs, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", f.Repo.Labels))
801
+
labelDefs, err := db.GetLabelDefinitions(
802
+
rp.db,
803
+
db.FilterIn("at_uri", f.Repo.Labels),
804
+
db.FilterContains("scope", tangled.RepoIssueNSID),
805
+
)
802
806
if err != nil {
803
807
log.Println("failed to fetch labels", err)
804
808
rp.pages.Error503(w)
+1
-1
appview/pages/templates/repo/fragments/labelPanel.html
+1
-1
appview/pages/templates/repo/fragments/labelPanel.html
+26
appview/pages/templates/repo/fragments/participants.html
+26
appview/pages/templates/repo/fragments/participants.html
···
1
+
{{ define "repo/fragments/participants" }}
2
+
{{ $all := . }}
3
+
{{ $ps := take $all 5 }}
4
+
<div class="px-6 md:px-0">
5
+
<div class="py-1 flex items-center text-sm">
6
+
<span class="font-bold text-gray-500 dark:text-gray-400 capitalize">Participants</span>
7
+
<span class="bg-gray-200 dark:bg-gray-700 rounded py-1/2 px-1 ml-1">{{ len $all }}</span>
8
+
</div>
9
+
<div class="flex items-center -space-x-3 mt-2">
10
+
{{ $c := "z-50 z-40 z-30 z-20 z-10" }}
11
+
{{ range $i, $p := $ps }}
12
+
<img
13
+
src="{{ tinyAvatar . }}"
14
+
alt=""
15
+
class="rounded-full h-8 w-8 mr-1 border-2 border-gray-100 dark:border-gray-900 z-{{sub 5 $i}}0"
16
+
/>
17
+
{{ end }}
18
+
19
+
{{ if gt (len $all) 5 }}
20
+
<span class="pl-4 text-gray-500 dark:text-gray-400 text-sm">
21
+
+{{ sub (len $all) 5 }}
22
+
</span>
23
+
{{ end }}
24
+
</div>
25
+
</div>
26
+
{{ end }}
+1
-27
appview/pages/templates/repo/issues/issue.html
+1
-27
appview/pages/templates/repo/issues/issue.html
···
22
22
"Defs" $.LabelDefs
23
23
"Subject" $.Issue.AtUri
24
24
"State" $.Issue.Labels) }}
25
-
{{ template "issueParticipants" . }}
25
+
{{ template "repo/fragments/participants" $.Issue.Participants }}
26
26
</div>
27
27
</div>
28
28
{{ end }}
···
122
122
</div>
123
123
{{ end }}
124
124
125
-
{{ define "issueParticipants" }}
126
-
{{ $all := .Issue.Participants }}
127
-
{{ $ps := take $all 5 }}
128
-
<div>
129
-
<div class="py-1 flex items-center text-sm">
130
-
<span class="font-bold text-gray-500 dark:text-gray-400 capitalize">Participants</span>
131
-
<span class="bg-gray-200 dark:bg-gray-700 rounded py-1/2 px-1 ml-1">{{ len $all }}</span>
132
-
</div>
133
-
<div class="flex items-center -space-x-3 mt-2">
134
-
{{ $c := "z-50 z-40 z-30 z-20 z-10" }}
135
-
{{ range $i, $p := $ps }}
136
-
<img
137
-
src="{{ tinyAvatar . }}"
138
-
alt=""
139
-
class="rounded-full h-8 w-8 mr-1 border-2 border-gray-100 dark:border-gray-900 z-{{sub 5 $i}}0"
140
-
/>
141
-
{{ end }}
142
-
143
-
{{ if gt (len $all) 5 }}
144
-
<span class="pl-4 text-gray-500 dark:text-gray-400 text-sm">
145
-
+{{ sub (len $all) 5 }}
146
-
</span>
147
-
{{ end }}
148
-
</div>
149
-
</div>
150
-
{{ end }}
151
125
152
126
{{ define "repoAfter" }}
153
127
<div class="flex flex-col gap-4 mt-4">
+30
-12
appview/pages/templates/repo/pulls/pull.html
+30
-12
appview/pages/templates/repo/pulls/pull.html
···
9
9
{{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo "Title" $title "Url" $url) }}
10
10
{{ end }}
11
11
12
+
{{ define "repoContentLayout" }}
13
+
<div class="grid grid-cols-1 md:grid-cols-10 gap-4 w-full">
14
+
<div class="col-span-1 md:col-span-8">
15
+
<section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto dark:text-white">
16
+
{{ block "repoContent" . }}{{ end }}
17
+
</section>
18
+
{{ block "repoAfter" . }}{{ end }}
19
+
</div>
20
+
<div class="col-span-1 md:col-span-2 flex flex-col gap-6">
21
+
{{ template "repo/fragments/labelPanel"
22
+
(dict "RepoInfo" $.RepoInfo
23
+
"Defs" $.LabelDefs
24
+
"Subject" $.Pull.PullAt
25
+
"State" $.Pull.Labels) }}
26
+
{{ template "repo/fragments/participants" $.Pull.Participants }}
27
+
</div>
28
+
</div>
29
+
{{ end }}
12
30
13
31
{{ define "repoContent" }}
14
32
{{ template "repo/pulls/fragments/pullHeader" . }}
···
39
57
{{ with $item }}
40
58
<details {{ if eq $idx $lastIdx }}open{{ end }}>
41
59
<summary id="round-#{{ .RoundNumber }}" class="list-none cursor-pointer">
42
-
<div class="flex flex-wrap gap-2 items-center">
60
+
<div class="flex flex-wrap gap-2 items-stretch">
43
61
<!-- round number -->
44
62
<div class="rounded bg-white dark:bg-gray-800 drop-shadow-sm px-3 py-2 dark:text-white">
45
63
<span class="flex items-center">{{ i "hash" "w-4 h-4" }}{{ .RoundNumber }}</span>
46
64
</div>
47
65
<!-- round summary -->
48
-
<div class="rounded drop-shadow-sm bg-white dark:bg-gray-800 p-2 text-gray-500 dark:text-gray-400">
66
+
<div class="flex-1 rounded drop-shadow-sm bg-white dark:bg-gray-800 p-2 text-gray-500 dark:text-gray-400">
49
67
<span class="gap-1 flex items-center">
50
68
{{ $owner := resolve $.Pull.OwnerDid }}
51
69
{{ $re := "re" }}
···
72
90
<span class="hidden md:inline">diff</span>
73
91
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
74
92
</a>
75
-
{{ if not (eq .RoundNumber 0) }}
76
-
<a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group"
77
-
hx-boost="true"
78
-
href="/{{ $.RepoInfo.FullName }}/pulls/{{ $.Pull.PullId }}/round/{{.RoundNumber}}/interdiff">
79
-
{{ i "chevrons-left-right-ellipsis" "w-4 h-4 rotate-90" }}
80
-
<span class="hidden md:inline">interdiff</span>
81
-
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
82
-
</a>
83
-
<span id="interdiff-error-{{.RoundNumber}}"></span>
93
+
{{ if ne $idx 0 }}
94
+
<a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group"
95
+
hx-boost="true"
96
+
href="/{{ $.RepoInfo.FullName }}/pulls/{{ $.Pull.PullId }}/round/{{.RoundNumber}}/interdiff">
97
+
{{ i "chevrons-left-right-ellipsis" "w-4 h-4 rotate-90" }}
98
+
<span class="hidden md:inline">interdiff</span>
99
+
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
100
+
</a>
84
101
{{ end }}
102
+
<span id="interdiff-error-{{.RoundNumber}}"></span>
85
103
</div>
86
104
</summary>
87
105
···
146
164
147
165
<div class="md:pl-[3.5rem] flex flex-col gap-2 mt-2 relative">
148
166
{{ range $cidx, $c := .Comments }}
149
-
<div id="comment-{{$c.ID}}" class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-2 px-4 relative w-full md:max-w-3/5 md:w-fit">
167
+
<div id="comment-{{$c.ID}}" class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-2 px-4 relative w-full">
150
168
{{ if gt $cidx 0 }}
151
169
<div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>
152
170
{{ end }}
+7
appview/pages/templates/repo/pulls/pulls.html
+7
appview/pages/templates/repo/pulls/pulls.html
···
108
108
<span class="before:content-['·']"></span>
109
109
{{ template "repo/pipelines/fragments/pipelineSymbol" $pipeline }}
110
110
{{ end }}
111
+
112
+
{{ $state := .Labels }}
113
+
{{ range $k, $d := $.LabelDefs }}
114
+
{{ range $v, $s := $state.GetValSet $d.AtUri.String }}
115
+
{{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }}
116
+
{{ end }}
117
+
{{ end }}
111
118
</div>
112
119
</div>
113
120
{{ if .StackId }}
+35
appview/pulls/pulls.go
+35
appview/pulls/pulls.go
···
200
200
userReactions = db.GetReactionStatusMap(s.db, user.Did, pull.PullAt())
201
201
}
202
202
203
+
labelDefs, err := db.GetLabelDefinitions(
204
+
s.db,
205
+
db.FilterIn("at_uri", f.Repo.Labels),
206
+
db.FilterContains("scope", tangled.RepoPullNSID),
207
+
)
208
+
if err != nil {
209
+
log.Println("failed to fetch labels", err)
210
+
s.pages.Error503(w)
211
+
return
212
+
}
213
+
214
+
defs := make(map[string]*models.LabelDefinition)
215
+
for _, l := range labelDefs {
216
+
defs[l.AtUri().String()] = &l
217
+
}
218
+
203
219
s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{
204
220
LoggedInUser: user,
205
221
RepoInfo: repoInfo,
···
213
229
OrderedReactionKinds: models.OrderedReactionKinds,
214
230
Reactions: reactionCountMap,
215
231
UserReacted: userReactions,
232
+
233
+
LabelDefs: defs,
216
234
})
217
235
}
218
236
···
557
575
m[p.Sha] = p
558
576
}
559
577
578
+
labelDefs, err := db.GetLabelDefinitions(
579
+
s.db,
580
+
db.FilterIn("at_uri", f.Repo.Labels),
581
+
db.FilterContains("scope", tangled.RepoPullNSID),
582
+
)
583
+
if err != nil {
584
+
log.Println("failed to fetch labels", err)
585
+
s.pages.Error503(w)
586
+
return
587
+
}
588
+
589
+
defs := make(map[string]*models.LabelDefinition)
590
+
for _, l := range labelDefs {
591
+
defs[l.AtUri().String()] = &l
592
+
}
593
+
560
594
s.pages.RepoPulls(w, pages.RepoPullsParams{
561
595
LoggedInUser: s.oauth.GetUser(r),
562
596
RepoInfo: f.RepoInfo(user),
563
597
Pulls: pulls,
598
+
LabelDefs: defs,
564
599
FilteringBy: state,
565
600
Stacks: stacks,
566
601
Pipelines: m,
+8
-48
appview/notify/db/db.go
+8
-48
appview/notify/db/db.go
···
30
30
31
31
func (n *databaseNotifier) NewStar(ctx context.Context, star *models.Star) {
32
32
var err error
33
-
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(star.RepoAt)))
33
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(star.RepoAt)))
34
34
if err != nil {
35
35
log.Printf("NewStar: failed to get repos: %v", err)
36
36
return
37
37
}
38
-
if len(repos) == 0 {
39
-
log.Printf("NewStar: no repo found for %s", star.RepoAt)
40
-
return
41
-
}
42
-
repo := repos[0]
43
38
44
39
// don't notify yourself
45
40
if repo.Did == star.StarredByDid {
···
76
71
}
77
72
78
73
func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) {
79
-
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(issue.RepoAt)))
74
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(issue.RepoAt)))
80
75
if err != nil {
81
76
log.Printf("NewIssue: failed to get repos: %v", err)
82
77
return
83
78
}
84
-
if len(repos) == 0 {
85
-
log.Printf("NewIssue: no repo found for %s", issue.RepoAt)
86
-
return
87
-
}
88
-
repo := repos[0]
89
79
90
80
if repo.Did == issue.Did {
91
81
return
···
129
119
}
130
120
issue := issues[0]
131
121
132
-
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(issue.RepoAt)))
122
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(issue.RepoAt)))
133
123
if err != nil {
134
124
log.Printf("NewIssueComment: failed to get repos: %v", err)
135
125
return
136
126
}
137
-
if len(repos) == 0 {
138
-
log.Printf("NewIssueComment: no repo found for %s", issue.RepoAt)
139
-
return
140
-
}
141
-
repo := repos[0]
142
127
143
128
recipients := make(map[string]bool)
144
129
···
211
196
}
212
197
213
198
func (n *databaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {
214
-
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(pull.RepoAt)))
199
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt)))
215
200
if err != nil {
216
201
log.Printf("NewPull: failed to get repos: %v", err)
217
202
return
218
203
}
219
-
if len(repos) == 0 {
220
-
log.Printf("NewPull: no repo found for %s", pull.RepoAt)
221
-
return
222
-
}
223
-
repo := repos[0]
224
204
225
205
if repo.Did == pull.OwnerDid {
226
206
return
···
266
246
}
267
247
pull := pulls[0]
268
248
269
-
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", comment.RepoAt))
249
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", comment.RepoAt))
270
250
if err != nil {
271
251
log.Printf("NewPullComment: failed to get repos: %v", err)
272
252
return
273
253
}
274
-
if len(repos) == 0 {
275
-
log.Printf("NewPullComment: no repo found for %s", comment.RepoAt)
276
-
return
277
-
}
278
-
repo := repos[0]
279
254
280
255
recipients := make(map[string]bool)
281
256
···
335
310
336
311
func (n *databaseNotifier) NewIssueClosed(ctx context.Context, issue *models.Issue) {
337
312
// Get repo details
338
-
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(issue.RepoAt)))
313
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(issue.RepoAt)))
339
314
if err != nil {
340
315
log.Printf("NewIssueClosed: failed to get repos: %v", err)
341
316
return
342
317
}
343
-
if len(repos) == 0 {
344
-
log.Printf("NewIssueClosed: no repo found for %s", issue.RepoAt)
345
-
return
346
-
}
347
-
repo := repos[0]
348
318
349
319
// Don't notify yourself
350
320
if repo.Did == issue.Did {
···
380
350
381
351
func (n *databaseNotifier) NewPullMerged(ctx context.Context, pull *models.Pull) {
382
352
// Get repo details
383
-
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(pull.RepoAt)))
353
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt)))
384
354
if err != nil {
385
355
log.Printf("NewPullMerged: failed to get repos: %v", err)
386
356
return
387
357
}
388
-
if len(repos) == 0 {
389
-
log.Printf("NewPullMerged: no repo found for %s", pull.RepoAt)
390
-
return
391
-
}
392
-
repo := repos[0]
393
358
394
359
// Don't notify yourself
395
360
if repo.Did == pull.OwnerDid {
···
425
390
426
391
func (n *databaseNotifier) NewPullClosed(ctx context.Context, pull *models.Pull) {
427
392
// Get repo details
428
-
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(pull.RepoAt)))
393
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt)))
429
394
if err != nil {
430
395
log.Printf("NewPullClosed: failed to get repos: %v", err)
431
396
return
432
397
}
433
-
if len(repos) == 0 {
434
-
log.Printf("NewPullClosed: no repo found for %s", pull.RepoAt)
435
-
return
436
-
}
437
-
repo := repos[0]
438
398
439
399
// Don't notify yourself
440
400
if repo.Did == pull.OwnerDid {
+29
-1
appview/models/notifications.go
+29
-1
appview/models/notifications.go
···
1
1
package models
2
2
3
-
import "time"
3
+
import (
4
+
"time"
5
+
)
4
6
5
7
type NotificationType string
6
8
···
32
34
PullId *int64
33
35
}
34
36
37
+
// lucide icon that represents this notification
38
+
func (n *Notification) Icon() string {
39
+
switch n.Type {
40
+
case NotificationTypeRepoStarred:
41
+
return "star"
42
+
case NotificationTypeIssueCreated:
43
+
return "circle-dot"
44
+
case NotificationTypeIssueCommented:
45
+
return "message-square"
46
+
case NotificationTypeIssueClosed:
47
+
return "ban"
48
+
case NotificationTypePullCreated:
49
+
return "git-pull-request-create"
50
+
case NotificationTypePullCommented:
51
+
return "message-square"
52
+
case NotificationTypePullMerged:
53
+
return "git-merge"
54
+
case NotificationTypePullClosed:
55
+
return "git-pull-request-closed"
56
+
case NotificationTypeFollowed:
57
+
return "user-plus"
58
+
default:
59
+
return ""
60
+
}
61
+
}
62
+
35
63
type NotificationWithEntity struct {
36
64
*Notification
37
65
Repo *Repo
+3
-4
appview/pages/pages.go
+3
-4
appview/pages/pages.go
···
326
326
LoggedInUser *oauth.User
327
327
Notifications []*models.NotificationWithEntity
328
328
UnreadCount int
329
-
HasMore bool
330
-
NextOffset int
331
-
Limit int
329
+
Page pagination.Page
330
+
Total int64
332
331
}
333
332
334
333
func (p *Pages) Notifications(w io.Writer, params NotificationsParams) error {
···
344
343
}
345
344
346
345
type NotificationCountParams struct {
347
-
Count int
346
+
Count int64
348
347
}
349
348
350
349
func (p *Pages) NotificationCount(w io.Writer, params NotificationCountParams) error {
+1
-1
appview/pagination/page.go
+1
-1
appview/pagination/page.go
+1
-1
nix/pkgs/knot-unwrapped.nix
+1
-1
nix/pkgs/knot-unwrapped.nix
+1
-1
appview/pages/templates/repo/fragments/cloneDropdown.html
+1
-1
appview/pages/templates/repo/fragments/cloneDropdown.html
+1
-1
docs/spindle/pipeline.md
+1
-1
docs/spindle/pipeline.md
···
21
21
- `manual`: The workflow can be triggered manually.
22
22
- `branch`: This is a **required** field that defines which branches the workflow should run for. If used with the `push` event, commits to the branch(es) listed here will trigger the workflow. If used with the `pull_request` event, updates to pull requests targeting the branch(es) listed here will trigger the workflow. This field has no effect with the `manual` event.
23
23
24
-
For example, if you'd like define a workflow that runs when commits are pushed to the `main` and `develop` branches, or when pull requests that target the `main` branch are updated, or manually, you can do so with:
24
+
For example, if you'd like to define a workflow that runs when commits are pushed to the `main` and `develop` branches, or when pull requests that target the `main` branch are updated, or manually, you can do so with:
25
25
26
26
```yaml
27
27
when:
+1
-1
knotserver/config/config.go
+1
-1
knotserver/config/config.go
···
41
41
Repo Repo `env:",prefix=KNOT_REPO_"`
42
42
Server Server `env:",prefix=KNOT_SERVER_"`
43
43
Git Git `env:",prefix=KNOT_GIT_"`
44
-
AppViewEndpoint string `env:"APPVIEW_ENDPOINT, default=https://tangled.sh"`
44
+
AppViewEndpoint string `env:"APPVIEW_ENDPOINT, default=https://tangled.org"`
45
45
}
46
46
47
47
func Load(ctx context.Context) (*Config, error) {
+1
appview/pages/templates/user/completeSignup.html
+1
appview/pages/templates/user/completeSignup.html
+1
appview/pages/templates/user/signup.html
+1
appview/pages/templates/user/signup.html
···
8
8
<meta property="og:url" content="https://tangled.org/signup" />
9
9
<meta property="og:description" content="sign up for tangled" />
10
10
<script src="/static/htmx.min.js"></script>
11
+
<link rel="manifest" href="/pwa-manifest.json" />
11
12
<link rel="stylesheet" href="/static/tw.css?{{ cssContentHash }}" type="text/css" />
12
13
<title>sign up · tangled</title>
13
14
+1
appview/state/router.go
+1
appview/state/router.go
+23
appview/state/state.go
+23
appview/state/state.go
···
198
198
s.pages.Favicon(w)
199
199
}
200
200
201
+
// https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest
202
+
const manifestJson = `{
203
+
"name": "tangled",
204
+
"description": "tightly-knit social coding.",
205
+
"icons": [
206
+
{
207
+
"src": "/favicon.svg",
208
+
"sizes": "144x144"
209
+
}
210
+
],
211
+
"start_url": "/",
212
+
"id": "org.tangled",
213
+
214
+
"display": "standalone",
215
+
"background_color": "#111827",
216
+
"theme_color": "#111827"
217
+
}`
218
+
219
+
func (p *State) PWAManifest(w http.ResponseWriter, r *http.Request) {
220
+
w.Header().Set("Content-Type", "application/json")
221
+
w.Write([]byte(manifestJson))
222
+
}
223
+
201
224
func (s *State) TermsOfService(w http.ResponseWriter, r *http.Request) {
202
225
user := s.oauth.GetUser(r)
203
226
s.pages.TermsOfService(w, pages.TermsOfServiceParams{
+1
-1
appview/pages/templates/layouts/fragments/topbar.html
+1
-1
appview/pages/templates/layouts/fragments/topbar.html
···
1
1
{{ define "layouts/fragments/topbar" }}
2
-
<nav class="space-x-4 px-6 py-2 rounded-b bg-white dark:bg-gray-800 dark:text-white drop-shadow-sm">
2
+
<nav class="mx-auto space-x-4 px-6 py-2 rounded-b dark:text-white drop-shadow-sm">
3
3
<div class="flex justify-between p-0 items-center">
4
4
<div id="left-items">
5
5
<a href="/" hx-boost="true" class="text-2xl no-underline hover:no-underline flex items-center gap-2">