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.
ERROR
appview/pages/templates/layouts/fragments/topbar.html
ERROR
appview/pages/templates/layouts/fragments/topbar.html
Failed to calculate interdiff for this file.
NEW
appview/config/config.go
NEW
appview/config/config.go
NEW
appview/db/email.go
NEW
appview/db/email.go
···
71
return did, nil
72
}
73
74
-
func GetEmailToDid(e Execer, ems []string, isVerifiedFilter bool) (map[string]string, error) {
75
-
if len(ems) == 0 {
76
return make(map[string]string), nil
77
}
78
···
81
verifiedFilter = 1
82
}
83
84
// Create placeholders for the IN clause
85
-
placeholders := make([]string, len(ems))
86
-
args := make([]any, len(ems)+1)
87
88
args[0] = verifiedFilter
89
-
for i, em := range ems {
90
-
placeholders[i] = "?"
91
-
args[i+1] = em
92
}
93
94
query := `
···
105
}
106
defer rows.Close()
107
108
-
assoc := make(map[string]string)
109
-
110
for rows.Next() {
111
var email, did string
112
if err := rows.Scan(&email, &did); err != nil {
···
71
return did, nil
72
}
73
74
+
func GetEmailToDid(e Execer, emails []string, isVerifiedFilter bool) (map[string]string, error) {
75
+
if len(emails) == 0 {
76
return make(map[string]string), nil
77
}
78
···
81
verifiedFilter = 1
82
}
83
84
+
assoc := make(map[string]string)
85
+
86
// Create placeholders for the IN clause
87
+
placeholders := make([]string, 0, len(emails))
88
+
args := make([]any, 1, len(emails)+1)
89
90
args[0] = verifiedFilter
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)
98
}
99
100
query := `
···
111
}
112
defer rows.Close()
113
114
for rows.Next() {
115
var email, did string
116
if err := rows.Scan(&email, &did); err != nil {
NEW
appview/pages/templates/repo/fork.html
NEW
appview/pages/templates/repo/fork.html
···
6
</div>
7
<div class="p-6 bg-white dark:bg-gray-800 drop-shadow-sm rounded">
8
<form hx-post="/{{ .RepoInfo.FullName }}/fork" class="space-y-12" hx-swap="none" hx-indicator="#spinner">
9
<fieldset class="space-y-3">
10
<legend class="dark:text-white">Select a knot to fork into</legend>
11
<div class="space-y-2">
···
6
</div>
7
<div class="p-6 bg-white dark:bg-gray-800 drop-shadow-sm rounded">
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
+
16
<fieldset class="space-y-3">
17
<legend class="dark:text-white">Select a knot to fork into</legend>
18
<div class="space-y-2">
NEW
appview/repo/repo.go
NEW
appview/repo/repo.go
···
2129
}
2130
2131
// choose a name for a fork
2132
-
forkName := f.Name
2133
// this check is *only* to see if the forked repo name already exists
2134
// in the user's account.
2135
existingRepo, err := db.GetRepo(
2136
rp.db,
2137
db.FilterEq("did", user.Did),
2138
-
db.FilterEq("name", f.Name),
2139
)
2140
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 {
2144
log.Println("error fetching existing repo from db", "err", err)
2145
rp.pages.Notice(w, "repo", "Failed to fork this repository. Try again later.")
2146
return
2147
}
2148
} else if existingRepo != nil {
2149
-
// repo with this name already exists, append random string
2150
-
forkName = fmt.Sprintf("%s-%s", forkName, randomString(3))
2151
}
2152
l = l.With("forkName", forkName)
2153
···
2129
}
2130
2131
// choose a name for a fork
2132
+
forkName := r.FormValue("repo_name")
2133
+
if forkName == "" {
2134
+
rp.pages.Notice(w, "repo", "Repository name cannot be empty.")
2135
+
return
2136
+
}
2137
+
2138
// this check is *only* to see if the forked repo name already exists
2139
// in the user's account.
2140
existingRepo, err := db.GetRepo(
2141
rp.db,
2142
db.FilterEq("did", user.Did),
2143
+
db.FilterEq("name", forkName),
2144
)
2145
if err != nil {
2146
+
if !errors.Is(err, sql.ErrNoRows) {
2147
log.Println("error fetching existing repo from db", "err", err)
2148
rp.pages.Notice(w, "repo", "Failed to fork this repository. Try again later.")
2149
return
2150
}
2151
} else if existingRepo != nil {
2152
+
// repo with this name already exists
2153
+
rp.pages.Notice(w, "repo", "A repository with this name already exists.")
2154
+
return
2155
}
2156
l = l.With("forkName", forkName)
2157
NEW
appview/db/db.go
NEW
appview/db/db.go
···
954
return err
955
})
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
+
1097
return &DB{db}, nil
1098
}
1099
NEW
appview/issues/issues.go
NEW
appview/issues/issues.go
NEW
appview/pages/templates/repo/fragments/labelPanel.html
NEW
appview/pages/templates/repo/fragments/labelPanel.html
NEW
appview/pages/templates/repo/fragments/participants.html
NEW
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 }}
NEW
appview/pages/templates/repo/issues/issue.html
NEW
appview/pages/templates/repo/issues/issue.html
···
22
"Defs" $.LabelDefs
23
"Subject" $.Issue.AtUri
24
"State" $.Issue.Labels) }}
25
-
{{ template "issueParticipants" . }}
26
</div>
27
</div>
28
{{ end }}
···
122
</div>
123
{{ end }}
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
152
{{ define "repoAfter" }}
153
<div class="flex flex-col gap-4 mt-4">
NEW
appview/pages/templates/repo/pulls/pull.html
NEW
appview/pages/templates/repo/pulls/pull.html
···
9
{{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo "Title" $title "Url" $url) }}
10
{{ end }}
11
12
13
{{ define "repoContent" }}
14
{{ template "repo/pulls/fragments/pullHeader" . }}
···
39
{{ with $item }}
40
<details {{ if eq $idx $lastIdx }}open{{ end }}>
41
<summary id="round-#{{ .RoundNumber }}" class="list-none cursor-pointer">
42
-
<div class="flex flex-wrap gap-2 items-center">
43
<!-- round number -->
44
<div class="rounded bg-white dark:bg-gray-800 drop-shadow-sm px-3 py-2 dark:text-white">
45
<span class="flex items-center">{{ i "hash" "w-4 h-4" }}{{ .RoundNumber }}</span>
46
</div>
47
<!-- round summary -->
48
-
<div class="rounded drop-shadow-sm bg-white dark:bg-gray-800 p-2 text-gray-500 dark:text-gray-400">
49
<span class="gap-1 flex items-center">
50
{{ $owner := resolve $.Pull.OwnerDid }}
51
{{ $re := "re" }}
···
72
<span class="hidden md:inline">diff</span>
73
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
74
</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>
84
{{ end }}
85
</div>
86
</summary>
87
···
146
147
<div class="md:pl-[3.5rem] flex flex-col gap-2 mt-2 relative">
148
{{ 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">
150
{{ if gt $cidx 0 }}
151
<div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>
152
{{ end }}
···
9
{{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo "Title" $title "Url" $url) }}
10
{{ end }}
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 }}
30
31
{{ define "repoContent" }}
32
{{ template "repo/pulls/fragments/pullHeader" . }}
···
57
{{ with $item }}
58
<details {{ if eq $idx $lastIdx }}open{{ end }}>
59
<summary id="round-#{{ .RoundNumber }}" class="list-none cursor-pointer">
60
+
<div class="flex flex-wrap gap-2 items-stretch">
61
<!-- round number -->
62
<div class="rounded bg-white dark:bg-gray-800 drop-shadow-sm px-3 py-2 dark:text-white">
63
<span class="flex items-center">{{ i "hash" "w-4 h-4" }}{{ .RoundNumber }}</span>
64
</div>
65
<!-- round summary -->
66
+
<div class="flex-1 rounded drop-shadow-sm bg-white dark:bg-gray-800 p-2 text-gray-500 dark:text-gray-400">
67
<span class="gap-1 flex items-center">
68
{{ $owner := resolve $.Pull.OwnerDid }}
69
{{ $re := "re" }}
···
90
<span class="hidden md:inline">diff</span>
91
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
92
</a>
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>
101
{{ end }}
102
+
<span id="interdiff-error-{{.RoundNumber}}"></span>
103
</div>
104
</summary>
105
···
164
165
<div class="md:pl-[3.5rem] flex flex-col gap-2 mt-2 relative">
166
{{ range $cidx, $c := .Comments }}
167
+
<div id="comment-{{$c.ID}}" class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-2 px-4 relative w-full">
168
{{ if gt $cidx 0 }}
169
<div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>
170
{{ end }}
NEW
appview/pages/templates/repo/pulls/pulls.html
NEW
appview/pages/templates/repo/pulls/pulls.html
···
108
<span class="before:content-['·']"></span>
109
{{ template "repo/pipelines/fragments/pipelineSymbol" $pipeline }}
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 }}
118
</div>
119
</div>
120
{{ if .StackId }}
NEW
appview/pulls/pulls.go
NEW
appview/pulls/pulls.go
···
200
userReactions = db.GetReactionStatusMap(s.db, user.Did, pull.PullAt())
201
}
202
203
s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{
204
LoggedInUser: user,
205
RepoInfo: repoInfo,
···
213
OrderedReactionKinds: models.OrderedReactionKinds,
214
Reactions: reactionCountMap,
215
UserReacted: userReactions,
216
})
217
}
218
···
557
m[p.Sha] = p
558
}
559
560
s.pages.RepoPulls(w, pages.RepoPullsParams{
561
LoggedInUser: s.oauth.GetUser(r),
562
RepoInfo: f.RepoInfo(user),
563
Pulls: pulls,
564
FilteringBy: state,
565
Stacks: stacks,
566
Pipelines: m,
···
200
userReactions = db.GetReactionStatusMap(s.db, user.Did, pull.PullAt())
201
}
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
+
219
s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{
220
LoggedInUser: user,
221
RepoInfo: repoInfo,
···
229
OrderedReactionKinds: models.OrderedReactionKinds,
230
Reactions: reactionCountMap,
231
UserReacted: userReactions,
232
+
233
+
LabelDefs: defs,
234
})
235
}
236
···
575
m[p.Sha] = p
576
}
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
+
594
s.pages.RepoPulls(w, pages.RepoPullsParams{
595
LoggedInUser: s.oauth.GetUser(r),
596
RepoInfo: f.RepoInfo(user),
597
Pulls: pulls,
598
+
LabelDefs: defs,
599
FilteringBy: state,
600
Stacks: stacks,
601
Pipelines: m,
NEW
appview/notify/db/db.go
NEW
appview/notify/db/db.go
···
30
31
func (n *databaseNotifier) NewStar(ctx context.Context, star *models.Star) {
32
var err error
33
-
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(star.RepoAt)))
34
if err != nil {
35
log.Printf("NewStar: failed to get repos: %v", err)
36
return
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
44
// don't notify yourself
45
if repo.Did == star.StarredByDid {
···
76
}
77
78
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)))
80
if err != nil {
81
log.Printf("NewIssue: failed to get repos: %v", err)
82
return
83
}
84
-
if len(repos) == 0 {
85
-
log.Printf("NewIssue: no repo found for %s", issue.RepoAt)
86
-
return
87
-
}
88
-
repo := repos[0]
89
90
if repo.Did == issue.Did {
91
return
···
129
}
130
issue := issues[0]
131
132
-
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(issue.RepoAt)))
133
if err != nil {
134
log.Printf("NewIssueComment: failed to get repos: %v", err)
135
return
136
}
137
-
if len(repos) == 0 {
138
-
log.Printf("NewIssueComment: no repo found for %s", issue.RepoAt)
139
-
return
140
-
}
141
-
repo := repos[0]
142
143
recipients := make(map[string]bool)
144
···
211
}
212
213
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)))
215
if err != nil {
216
log.Printf("NewPull: failed to get repos: %v", err)
217
return
218
}
219
-
if len(repos) == 0 {
220
-
log.Printf("NewPull: no repo found for %s", pull.RepoAt)
221
-
return
222
-
}
223
-
repo := repos[0]
224
225
if repo.Did == pull.OwnerDid {
226
return
···
266
}
267
pull := pulls[0]
268
269
-
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", comment.RepoAt))
270
if err != nil {
271
log.Printf("NewPullComment: failed to get repos: %v", err)
272
return
273
}
274
-
if len(repos) == 0 {
275
-
log.Printf("NewPullComment: no repo found for %s", comment.RepoAt)
276
-
return
277
-
}
278
-
repo := repos[0]
279
280
recipients := make(map[string]bool)
281
···
335
336
func (n *databaseNotifier) NewIssueClosed(ctx context.Context, issue *models.Issue) {
337
// Get repo details
338
-
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(issue.RepoAt)))
339
if err != nil {
340
log.Printf("NewIssueClosed: failed to get repos: %v", err)
341
return
342
}
343
-
if len(repos) == 0 {
344
-
log.Printf("NewIssueClosed: no repo found for %s", issue.RepoAt)
345
-
return
346
-
}
347
-
repo := repos[0]
348
349
// Don't notify yourself
350
if repo.Did == issue.Did {
···
380
381
func (n *databaseNotifier) NewPullMerged(ctx context.Context, pull *models.Pull) {
382
// Get repo details
383
-
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(pull.RepoAt)))
384
if err != nil {
385
log.Printf("NewPullMerged: failed to get repos: %v", err)
386
return
387
}
388
-
if len(repos) == 0 {
389
-
log.Printf("NewPullMerged: no repo found for %s", pull.RepoAt)
390
-
return
391
-
}
392
-
repo := repos[0]
393
394
// Don't notify yourself
395
if repo.Did == pull.OwnerDid {
···
425
426
func (n *databaseNotifier) NewPullClosed(ctx context.Context, pull *models.Pull) {
427
// Get repo details
428
-
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(pull.RepoAt)))
429
if err != nil {
430
log.Printf("NewPullClosed: failed to get repos: %v", err)
431
return
432
}
433
-
if len(repos) == 0 {
434
-
log.Printf("NewPullClosed: no repo found for %s", pull.RepoAt)
435
-
return
436
-
}
437
-
repo := repos[0]
438
439
// Don't notify yourself
440
if repo.Did == pull.OwnerDid {
···
30
31
func (n *databaseNotifier) NewStar(ctx context.Context, star *models.Star) {
32
var err error
33
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(star.RepoAt)))
34
if err != nil {
35
log.Printf("NewStar: failed to get repos: %v", err)
36
return
37
}
38
39
// don't notify yourself
40
if repo.Did == star.StarredByDid {
···
71
}
72
73
func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) {
74
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(issue.RepoAt)))
75
if err != nil {
76
log.Printf("NewIssue: failed to get repos: %v", err)
77
return
78
}
79
80
if repo.Did == issue.Did {
81
return
···
119
}
120
issue := issues[0]
121
122
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(issue.RepoAt)))
123
if err != nil {
124
log.Printf("NewIssueComment: failed to get repos: %v", err)
125
return
126
}
127
128
recipients := make(map[string]bool)
129
···
196
}
197
198
func (n *databaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {
199
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt)))
200
if err != nil {
201
log.Printf("NewPull: failed to get repos: %v", err)
202
return
203
}
204
205
if repo.Did == pull.OwnerDid {
206
return
···
246
}
247
pull := pulls[0]
248
249
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", comment.RepoAt))
250
if err != nil {
251
log.Printf("NewPullComment: failed to get repos: %v", err)
252
return
253
}
254
255
recipients := make(map[string]bool)
256
···
310
311
func (n *databaseNotifier) NewIssueClosed(ctx context.Context, issue *models.Issue) {
312
// Get repo details
313
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(issue.RepoAt)))
314
if err != nil {
315
log.Printf("NewIssueClosed: failed to get repos: %v", err)
316
return
317
}
318
319
// Don't notify yourself
320
if repo.Did == issue.Did {
···
350
351
func (n *databaseNotifier) NewPullMerged(ctx context.Context, pull *models.Pull) {
352
// Get repo details
353
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt)))
354
if err != nil {
355
log.Printf("NewPullMerged: failed to get repos: %v", err)
356
return
357
}
358
359
// Don't notify yourself
360
if repo.Did == pull.OwnerDid {
···
390
391
func (n *databaseNotifier) NewPullClosed(ctx context.Context, pull *models.Pull) {
392
// Get repo details
393
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt)))
394
if err != nil {
395
log.Printf("NewPullClosed: failed to get repos: %v", err)
396
return
397
}
398
399
// Don't notify yourself
400
if repo.Did == pull.OwnerDid {
NEW
appview/models/notifications.go
NEW
appview/models/notifications.go
···
1
package models
2
3
+
import (
4
+
"time"
5
+
)
6
7
type NotificationType string
8
···
34
PullId *int64
35
}
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
+
63
type NotificationWithEntity struct {
64
*Notification
65
Repo *Repo
NEW
appview/pages/pages.go
NEW
appview/pages/pages.go
···
326
LoggedInUser *oauth.User
327
Notifications []*models.NotificationWithEntity
328
UnreadCount int
329
-
HasMore bool
330
-
NextOffset int
331
-
Limit int
332
}
333
334
func (p *Pages) Notifications(w io.Writer, params NotificationsParams) error {
···
344
}
345
346
type NotificationCountParams struct {
347
-
Count int
348
}
349
350
func (p *Pages) NotificationCount(w io.Writer, params NotificationCountParams) error {
···
326
LoggedInUser *oauth.User
327
Notifications []*models.NotificationWithEntity
328
UnreadCount int
329
+
Page pagination.Page
330
+
Total int64
331
}
332
333
func (p *Pages) Notifications(w io.Writer, params NotificationsParams) error {
···
343
}
344
345
type NotificationCountParams struct {
346
+
Count int64
347
}
348
349
func (p *Pages) NotificationCount(w io.Writer, params NotificationCountParams) error {
NEW
appview/pagination/page.go
NEW
appview/pagination/page.go
NEW
nix/pkgs/knot-unwrapped.nix
NEW
nix/pkgs/knot-unwrapped.nix
NEW
appview/pages/templates/repo/fragments/cloneDropdown.html
NEW
appview/pages/templates/repo/fragments/cloneDropdown.html
NEW
docs/spindle/pipeline.md
NEW
docs/spindle/pipeline.md
···
21
- `manual`: The workflow can be triggered manually.
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
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:
25
26
```yaml
27
when:
···
21
- `manual`: The workflow can be triggered manually.
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
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
26
```yaml
27
when:
NEW
knotserver/config/config.go
NEW
knotserver/config/config.go
NEW
appview/pages/templates/user/completeSignup.html
NEW
appview/pages/templates/user/completeSignup.html
NEW
appview/pages/templates/user/signup.html
NEW
appview/pages/templates/user/signup.html
···
8
<meta property="og:url" content="https://tangled.org/signup" />
9
<meta property="og:description" content="sign up for tangled" />
10
<script src="/static/htmx.min.js"></script>
11
<link rel="stylesheet" href="/static/tw.css?{{ cssContentHash }}" type="text/css" />
12
<title>sign up · tangled</title>
13
···
8
<meta property="og:url" content="https://tangled.org/signup" />
9
<meta property="og:description" content="sign up for tangled" />
10
<script src="/static/htmx.min.js"></script>
11
+
<link rel="manifest" href="/pwa-manifest.json" />
12
<link rel="stylesheet" href="/static/tw.css?{{ cssContentHash }}" type="text/css" />
13
<title>sign up · tangled</title>
14
NEW
appview/state/router.go
NEW
appview/state/router.go
NEW
appview/state/state.go
NEW
appview/state/state.go
···
198
s.pages.Favicon(w)
199
}
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
+
224
func (s *State) TermsOfService(w http.ResponseWriter, r *http.Request) {
225
user := s.oauth.GetUser(r)
226
s.pages.TermsOfService(w, pages.TermsOfServiceParams{