+5
appview/db/pulls.go
+5
appview/db/pulls.go
+4
-4
appview/pages/templates/repo/issues/issue.html
+4
-4
appview/pages/templates/repo/issues/issue.html
···
1
1
{{ define "title" }}
2
-
{{ .Issue.Title }} ·
2
+
{{ .Issue.Title }} · issue #{{ .Issue.IssueId }} ·
3
3
{{ .RepoInfo.FullName }}
4
4
{{ end }}
5
5
6
6
{{ define "repoContent" }}
7
-
<header>
8
-
<p class="text-2xl">
7
+
<header class="pb-4">
8
+
<h1 class="text-2xl">
9
9
{{ .Issue.Title }}
10
10
<span class="text-gray-500">#{{ .Issue.IssueId }}</span>
11
-
</p>
11
+
</h1>
12
12
</header>
13
13
14
14
{{ $bgColor := "bg-gray-800" }}
+135
-34
appview/pages/templates/repo/pulls/pull.html
+135
-34
appview/pages/templates/repo/pulls/pull.html
···
4
4
{{ end }}
5
5
6
6
{{ define "repoContent" }}
7
-
<h1>
8
-
{{ .Pull.Title }}
9
-
<span class="text-gray-400">#{{ .Pull.PullId }}</span>
10
-
</h1>
7
+
8
+
<header class="pb-4">
9
+
<h1 class="text-2xl">
10
+
{{ .Pull.Title }}
11
+
<span class="text-gray-500">#{{ .Pull.PullId }}</span>
12
+
</h1>
13
+
</header>
14
+
11
15
{{ $bgColor := "bg-gray-800" }}
12
16
{{ $icon := "ban" }}
13
17
{{ if eq .State "open" }}
···
49
53
{{ end }}
50
54
</section>
51
55
52
-
<div>
56
+
<div class="flex flex-col justify-end mt-4">
53
57
<details>
54
58
<summary
55
-
class="list-none cursor-pointer sticky top-0 bg-white rounded-sm px-3 py-2 border border-gray-200 flex items-center text-gray-700 hover:bg-gray-50 transition-colors"
59
+
class="list-none cursor-pointer sticky top-0 bg-white rounded-sm px-3 py-2 border border-gray-200 flex items-center text-gray-700 hover:bg-gray-50 transition-colors mt-auto"
56
60
>
57
61
<i data-lucide="code" class="w-4 h-4 mr-2"></i>
58
62
<span>patch</span>
59
63
</summary>
60
-
<pre class="font-mono overflow-x-scroll bg-gray-50 p-4 rounded-b border border-gray-200 text-sm">
64
+
<div class="relative">
65
+
<pre
66
+
id="patch-preview"
67
+
class="font-mono overflow-x-scroll bg-gray-50 p-4 rounded-b border border-gray-200 text-sm"
68
+
>
61
69
{{- .Pull.Patch -}}
62
-
</pre>
70
+
</pre
71
+
>
72
+
<form
73
+
id="patch-form"
74
+
hx-patch="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/patch"
75
+
hx-swap="none"
76
+
>
77
+
<textarea
78
+
id="patch"
79
+
name="patch"
80
+
class="font-mono w-full h-full p-4 rounded-b border border-gray-200 text-sm hidden"
81
+
>
82
+
{{- .Pull.Patch -}}</textarea
83
+
>
84
+
85
+
<div class="flex gap-2 justify-end mt-2">
86
+
<button
87
+
id="edit-patch-btn"
88
+
type="button"
89
+
class="btn btn-sm"
90
+
onclick="togglePatchEdit(true)"
91
+
>
92
+
<i data-lucide="edit" class="w-4 h-4 mr-1"></i>Edit
93
+
</button>
94
+
<button
95
+
id="save-patch-btn"
96
+
type="submit"
97
+
class="btn btn-sm bg-green-500 hidden"
98
+
>
99
+
<i data-lucide="save" class="w-4 h-4 mr-1"></i>Save
100
+
</button>
101
+
<button
102
+
id="cancel-patch-btn"
103
+
type="button"
104
+
class="btn btn-sm bg-gray-300 hidden"
105
+
onclick="togglePatchEdit(false)"
106
+
>
107
+
Cancel
108
+
</button>
109
+
</div>
110
+
</form>
111
+
112
+
<div id="pull-error" class="error"></div>
113
+
<div id="pull-success" class="success"></div>
114
+
</div>
115
+
<script>
116
+
function togglePatchEdit(editMode) {
117
+
const preview = document.getElementById("patch-preview");
118
+
const editor = document.getElementById("patch");
119
+
const editBtn = document.getElementById("edit-patch-btn");
120
+
const saveBtn = document.getElementById("save-patch-btn");
121
+
const cancelBtn =
122
+
document.getElementById("cancel-patch-btn");
123
+
124
+
if (editMode) {
125
+
preview.classList.add("hidden");
126
+
editor.classList.remove("hidden");
127
+
editBtn.classList.add("hidden");
128
+
saveBtn.classList.remove("hidden");
129
+
cancelBtn.classList.remove("hidden");
130
+
} else {
131
+
preview.classList.remove("hidden");
132
+
editor.classList.add("hidden");
133
+
editBtn.classList.remove("hidden");
134
+
saveBtn.classList.add("hidden");
135
+
cancelBtn.classList.add("hidden");
136
+
}
137
+
}
138
+
139
+
document
140
+
.getElementById("save-patch-btn")
141
+
.addEventListener("click", function () {
142
+
togglePatchEdit(false);
143
+
});
144
+
</script>
63
145
</details>
64
146
</div>
65
-
66
-
<div class="mt-4">
67
-
{{ if .MergeCheck }}
68
-
<div class="rounded-sm border p-4 {{ if .MergeCheck.IsConflicted }}bg-red-50 border-red-200{{ else }}bg-green-50 border-green-200{{ end }}">
69
-
<div class="flex items-center gap-2 rounded-sm {{ if .MergeCheck.IsConflicted }}text-red-500{{ else }}text-green-500 {{ end }}">
70
-
{{ if .MergeCheck.IsConflicted }}
71
-
<i data-lucide="alert-triangle" class="w-4 h-4"></i>
72
-
<span class="font-medium">merge conflicts detected</span>
147
+
148
+
{{ if .MergeCheck }}
149
+
<div class="mt-4" id="merge-check">
150
+
<div
151
+
class="rounded-sm border p-4 {{ if .MergeCheck.IsConflicted }}
152
+
bg-red-50 border-red-200
73
153
{{ else }}
74
-
<i data-lucide="check-circle" class="w-4 h-4"></i>
75
-
<span class="font-medium">ready to merge</span>
154
+
bg-green-50 border-green-200
155
+
{{ end }}"
156
+
>
157
+
<div
158
+
class="flex items-center gap-2 rounded-sm {{ if .MergeCheck.IsConflicted }}
159
+
text-red-500
160
+
{{ else }}
161
+
text-green-500
162
+
{{ end }}"
163
+
>
164
+
{{ if .MergeCheck.IsConflicted }}
165
+
<i data-lucide="alert-triangle" class="w-4 h-4"></i>
166
+
<span class="font-medium"
167
+
>merge conflicts detected</span
168
+
>
169
+
{{ else }}
170
+
<i data-lucide="check-circle" class="w-4 h-4"></i>
171
+
<span class="font-medium">ready to merge</span>
172
+
{{ end }}
173
+
</div>
174
+
175
+
{{ if .MergeCheck.IsConflicted }}
176
+
<div class="mt-2">
177
+
<ul class="text-sm space-y-1">
178
+
{{ range .MergeCheck.Conflicts }}
179
+
<li class="flex items-center">
180
+
<i
181
+
data-lucide="file-warning"
182
+
class="w-3 h-3 mr-1.5 text-red-500"
183
+
></i>
184
+
<span class="font-mono"
185
+
>{{ slice .Filename 0 (sub (len .Filename) 2) }}</span
186
+
>
187
+
</li>
188
+
{{ end }}
189
+
</ul>
190
+
</div>
76
191
{{ end }}
77
192
</div>
78
-
79
-
{{ if .MergeCheck.IsConflicted }}
80
-
<div class="mt-2">
81
-
<ul class="text-sm space-y-1">
82
-
{{ range .MergeCheck.Conflicts }}
83
-
<li class="flex items-center">
84
-
<i data-lucide="file-warning" class="w-3 h-3 mr-1.5 text-red-500"></i>
85
-
<span class="font-mono">{{ slice .Filename 0 (sub (len .Filename) 2) }}</span>
86
-
</li>
87
-
{{ end }}
88
-
</ul>
89
-
</div>
90
-
{{ end }}
91
193
</div>
92
-
{{ end }}
93
-
</div>
194
+
{{ end }}
94
195
{{ end }}
95
196
96
197
{{ define "repoAfter" }}
···
134
235
</div>
135
236
</div>
136
237
{{ end }}
238
+
137
239
</section>
138
240
139
241
{{ if .LoggedInUser }}
···
172
274
></i>
173
275
<span class="text-black">{{ $action }}</span>
174
276
</button>
175
-
<div id="pull-action" class="error"></div>
176
277
</form>
177
278
{{ end }}
178
279
{{ end }}
+36
-14
appview/state/repo.go
+36
-14
appview/state/repo.go
···
230
230
}
231
231
}
232
232
233
-
// MergeCheck gets called async, every time the patch diff is updated in a pull.
234
-
func (s *State) MergeCheck(w http.ResponseWriter, r *http.Request) {
233
+
func (s *State) EditPatch(w http.ResponseWriter, r *http.Request) {
235
234
user := s.auth.GetUser(r)
236
235
f, err := fullyResolvedRepo(r)
237
236
if err != nil {
238
237
log.Println("failed to get repo and knot", err)
239
-
s.pages.Notice(w, "pull", "Failed to check mergeability. Try again later.")
238
+
s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
239
+
return
240
+
}
241
+
242
+
prId := chi.URLParam(r, "pull")
243
+
prIdInt, err := strconv.Atoi(prId)
244
+
if err != nil {
245
+
http.Error(w, "bad pr id", http.StatusBadRequest)
246
+
log.Println("failed to parse pr id", err)
240
247
return
241
248
}
242
249
243
250
patch := r.FormValue("patch")
244
-
targetBranch := r.FormValue("targetBranch")
251
+
if patch == "" {
252
+
s.pages.Notice(w, "pull-error", "Patch is required.")
253
+
return
254
+
}
245
255
246
-
if patch == "" || targetBranch == "" {
247
-
s.pages.Notice(w, "pull", "Patch and target branch are required.")
256
+
err = db.EditPatch(s.db, f.RepoAt, prIdInt, patch)
257
+
if err != nil {
258
+
log.Println("failed to update patch", err)
259
+
s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
248
260
return
249
261
}
250
262
263
+
// Get target branch after patch update
264
+
pull, _, err := db.GetPullWithComments(s.db, f.RepoAt, prIdInt)
265
+
if err != nil {
266
+
log.Println("failed to get pull information", err)
267
+
s.pages.Notice(w, "pull-success", "Patch updated successfully.")
268
+
return
269
+
}
270
+
271
+
targetBranch := pull.TargetBranch
272
+
273
+
// Perform merge check
251
274
secret, err := db.GetRegistrationKey(s.db, f.Knot)
252
275
if err != nil {
253
276
log.Printf("no key found for domain %s: %s\n", f.Knot, err)
254
-
s.pages.Notice(w, "pull", "Failed to check mergeability. Try again later.")
277
+
s.pages.Notice(w, "pull-success", "Patch updated successfully, but couldn't check mergeability.")
255
278
return
256
279
}
257
280
258
281
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev)
259
282
if err != nil {
260
283
log.Printf("failed to create signed client for %s", f.Knot)
261
-
s.pages.Notice(w, "pull", "Failed to check mergeability. Try again later.")
284
+
s.pages.Notice(w, "pull-success", "Patch updated successfully, but couldn't check mergeability.")
262
285
return
263
286
}
264
287
265
288
resp, err := ksClient.MergeCheck([]byte(patch), user.Did, f.RepoName, targetBranch)
266
289
if err != nil {
267
290
log.Println("failed to check mergeability", err)
268
-
s.pages.Notice(w, "pull", "Unable to check for mergeability. Try again later.")
291
+
s.pages.Notice(w, "pull-success", "Patch updated successfully, but couldn't check mergeability.")
269
292
return
270
293
}
271
294
272
295
respBody, err := io.ReadAll(resp.Body)
273
296
if err != nil {
274
297
log.Println("failed to read knotserver response body")
275
-
s.pages.Notice(w, "pull", "Unable to check for mergeability. Try again later.")
298
+
s.pages.Notice(w, "pull-success", "Patch updated successfully, but couldn't check mergeability.")
276
299
return
277
300
}
278
301
···
280
303
err = json.Unmarshal(respBody, &mergeCheckResponse)
281
304
if err != nil {
282
305
log.Println("failed to unmarshal merge check response", err)
283
-
s.pages.Notice(w, "pull", "Failed to check mergeability. Try again later.")
306
+
s.pages.Notice(w, "pull-success", "Patch updated successfully, but couldn't check mergeability.")
284
307
return
285
308
}
286
309
287
-
// TODO: this has to return a html fragment
288
-
w.Header().Set("Content-Type", "application/json")
289
-
json.NewEncoder(w).Encode(mergeCheckResponse)
310
+
s.pages.HxLocation(w, fmt.Sprintf("/@%s/%s/pulls/%d", f.OwnerHandle(), f.RepoName, prIdInt))
311
+
return
290
312
}
291
313
292
314
func (s *State) NewPull(w http.ResponseWriter, r *http.Request) {
+1
appview/state/router.go
+1
appview/state/router.go
+4
-4
input.css
+4
-4
input.css
···
105
105
106
106
@layer base {
107
107
html {
108
-
letter-spacing: -0.01em;
109
-
word-spacing: -0.07em;
110
-
font-size: 14px;
108
+
letter-spacing: -0.01em;
109
+
word-spacing: -0.07em;
110
+
font-size: 14px;
111
111
}
112
112
a {
113
113
@apply no-underline text-black hover:underline hover:text-gray-800;
···
147
147
@apply py-1 text-red-400;
148
148
}
149
149
.success {
150
-
@apply py-1 text-black;
150
+
@apply py-1 text-green-400;
151
151
}
152
152
}
153
153
}