+2
appview/pages/pages.go
+2
appview/pages/pages.go
+124
-67
appview/pages/templates/repo/index.html
+124
-67
appview/pages/templates/repo/index.html
···
1
-
{{define "repoContent"}}
1
+
{{ define "repoContent" }}
2
+
<main>
3
+
{{- if .IsEmpty }}
4
+
this repo is empty
5
+
{{ else }}
6
+
<div class="flex gap-4">
7
+
<div id="file-tree" class="w-1/2">
8
+
{{ $containerstyle := "py-1" }}
9
+
{{ $linkstyle := "no-underline hover:underline" }}
2
10
3
-
<main>
4
-
{{- if .IsEmpty }}
5
-
this repo is empty
6
-
{{ else }}
7
-
<div class="flex gap-4">
8
-
<div id="file-tree" class="w-1/2">
9
-
{{ $containerstyle := "py-1" }}
10
-
{{ $linkstyle := "no-underline hover:underline" }}
11
11
12
-
{{ range .Files }}
13
-
{{ if not .IsFile }}
14
-
<div class="{{ $containerstyle }}">
15
-
<a href="/{{ $.RepoInfo.FullName }}/tree/{{ $.Ref }}/{{ .Name }}" class="{{ $linkstyle }}">
16
-
<div class="flex items-center gap-2">
17
-
<i class="w-3 h-3 fill-current" data-lucide="folder"></i>{{ .Name }}/
18
-
</div>
19
-
</a>
20
-
</div>
21
-
{{ end }}
22
-
{{ end }}
12
+
<div class="flex justify-end">
13
+
<select
14
+
hx-get="/{{ .RepoInfo.FullName }}/tree/"
15
+
hx-on::config-request = "event.detail.path += this.value"
16
+
hx-trigger="change"
17
+
hx-target="#repo-content"
18
+
hx-select="#repo-content"
19
+
hx-push-url="true"
20
+
class="p-1 border border-gray-500 bg-white"
21
+
>
22
+
<optgroup label="branches" class="font-semibold">
23
+
{{ range .Branches }}
24
+
<option
25
+
value="{{ .Reference.Name }}"
26
+
class="py-1"
27
+
>
28
+
{{ .Reference.Name }}
29
+
</option>
30
+
{{ end }}
31
+
</optgroup>
32
+
<optgroup label="tags" class="font-semibold">
33
+
{{ range .Tags }}
34
+
<option
35
+
value="{{ .Reference.Name }}"
36
+
class="py-1"
37
+
>
38
+
{{ .Reference.Name }}
39
+
</option>
40
+
{{ end }}
41
+
</optgroup>
42
+
</select>
43
+
</div>
23
44
24
-
{{ range .Files }}
25
-
{{ if .IsFile }}
26
-
<div class="{{ $containerstyle }}">
27
-
<a href="/{{ $.RepoInfo.FullName }}/blob/{{ $.Ref }}/{{ .Name }}" class="{{ $linkstyle }}">
28
-
<div class="flex items-center gap-2">
29
-
<i class="w-3 h-3" data-lucide="file"></i>{{ .Name }}
30
-
</div>
31
-
</a>
32
-
</div>
33
-
{{ end }}
34
-
{{ end }}
35
-
</div>
36
-
<div id="commit-log" class="flex-1">
37
-
{{ range .Commits }}
38
-
<div class=
39
-
"relative
45
+
<section id="repo-content">
46
+
{{ range .Files }}
47
+
{{ if not .IsFile }}
48
+
<div class="{{ $containerstyle }}">
49
+
<a
50
+
href="/{{ $.RepoInfo.FullName }}/tree/{{ $.Ref }}/{{ .Name }}"
51
+
class="{{ $linkstyle }}"
52
+
>
53
+
<div class="flex items-center gap-2">
54
+
<i
55
+
class="w-3 h-3 fill-current"
56
+
data-lucide="folder"
57
+
></i
58
+
>{{ .Name }}/
59
+
</div>
60
+
</a>
61
+
</div>
62
+
{{ end }}
63
+
{{ end }}
64
+
65
+
{{ range .Files }}
66
+
{{ if .IsFile }}
67
+
<div class="{{ $containerstyle }}">
68
+
<a
69
+
href="/{{ $.RepoInfo.FullName }}/blob/{{ $.Ref }}/{{ .Name }}"
70
+
class="{{ $linkstyle }}"
71
+
>
72
+
<div class="flex items-center gap-2">
73
+
<i
74
+
class="w-3 h-3"
75
+
data-lucide="file"
76
+
></i
77
+
>{{ .Name }}
78
+
</div>
79
+
</a>
80
+
</div>
81
+
{{ end }}
82
+
{{ end }}
83
+
</div>
84
+
<div id="commit-log" class="flex-1">
85
+
{{ range .Commits }}
86
+
<div
87
+
class="relative
40
88
px-4
41
89
py-4
42
90
border-l
43
91
border-black
44
-
before:content-['']
45
-
before:absolute
92
+
before:content-['']
93
+
before:absolute
46
94
before:w-1
47
95
before:h-1
48
96
before:bg-black
···
64
112
</div>
65
113
</div>
66
114
67
-
<div class="text-xs text-gray-500">
68
-
<span class="font-mono">
69
-
<a href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}" class="text-gray-500 no-underline hover:underline">{{ slice .Hash.String 0 8 }}</a>
70
-
</span>
71
-
·
72
-
<span>
73
-
<a href="mailto:{{ .Author.Email }}" class="text-gray-500 no-underline hover:underline">{{ .Author.Name }}</a>
74
-
</span>
75
-
·
76
-
<span>{{ timeFmt .Author.When }}</span>
77
-
</div>
115
+
<div class="text-xs text-gray-500">
116
+
<span class="font-mono">
117
+
<a
118
+
href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}"
119
+
class="text-gray-500 no-underline hover:underline"
120
+
>{{ slice .Hash.String 0 8 }}</a
121
+
>
122
+
</span>
123
+
·
124
+
<span>
125
+
<a
126
+
href="mailto:{{ .Author.Email }}"
127
+
class="text-gray-500 no-underline hover:underline"
128
+
>{{ .Author.Name }}</a
129
+
>
130
+
</span>
131
+
·
132
+
<span>{{ timeFmt .Author.When }}</span>
133
+
</div>
134
+
</div>
135
+
{{ end }}
136
+
</div>
137
+
</div>
138
+
</section>
139
+
{{- if .Readme }}
140
+
<article class="readme">
141
+
{{- .Readme -}}
142
+
</article>
143
+
{{- end -}}
144
+
{{- end -}}
78
145
79
-
</div>
80
-
{{ end }}
81
-
</div>
82
-
</div>
83
-
{{- if .Readme }}
84
-
<article class="readme">
85
-
{{- .Readme -}}
86
-
</article>
87
-
{{- end -}}
88
-
{{- end -}}
89
146
90
-
<div class="clone-url">
91
-
<strong>clone</strong>
92
-
<pre>
147
+
<div class="clone-url">
148
+
<strong>clone</strong>
149
+
<pre>
93
150
git clone https://tangled.sh/{{ .RepoInfo.OwnerWithAt }}/{{ .RepoInfo.Name }}
94
-
</pre>
95
-
</div>
96
-
</main>
97
-
{{end}}
98
-
151
+
</pre
152
+
>
153
+
</div>
154
+
</main>
155
+
{{ end }}
+50
-1
appview/state/repo.go
+50
-1
appview/state/repo.go
···
18
18
)
19
19
20
20
func (s *State) RepoIndex(w http.ResponseWriter, r *http.Request) {
21
+
ref := chi.URLParam(r, "ref")
21
22
f, err := fullyResolvedRepo(r)
22
23
if err != nil {
23
24
log.Println("failed to fully resolve repo", err)
24
25
return
25
26
}
27
+
var reqUrl string
28
+
if ref != "" {
29
+
reqUrl = fmt.Sprintf("http://%s/%s/%s/tree/%s", f.Knot, f.OwnerDid(), f.RepoName, ref)
30
+
} else {
31
+
reqUrl = fmt.Sprintf("http://%s/%s/%s", f.Knot, f.OwnerDid(), f.RepoName)
32
+
}
26
33
27
-
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s", f.Knot, f.OwnerDid(), f.RepoName))
34
+
resp, err := http.Get(reqUrl)
28
35
if err != nil {
29
36
s.pages.Error503(w)
30
37
log.Println("failed to reach knotserver", err)
···
45
52
return
46
53
}
47
54
55
+
branchesResp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/branches", f.Knot, f.OwnerDid(), f.RepoName))
56
+
if err != nil {
57
+
log.Println("failed to reach knotserver for branches", err)
58
+
return
59
+
}
60
+
defer branchesResp.Body.Close()
61
+
62
+
tagsResp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tags", f.Knot, f.OwnerDid(), f.RepoName))
63
+
if err != nil {
64
+
log.Println("failed to reach knotserver for tags", err)
65
+
return
66
+
}
67
+
defer tagsResp.Body.Close()
68
+
69
+
branchesBody, err := io.ReadAll(branchesResp.Body)
70
+
if err != nil {
71
+
log.Println("failed to read branches response", err)
72
+
return
73
+
}
74
+
75
+
tagsBody, err := io.ReadAll(tagsResp.Body)
76
+
if err != nil {
77
+
log.Println("failed to read tags response", err)
78
+
return
79
+
}
80
+
81
+
var branchesResult types.RepoBranchesResponse
82
+
err = json.Unmarshal(branchesBody, &branchesResult)
83
+
if err != nil {
84
+
log.Println("failed to parse branches response", err)
85
+
return
86
+
}
87
+
88
+
var tagsResult types.RepoTagsResponse
89
+
err = json.Unmarshal(tagsBody, &tagsResult)
90
+
if err != nil {
91
+
log.Println("failed to parse tags response", err)
92
+
return
93
+
}
94
+
48
95
log.Println(resp.Status, result)
49
96
50
97
user := s.auth.GetUser(r)
···
57
104
SettingsAllowed: settingsAllowed(s, user, f),
58
105
},
59
106
RepoIndexResponse: result,
107
+
Branches: branchesResult.Branches,
108
+
Tags: tagsResult.Tags,
60
109
})
61
110
62
111
return
+1
appview/state/state.go
+1
appview/state/state.go
+122
-97
flake.nix
+122
-97
flake.nix
···
12
12
flake = false;
13
13
};
14
14
lucide-src = {
15
-
url = "https://unpkg.com/lucide@latest";
16
-
flake = false;
15
+
url = "https://unpkg.com/lucide@latest";
16
+
flake = false;
17
17
};
18
18
gitignore = {
19
19
url = "github:hercules-ci/gitignore.nix";
···
21
21
};
22
22
};
23
23
24
-
outputs = {
25
-
self,
26
-
nixpkgs,
27
-
indigo,
28
-
htmx-src,
29
-
lucide-src,
30
-
gitignore,
31
-
}: let
32
-
supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin"];
33
-
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
34
-
nixpkgsFor = forAllSystems (system:
35
-
import nixpkgs {
36
-
inherit system;
37
-
overlays = [self.overlays.default];
38
-
});
39
-
inherit (gitignore.lib) gitignoreSource;
40
-
in {
41
-
overlays.default = final: prev: {
42
-
indigo-lexgen = with final;
43
-
final.buildGoModule {
44
-
pname = "indigo-lexgen";
45
-
version = "0.1.0";
46
-
src = indigo;
47
-
subPackages = ["cmd/lexgen"];
48
-
vendorHash = "sha256-pGc29fgJFq8LP7n/pY1cv6ExZl88PAeFqIbFEhB3xXs=";
49
-
doCheck = false;
50
-
};
24
+
outputs =
25
+
{ self
26
+
, nixpkgs
27
+
, indigo
28
+
, htmx-src
29
+
, lucide-src
30
+
, gitignore
31
+
,
32
+
}:
33
+
let
34
+
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
35
+
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
36
+
nixpkgsFor = forAllSystems (system:
37
+
import nixpkgs {
38
+
inherit system;
39
+
overlays = [ self.overlays.default ];
40
+
});
41
+
inherit (gitignore.lib) gitignoreSource;
42
+
in
43
+
{
44
+
overlays.default = final: prev: {
45
+
indigo-lexgen = with final;
46
+
final.buildGoModule {
47
+
pname = "indigo-lexgen";
48
+
version = "0.1.0";
49
+
src = indigo;
50
+
subPackages = [ "cmd/lexgen" ];
51
+
vendorHash = "sha256-pGc29fgJFq8LP7n/pY1cv6ExZl88PAeFqIbFEhB3xXs=";
52
+
doCheck = false;
53
+
};
51
54
52
-
appview = with final;
53
-
final.pkgsStatic.buildGoModule {
54
-
pname = "appview";
55
-
version = "0.1.0";
56
-
src = gitignoreSource ./.;
57
-
postConfigureHook = ''
58
-
cp -f ${htmx-src} appview/pages/static/htmx.min.js
59
-
cp -f ${lucide-src} appview/pages/static/lucide.min.js
60
-
${pkgs.tailwindcss}/bin/tailwindcss -i input.css -o appview/pages/static/tw.css
61
-
'';
62
-
subPackages = ["cmd/appview"];
63
-
vendorHash = "sha256-t7lWrCyFWCI7zjUcC6XNWzCrUPSCFnFu9gTmRTsYrz0=";
64
-
env.CGO_ENABLED = 1;
65
-
stdenv = pkgsStatic.stdenv;
66
-
};
67
-
knotserver = with final;
55
+
appview = with final;
56
+
final.pkgsStatic.buildGoModule {
57
+
pname = "appview";
58
+
version = "0.1.0";
59
+
src = gitignoreSource ./.;
60
+
postConfigureHook = ''
61
+
cp -f ${htmx-src} appview/pages/static/htmx.min.js
62
+
cp -f ${lucide-src} appview/pages/static/lucide.min.js
63
+
${pkgs.tailwindcss}/bin/tailwindcss -i input.css -o appview/pages/static/tw.css
64
+
'';
65
+
subPackages = [ "cmd/appview" ];
66
+
vendorHash = "sha256-t7lWrCyFWCI7zjUcC6XNWzCrUPSCFnFu9gTmRTsYrz0=";
67
+
env.CGO_ENABLED = 1;
68
+
stdenv = pkgsStatic.stdenv;
69
+
};
70
+
knotserver = with final;
71
+
final.pkgsStatic.buildGoModule {
72
+
pname = "knotserver";
73
+
version = "0.1.0";
74
+
src = gitignoreSource ./.;
75
+
subPackages = [ "cmd/knotserver" ];
76
+
vendorHash = "sha256-t7lWrCyFWCI7zjUcC6XNWzCrUPSCFnFu9gTmRTsYrz0=";
77
+
env.CGO_ENABLED = 1;
78
+
};
79
+
repoguard = with final;
80
+
final.pkgsStatic.buildGoModule {
81
+
pname = "repoguard";
82
+
version = "0.1.0";
83
+
src = gitignoreSource ./.;
84
+
subPackages = [ "cmd/repoguard" ];
85
+
vendorHash = "sha256-t7lWrCyFWCI7zjUcC6XNWzCrUPSCFnFu9gTmRTsYrz0=";
86
+
env.CGO_ENABLED = 0;
87
+
};
88
+
keyfetch = with final;
68
89
final.pkgsStatic.buildGoModule {
69
-
pname = "knotserver";
70
-
version = "0.1.0";
71
-
src = gitignoreSource ./.;
72
-
subPackages = ["cmd/knotserver"];
73
-
vendorHash = "sha256-t7lWrCyFWCI7zjUcC6XNWzCrUPSCFnFu9gTmRTsYrz0=";
74
-
env.CGO_ENABLED = 1;
75
-
};
90
+
pname = "keyfetch";
91
+
version = "0.1.0";
92
+
src = gitignoreSource ./.;
93
+
subPackages = [ "cmd/keyfetch" ];
94
+
vendorHash = "sha256-t7lWrCyFWCI7zjUcC6XNWzCrUPSCFnFu9gTmRTsYrz0=";
95
+
env.CGO_ENABLED = 0;
96
+
};
97
+
};
98
+
packages = forAllSystems (system: {
99
+
inherit (nixpkgsFor."${system}") indigo-lexgen appview knotserver repoguard keyfetch;
100
+
});
101
+
defaultPackage = forAllSystems (system: nixpkgsFor.${system}.appview);
102
+
formatter = forAllSystems (system: nixpkgsFor."${system}".alejandra);
103
+
devShells = forAllSystems (system:
104
+
let
105
+
pkgs = nixpkgsFor.${system};
106
+
staticShell = pkgs.mkShell.override {
107
+
stdenv = pkgs.pkgsStatic.stdenv;
108
+
};
109
+
in
110
+
{
111
+
default = staticShell {
112
+
nativeBuildInputs = [
113
+
pkgs.go
114
+
pkgs.air
115
+
pkgs.gopls
116
+
pkgs.httpie
117
+
pkgs.indigo-lexgen
118
+
pkgs.litecli
119
+
pkgs.websocat
120
+
pkgs.tailwindcss
121
+
];
122
+
};
123
+
});
124
+
apps = forAllSystems (system:
125
+
let
126
+
pkgs = nixpkgsFor."${system}";
127
+
air-watcher = name:
128
+
pkgs.writeShellScriptBin "run"
129
+
''
130
+
${pkgs.air}/bin/air -c /dev/null \
131
+
-build.cmd "${pkgs.tailwindcss}/bin/tailwindcss -i input.css -o ./appview/pages/static/tw.css && ${pkgs.go}/bin/go build -o ./out/${name}.out ./cmd/${name}/main.go" \
132
+
-build.bin "./out/${name}.out" \
133
+
-build.include_ext "go,html,css"
134
+
'';
135
+
in
136
+
{
137
+
watch-appview = {
138
+
type = "app";
139
+
program = ''${air-watcher "appview"}/bin/run'';
140
+
};
141
+
watch-knotserver = {
142
+
type = "app";
143
+
program = ''${air-watcher "knotserver"}/bin/run'';
144
+
};
145
+
});
76
146
};
77
-
packages = forAllSystems (system: {
78
-
inherit (nixpkgsFor."${system}") indigo-lexgen appview knotserver;
79
-
});
80
-
defaultPackage = forAllSystems (system: nixpkgsFor.${system}.appview);
81
-
formatter = forAllSystems (system: nixpkgsFor."${system}".alejandra);
82
-
devShells = forAllSystems (system: let
83
-
pkgs = nixpkgsFor.${system};
84
-
staticShell = pkgs.mkShell.override {
85
-
stdenv = pkgs.pkgsStatic.stdenv;
86
-
};
87
-
in {
88
-
default = staticShell {
89
-
nativeBuildInputs = [
90
-
pkgs.go
91
-
pkgs.air
92
-
pkgs.gopls
93
-
pkgs.httpie
94
-
pkgs.indigo-lexgen
95
-
pkgs.litecli
96
-
pkgs.websocat
97
-
pkgs.tailwindcss
98
-
];
99
-
};
100
-
});
101
-
apps = forAllSystems (system: let
102
-
pkgs = nixpkgsFor."${system}";
103
-
air-watcher = name:
104
-
pkgs.writeShellScriptBin "run"
105
-
''
106
-
${pkgs.air}/bin/air -c /dev/null \
107
-
-build.cmd "${pkgs.tailwindcss}/bin/tailwindcss -i input.css -o ./appview/pages/static/tw.css && ${pkgs.go}/bin/go build -o ./out/${name}.out ./cmd/${name}/main.go" \
108
-
-build.bin "./out/${name}.out" \
109
-
-build.include_ext "go,html,css"
110
-
'';
111
-
in {
112
-
watch-appview = {
113
-
type = "app";
114
-
program = ''${air-watcher "appview"}/bin/run'';
115
-
};
116
-
watch-knotserver = {
117
-
type = "app";
118
-
program = ''${air-watcher "knotserver"}/bin/run'';
119
-
};
120
-
});
121
-
};
122
147
}
+5
-4
knotserver/routes.go
+5
-4
knotserver/routes.go
···
342
342
rtags := []*types.TagReference{}
343
343
for _, tag := range tags {
344
344
tr := types.TagReference{
345
-
Ref: types.Reference{
346
-
Name: tag.Name(),
347
-
Hash: tag.Hash().String(),
348
-
},
349
345
Tag: tag.TagObject(),
346
+
}
347
+
348
+
tr.Reference = types.Reference{
349
+
Name: tag.Name(),
350
+
Hash: tag.Hash().String(),
350
351
}
351
352
352
353
if tag.Message() != "" {
+3
-3
types/repo.go
+3
-3
types/repo.go
···
39
39
}
40
40
41
41
type TagReference struct {
42
-
Ref Reference `json:"ref,omitempty"`
43
-
Tag *object.Tag `json:"tag,omitempty"`
44
-
Message string `json:"message,omitempty"`
42
+
Reference `json:"ref,omitempty"`
43
+
Tag *object.Tag `json:"tag,omitempty"`
44
+
Message string `json:"message,omitempty"`
45
45
}
46
46
47
47
type Reference struct {