forked from tangled.org/core
Monorepo for Tangled

improve issue styling, add comment counts

Changed files
+152 -86
appview
+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 {{ 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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 },