+29
-2
appview/db/issues.go
+29
-2
appview/db/issues.go
···
16
16
Title string
17
17
Body string
18
18
Open bool
19
+
Metadata *IssueMetadata
20
+
}
21
+
22
+
type IssueMetadata struct {
23
+
CommentCount int
24
+
// labels, assignee etc.
19
25
}
20
26
21
27
type Comment struct {
···
93
99
func GetIssues(e Execer, repoAt syntax.ATURI) ([]Issue, error) {
94
100
var issues []Issue
95
101
96
-
rows, err := e.Query(`select owner_did, issue_id, created, title, body, open from issues where repo_at = ? order by created desc`, repoAt)
102
+
rows, err := e.Query(
103
+
`select
104
+
i.owner_did,
105
+
i.issue_id,
106
+
i.created,
107
+
i.title,
108
+
i.body,
109
+
i.open,
110
+
count(c.id)
111
+
from
112
+
issues i
113
+
left join
114
+
comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id
115
+
where
116
+
i.repo_at = ?
117
+
group by
118
+
i.id, i.owner_did, i.issue_id, i.created, i.title, i.body, i.open
119
+
order by
120
+
i.created desc`,
121
+
repoAt)
97
122
if err != nil {
98
123
return nil, err
99
124
}
···
102
127
for rows.Next() {
103
128
var issue Issue
104
129
var createdAt string
105
-
err := rows.Scan(&issue.OwnerDid, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open)
130
+
var metadata IssueMetadata
131
+
err := rows.Scan(&issue.OwnerDid, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open, &metadata.CommentCount)
106
132
if err != nil {
107
133
return nil, err
108
134
}
···
112
138
return nil, err
113
139
}
114
140
issue.Created = &createdTime
141
+
issue.Metadata = &metadata
115
142
116
143
issues = append(issues, issue)
117
144
}
+1
-1
appview/pages/templates/fragments/star.html
+1
-1
appview/pages/templates/fragments/star.html
···
1
1
{{ define "fragments/star" }}
2
2
<button id="starBtn"
3
-
class="btn text-sm disabled:opacity-50 disabled:cursor-not-allowed"
3
+
class="text-sm disabled:opacity-50 disabled:cursor-not-allowed"
4
4
5
5
{{ if .IsStarred }}
6
6
hx-delete="/star?subject={{.RepoAt}}&countHint={{.Stats.StarCount}}"
+11
-11
appview/pages/templates/layouts/repobase.html
+11
-11
appview/pages/templates/layouts/repobase.html
···
2
2
3
3
{{ define "content" }}
4
4
<section id="repo-header" class="mb-4 py-2 px-6">
5
-
<div class="flex gap-3">
6
-
<p class="text-lg">
7
-
<a href="/{{ .RepoInfo.OwnerWithAt }}">{{ .RepoInfo.OwnerWithAt }}</a>
8
-
<span class="select-none">/</span>
9
-
<a href="/{{ .RepoInfo.FullName }}" class="font-bold">{{ .RepoInfo.Name }}</a>
10
-
</p>
11
-
{{ template "fragments/star" .RepoInfo }}
12
-
</div>
5
+
<p class="text-lg">
6
+
<a href="/{{ .RepoInfo.OwnerWithAt }}">{{ .RepoInfo.OwnerWithAt }}</a>
7
+
<span class="select-none">/</span>
8
+
<a href="/{{ .RepoInfo.FullName }}" class="font-bold">{{ .RepoInfo.Name }}</a>
9
+
<span class="ml-3">
10
+
{{ template "fragments/star" .RepoInfo }}
11
+
</span>
12
+
</p>
13
13
{{ template "fragments/repoDescription" . }}
14
14
</section>
15
-
<section id="repo-links" class="min-h-screen flex flex-col drop-shadow-sm">
15
+
<section class="min-h-screen flex flex-col drop-shadow-sm">
16
16
<nav class="w-full mx-auto ml-4">
17
-
<div class="flex z-60">
17
+
<div class="flex z-60 overflow-auto">
18
18
{{ $activeTabStyles := "-mb-px bg-white" }}
19
19
{{ $tabs := .RepoInfo.GetTabs }}
20
20
{{ $tabmeta := .RepoInfo.TabMetadata }}
···
28
28
hx-boost="true"
29
29
>
30
30
<div
31
-
class="px-4 py-1 mr-1 text-black min-w-[80px] text-center relative rounded-t
31
+
class="px-4 py-1 mr-1 text-black min-w-[80px] text-center relative rounded-t whitespace-nowrap
32
32
{{ if eq $.Active $key }}
33
33
{{ $activeTabStyles }}
34
34
{{ else }}
+3
-1
appview/pages/templates/repo/index.html
+3
-1
appview/pages/templates/repo/index.html
···
175
175
></div>
176
176
<span>{{ timeFmt .Author.When }}</span>
177
177
{{ $tagsForCommit := index $.TagMap .Hash.String }}
178
-
{{ range $tagsForCommit }}
178
+
{{ if gt (len $tagsForCommit) 0 }}
179
179
<div
180
180
class="inline-block px-1 select-none after:content-['·']"
181
181
></div>
182
+
{{ end }}
183
+
{{ range $tagsForCommit }}
182
184
<span class="text-xs rounded bg-gray-100 font-mono px-2 mx-1/2 inline-flex items-center">
183
185
{{ . }}
184
186
</span>
+50
-36
appview/pages/templates/repo/issues/issues.html
+50
-36
appview/pages/templates/repo/issues/issues.html
···
11
11
<span>new issue</span>
12
12
</a>
13
13
</div>
14
+
{{ end }}
14
15
15
-
<section id="issues" class="mt-8 space-y-4">
16
-
{{ range .Issues }}
17
-
<div class="border border-gray-200 p-4 mx-4 hover:bg-gray-50">
18
-
<time class="float-right text-sm">
19
-
{{ .Created | timeFmt }}
20
-
</time>
21
-
<div class="flex items-center gap-2 py-2">
22
-
{{ if .Open }}
23
-
<i
24
-
data-lucide="circle-dot"
25
-
class="w-4 h-4 text-green-600"
26
-
></i>
27
-
{{ else }}
28
-
<i data-lucide="ban" class="w-4 h-4 text-red-600"></i>
29
-
{{ end }}
30
-
<a
31
-
href="/{{ $.RepoInfo.FullName }}/issues/{{ .IssueId }}"
32
-
class="no-underline hover:underline"
33
-
>
34
-
{{ .Title }}
35
-
</a>
36
-
</div>
37
-
<div class="text-sm flex gap-2 text-gray-400">
38
-
<span>#{{ .IssueId }}</span>
39
-
<span class="before:content-['·']">
40
-
opened by
41
-
{{ $owner := index $.DidHandleMap .OwnerDid }}
42
-
<a
43
-
href="/{{ $owner }}"
44
-
class="no-underline hover:underline"
45
-
>{{ $owner }}</a
46
-
>
47
-
</span>
48
-
</div>
49
-
</div>
16
+
{{ define "repoAfter" }}
17
+
<div class="flex flex-col gap-2 mt-8">
18
+
{{ range .Issues }}
19
+
<div class="rounded drop-shadow-sm bg-white px-6 py-4">
20
+
<div class="pb-2">
21
+
<a
22
+
href="/{{ $.RepoInfo.FullName }}/issues/{{ .IssueId }}"
23
+
class="no-underline hover:underline"
24
+
>
25
+
{{ .Title }}
26
+
<span class="text-gray-400">#{{ .IssueId }}</span>
27
+
</a>
28
+
</div>
29
+
<p class="text-sm text-gray-400">
30
+
{{ $bgColor := "bg-gray-800" }}
31
+
{{ $icon := "ban" }}
32
+
{{ $state := "closed" }}
33
+
{{ if .Open }}
34
+
{{ $bgColor = "bg-green-600" }}
35
+
{{ $icon = "circle-dot" }}
36
+
{{ $state = "open" }}
37
+
{{ end }}
38
+
39
+
<span class="inline-flex items-center rounded px-2 py-[5px] {{ $bgColor }} text-sm">
40
+
<i data-lucide="{{ $icon }}" class="w-3 h-3 mr-1.5 text-white"></i>
41
+
<span class="text-white">{{ $state }}</span>
42
+
</span>
43
+
44
+
<span>
45
+
{{ $owner := index $.DidHandleMap .OwnerDid }}
46
+
<a href="/{{ $owner }}">{{ $owner }}</a>
47
+
</span>
48
+
49
+
<span class="before:content-['·']">
50
+
<time>
51
+
{{ .Created | timeFmt }}
52
+
</time>
53
+
</span>
54
+
55
+
<span class="before:content-['·']">
56
+
{{ $s := "s" }}
57
+
{{ if eq .Metadata.CommentCount 1 }}
58
+
{{ $s = "" }}
50
59
{{ end }}
51
-
</section>
60
+
<a href="/{{ $.RepoInfo.FullName }}/issues/{{ .IssueId }}" class="text-gray-400">{{ .Metadata.CommentCount }} comment{{$s}}</a>
61
+
</span>
62
+
</p>
63
+
</div>
64
+
{{ end }}
65
+
</div>
52
66
{{ end }}
+1
-1
appview/pages/templates/repo/log.html
+1
-1
appview/pages/templates/repo/log.html
···
48
48
{{ range $commits }}
49
49
<div class="flex flex-row justify-between items-center">
50
50
<div
51
-
class="relative w-full px-4 py-4 mt-5 hover:bg-gray-50 rounded-sm bg-white"
51
+
class="relative w-full px-4 py-4 mt-4 rounded-sm bg-white"
52
52
>
53
53
<div id="commit-message">
54
54
{{ $messageParts := splitN .Message "\n\n" 2 }}
+2
-2
appview/pages/templates/settings.html
+2
-2
appview/pages/templates/settings.html
···
13
13
14
14
{{ define "profile" }}
15
15
<header class="text-sm font-bold py-2 px-6 uppercase">profile</header>
16
-
<section class="rounded bg-white drop-shadow-sm px-6 py-4 mb-6 w-fit">
16
+
<section class="rounded bg-white drop-shadow-sm px-6 py-4 mb-6 w-full lg:w-fit">
17
17
<dl class="grid grid-cols-[auto_1fr] gap-x-4">
18
18
{{ if .LoggedInUser.Handle }}
19
19
<dt class="font-bold">handle</dt>
···
29
29
30
30
{{ define "keys" }}
31
31
<header class="text-sm font-bold py-2 px-6 uppercase">ssh keys</header>
32
-
<section class="rounded bg-white drop-shadow-sm px-6 py-4 mb-6 w-fit">
32
+
<section class="rounded bg-white drop-shadow-sm px-6 py-4 mb-6 w-full lg:w-fit">
33
33
<div id="key-list" class="flex flex-col gap-6 mb-8">
34
34
{{ range .PubKeys }}
35
35
<div>
+3
-3
appview/pages/templates/timeline.html
+3
-3
appview/pages/templates/timeline.html
···
46
46
<a href="/{{ $userHandle }}" class="no-underline hover:underline">{{ $userHandle }}</a>
47
47
created
48
48
<a href="/{{ $userHandle }}/{{ .Repo.Name }}" class="no-underline hover:underline">{{ .Repo.Name }}</a>
49
-
<time class="text-gray-700">{{ .Repo.Created | timeFmt }}</time>
49
+
<time class="text-gray-700 text-xs">{{ .Repo.Created | timeFmt }}</time>
50
50
</p>
51
51
</div>
52
52
{{ else if .Follow }}
···
57
57
<a href="/{{ $userHandle }}" class="no-underline hover:underline">{{ $userHandle }}</a>
58
58
followed
59
59
<a href="/{{ $subjectHandle }}" class="no-underline hover:underline">{{ $subjectHandle }}</a>
60
-
<time class="text-gray-700">{{ .Follow.FollowedAt | timeFmt }}</time>
60
+
<time class="text-gray-700 text-xs">{{ .Follow.FollowedAt | timeFmt }}</time>
61
61
</p>
62
62
</div>
63
63
{{ else if .Star }}
···
68
68
<a href="/{{ $starrerHandle }}" class="no-underline hover:underline">{{ $starrerHandle }}</a>
69
69
starred
70
70
<a href="/{{ $repoOwnerHandle }}/{{ .Star.Repo.Name }}" class="no-underline hover:underline">{{ $repoOwnerHandle }}/{{ .Star.Repo.Name }}</a>
71
-
<time class="text-gray-700">{{ .Star.Created | timeFmt }}</time>
71
+
<time class="text-gray-700 text-xs">{{ .Star.Created | timeFmt }}</time>
72
72
</p>
73
73
</div>
74
74
{{ end }}
+4
-4
appview/pages/templates/user/profile.html
+4
-4
appview/pages/templates/user/profile.html
···
1
1
{{ define "title" }}{{ or .UserHandle .UserDid }}{{ end }}
2
2
3
3
{{ define "content" }}
4
-
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
5
-
<div class="lg:col-span-1">
4
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
5
+
<div class="md:col-span-1">
6
6
{{ block "profileCard" . }} {{ end }}
7
7
</div>
8
8
9
-
<div class="lg:col-span-3">
9
+
<div class="md:col-span-3">
10
10
{{ block "ownRepos" . }} {{ end }}
11
11
{{ block "collaboratingRepos" . }} {{ end }}
12
12
</div>
···
17
17
<div class="bg-white px-6 py-4 rounded drop-shadow-sm max-h-fit">
18
18
<div class="flex justify-center items-center">
19
19
{{ if .AvatarUri }}
20
-
<img class="w-1/2 lg:w-full rounded-full p-2" src="{{ .AvatarUri }}" />
20
+
<img class="w-1/2 rounded-full p-2" src="{{ .AvatarUri }}" />
21
21
{{ end }}
22
22
</div>
23
23
<p class="text-xl font-bold text-center">
+31
-14
flake.nix
+31
-14
flake.nix
···
92
92
pname = "knotserver";
93
93
version = "0.1.0";
94
94
src = gitignoreSource ./.;
95
-
nativeBuildInputs = [ final.makeWrapper ];
95
+
nativeBuildInputs = [final.makeWrapper];
96
96
subPackages = ["cmd/knotserver"];
97
97
vendorHash = goModHash;
98
98
installPhase = ''
99
-
runHook preInstall
99
+
runHook preInstall
100
100
101
-
mkdir -p $out/bin
102
-
cp $GOPATH/bin/knotserver $out/bin/knotserver
101
+
mkdir -p $out/bin
102
+
cp $GOPATH/bin/knotserver $out/bin/knotserver
103
103
104
-
wrapProgram $out/bin/knotserver \
105
-
--prefix PATH : ${pkgs.git}/bin
104
+
wrapProgram $out/bin/knotserver \
105
+
--prefix PATH : ${pkgs.git}/bin
106
106
107
-
runHook postInstall
107
+
runHook postInstall
108
108
'';
109
109
env.CGO_ENABLED = 1;
110
110
};
111
+
knotserver-unwrapped = with final;
112
+
final.pkgsStatic.buildGoModule {
113
+
pname = "knotserver";
114
+
version = "0.1.0";
115
+
src = gitignoreSource ./.;
116
+
subPackages = ["cmd/knotserver"];
117
+
vendorHash = goModHash;
118
+
env.CGO_ENABLED = 1;
119
+
};
111
120
repoguard = buildCmdPackage "repoguard";
112
121
keyfetch = buildCmdPackage "keyfetch";
113
122
};
114
123
packages = forAllSystems (system: {
115
-
inherit (nixpkgsFor."${system}") indigo-lexgen appview knotserver repoguard keyfetch;
124
+
inherit
125
+
(nixpkgsFor."${system}")
126
+
indigo-lexgen
127
+
appview
128
+
knotserver
129
+
knotserver-unwrapped
130
+
repoguard
131
+
keyfetch
132
+
;
116
133
});
117
134
defaultPackage = forAllSystems (system: nixpkgsFor.${system}.appview);
118
135
formatter = forAllSystems (system: nixpkgsFor."${system}".alejandra);
···
294
311
config = mkIf config.services.tangled-knotserver.enable {
295
312
nixpkgs.overlays = [self.overlays.default];
296
313
297
-
environment.systemPackages = with pkgs; [ git ];
314
+
environment.systemPackages = with pkgs; [git];
298
315
299
316
users.users.git = {
300
317
isNormalUser = true;
···
316
333
};
317
334
318
335
environment.etc."ssh/keyfetch_wrapper" = {
319
-
mode = "0555";
320
-
text = ''
321
-
#!${pkgs.stdenv.shell}
322
-
${pkgs.keyfetch}/bin/keyfetch -repoguard-path ${pkgs.repoguard}/bin/repoguard -log-path /tmp/repoguard.log
323
-
'';
336
+
mode = "0555";
337
+
text = ''
338
+
#!${pkgs.stdenv.shell}
339
+
${pkgs.keyfetch}/bin/keyfetch -repoguard-path ${pkgs.repoguard}/bin/repoguard -log-path /tmp/repoguard.log
340
+
'';
324
341
};
325
342
326
343
systemd.services.knotserver = {
+5
-5
input.css
+5
-5
input.css
···
109
109
@apply bg-opacity-30;
110
110
}
111
111
112
-
html {
113
-
letter-spacing: -0.01em;
114
-
word-spacing: -0.07em;
115
-
}
116
-
117
112
@layer base {
113
+
html {
114
+
letter-spacing: -0.01em;
115
+
word-spacing: -0.07em;
116
+
font-size: 14px;
117
+
}
118
118
a {
119
119
@apply no-underline text-black hover:underline hover:text-gray-800;
120
120
}
+12
-6
tailwind.config.js
+12
-6
tailwind.config.js
···
1
1
/** @type {import('tailwindcss').Config} */
2
+
const colors = require('tailwindcss/colors')
3
+
2
4
module.exports = {
3
5
content: ["./appview/pages/templates/**/*.html"],
4
6
theme: {
···
6
8
padding: "2rem",
7
9
center: true,
8
10
screens: {
9
-
sm: "540px",
10
-
md: "650px",
11
-
lg: "900px",
12
-
xl: "1100px",
13
-
"2xl": "1300px"
11
+
sm: "500px",
12
+
md: "600px",
13
+
lg: "800px",
14
+
xl: "1000px",
15
+
"2xl": "1200px"
14
16
},
15
17
},
16
18
extend: {
···
22
24
DEFAULT: {
23
25
css: {
24
26
maxWidth: 'none',
25
-
}
27
+
pre: {
28
+
backgroundColor: colors.gray[100],
29
+
color: colors.black,
30
+
},
31
+
},
26
32
},
27
33
},
28
34
},