appview: support star for `sh.tangled.string`

Not sending "XXX starred your string" notif yet.

Close: <https://tangled.org/tangled.org/core/issues/296>
Signed-off-by: Seongmin Lee <git@boltless.me>

authored by boltless.me and committed by Tangled c6a41ebf fe343b6c

Changed files
+49 -31
appview
models
pages
templates
fragments
layouts
strings
timeline
fragments
user
fragments
state
strings
+1 -1
appview/models/string.go
··· 22 22 Edited *time.Time 23 23 } 24 24 25 - func (s *String) StringAt() syntax.ATURI { 25 + func (s *String) AtUri() syntax.ATURI { 26 26 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", s.Did, tangled.StringNSID, s.Rkey)) 27 27 } 28 28
+8 -6
appview/pages/pages.go
··· 625 625 return p.executePlain("user/fragments/editPins", w, params) 626 626 } 627 627 628 - type RepoStarFragmentParams struct { 628 + type StarBtnFragmentParams struct { 629 629 IsStarred bool 630 - RepoAt syntax.ATURI 631 - Stats models.RepoStats 630 + SubjectAt syntax.ATURI 631 + StarCount int 632 632 } 633 633 634 - func (p *Pages) RepoStarFragment(w io.Writer, params RepoStarFragmentParams) error { 635 - return p.executePlain("repo/fragments/repoStar", w, params) 634 + func (p *Pages) StarBtnFragment(w io.Writer, params StarBtnFragmentParams) error { 635 + return p.executePlain("fragments/starBtn", w, params) 636 636 } 637 637 638 638 type RepoIndexParams struct { ··· 1376 1376 ShowRendered bool 1377 1377 RenderToggle bool 1378 1378 RenderedContents template.HTML 1379 - String models.String 1379 + String *models.String 1380 1380 Stats models.StringStats 1381 + IsStarred bool 1382 + StarCount int 1381 1383 Owner identity.Identity 1382 1384 } 1383 1385
+4 -1
appview/pages/templates/layouts/repobase.html
··· 49 49 </div> 50 50 51 51 <div class="w-full sm:w-fit grid grid-cols-3 gap-2 z-auto"> 52 - {{ template "repo/fragments/repoStar" .RepoInfo }} 52 + {{ template "fragments/starBtn" 53 + (dict "SubjectAt" .RepoInfo.RepoAt 54 + "IsStarred" .RepoInfo.IsStarred 55 + "StarCount" .RepoInfo.Stats.StarCount) }} 53 56 <a 54 57 class="btn text-sm no-underline hover:no-underline flex items-center gap-2 group" 55 58 hx-boost="true"
+4 -4
appview/pages/templates/repo/fragments/repoStar.html appview/pages/templates/fragments/starBtn.html
··· 1 - {{ define "repo/fragments/repoStar" }} 1 + {{ define "fragments/starBtn" }} 2 2 <button 3 3 id="starBtn" 4 4 class="btn disabled:opacity-50 disabled:cursor-not-allowed flex gap-2 items-center group" 5 5 {{ if .IsStarred }} 6 - hx-delete="/star?subject={{ .RepoAt }}&countHint={{ .Stats.StarCount }}" 6 + hx-delete="/star?subject={{ .SubjectAt }}&countHint={{ .StarCount }}" 7 7 {{ else }} 8 - hx-post="/star?subject={{ .RepoAt }}&countHint={{ .Stats.StarCount }}" 8 + hx-post="/star?subject={{ .SubjectAt }}&countHint={{ .StarCount }}" 9 9 {{ end }} 10 10 11 11 hx-trigger="click" ··· 19 19 {{ i "star" "w-4 h-4" }} 20 20 {{ end }} 21 21 <span class="text-sm"> 22 - {{ .Stats.StarCount }} 22 + {{ .StarCount }} 23 23 </span> 24 24 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 25 25 </button>
+8 -4
appview/pages/templates/strings/string.html
··· 17 17 <span class="select-none">/</span> 18 18 <a href="/strings/{{ $ownerId }}/{{ .String.Rkey }}" class="font-bold">{{ .String.Filename }}</a> 19 19 </div> 20 - {{ if and .LoggedInUser (eq .LoggedInUser.Did .String.Did) }} 21 - <div class="flex gap-2 text-base"> 20 + <div class="flex gap-2 text-base"> 21 + {{ if and .LoggedInUser (eq .LoggedInUser.Did .String.Did) }} 22 22 <a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group" 23 23 hx-boost="true" 24 24 href="/strings/{{ .String.Did }}/{{ .String.Rkey }}/edit"> ··· 37 37 <span class="hidden md:inline">delete</span> 38 38 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 39 39 </button> 40 - </div> 41 - {{ end }} 40 + {{ end }} 41 + {{ template "fragments/starBtn" 42 + (dict "SubjectAt" .String.AtUri 43 + "IsStarred" .IsStarred 44 + "StarCount" .StarCount) }} 45 + </div> 42 46 </div> 43 47 <span> 44 48 {{ with .String.Description }}
+2 -2
appview/pages/templates/timeline/fragments/timeline.html
··· 52 52 <span class="text-gray-700 dark:text-gray-400 text-xs">{{ template "repo/fragments/time" $repo.Created }}</span> 53 53 </div> 54 54 {{ with $repo }} 55 - {{ template "user/fragments/repoCard" (list $root . true true (dict "IsStarred" $event.IsStarred "RepoAt" .RepoAt "Stats" (dict "StarCount" $event.StarCount))) }} 55 + {{ template "user/fragments/repoCard" (list $root . true true (dict "IsStarred" $event.IsStarred "SubjectAt" .RepoAt "StarCount" $event.StarCount)) }} 56 56 {{ end }} 57 57 {{ end }} 58 58 ··· 72 72 <span class="text-gray-700 dark:text-gray-400 text-xs">{{ template "repo/fragments/time" .Created }}</span> 73 73 </div> 74 74 {{ with .Repo }} 75 - {{ template "user/fragments/repoCard" (list $root . true true (dict "IsStarred" $event.IsStarred "RepoAt" .RepoAt "Stats" (dict "StarCount" $event.StarCount))) }} 75 + {{ template "user/fragments/repoCard" (list $root . true true (dict "IsStarred" $event.IsStarred "SubjectAt" .RepoAt "StarCount" $event.StarCount)) }} 76 76 {{ end }} 77 77 {{ end }} 78 78 {{ end }}
+2 -1
appview/pages/templates/user/fragments/repoCard.html
··· 1 1 {{ define "user/fragments/repoCard" }} 2 + {{/* root, repo, fullName [,starButton [,starData]] */}} 2 3 {{ $root := index . 0 }} 3 4 {{ $repo := index . 1 }} 4 5 {{ $fullName := index . 2 }} ··· 29 30 </div> 30 31 {{ if and $starButton $root.LoggedInUser }} 31 32 <div class="shrink-0"> 32 - {{ template "repo/fragments/repoStar" $starData }} 33 + {{ template "fragments/starBtn" $starData }} 33 34 </div> 34 35 {{ end }} 35 36 </div>
+6 -10
appview/state/star.go
··· 75 75 76 76 s.notifier.NewStar(r.Context(), star) 77 77 78 - s.pages.RepoStarFragment(w, pages.RepoStarFragmentParams{ 78 + s.pages.StarBtnFragment(w, pages.StarBtnFragmentParams{ 79 79 IsStarred: true, 80 - RepoAt: subjectUri, 81 - Stats: models.RepoStats{ 82 - StarCount: starCount, 83 - }, 80 + SubjectAt: subjectUri, 81 + StarCount: starCount, 84 82 }) 85 83 86 84 return ··· 117 115 118 116 s.notifier.DeleteStar(r.Context(), star) 119 117 120 - s.pages.RepoStarFragment(w, pages.RepoStarFragmentParams{ 118 + s.pages.StarBtnFragment(w, pages.StarBtnFragmentParams{ 121 119 IsStarred: false, 122 - RepoAt: subjectUri, 123 - Stats: models.RepoStats{ 124 - StarCount: starCount, 125 - }, 120 + SubjectAt: subjectUri, 121 + StarCount: starCount, 126 122 }) 127 123 128 124 return
+14 -2
appview/strings/strings.go
··· 148 148 showRendered = r.URL.Query().Get("code") != "true" 149 149 } 150 150 151 + starCount, err := db.GetStarCount(s.Db, string.AtUri()) 152 + if err != nil { 153 + l.Error("failed to get star count", "err", err) 154 + } 155 + user := s.OAuth.GetUser(r) 156 + isStarred := false 157 + if user != nil { 158 + isStarred = db.GetStarStatus(s.Db, user.Did, string.AtUri()) 159 + } 160 + 151 161 s.Pages.SingleString(w, pages.SingleStringParams{ 152 - LoggedInUser: s.OAuth.GetUser(r), 162 + LoggedInUser: user, 153 163 RenderToggle: renderToggle, 154 164 ShowRendered: showRendered, 155 - String: string, 165 + String: &string, 156 166 Stats: string.Stats(), 167 + IsStarred: isStarred, 168 + StarCount: starCount, 157 169 Owner: id, 158 170 }) 159 171 }