tangled
alpha
login
or
join now
moshyfawn.dev
/
core
forked from
tangled.org/core
Monorepo for Tangled
0
fork
atom
overview
issues
1
pulls
pipelines
Compare changes
Choose any two refs to compare.
base:
txns
two-way-comms
tracing
three-easy-steps
test-pr-link
test-ci
tags-and-releases
summarize-rounds
stars
stacked-prs
spindle
sl/wnrvrwyvrlzo
sl/uvpzuszrulvq
sl/spindle-rewrite
sl/okmkyytolvko
sl/lwwtkzxrqksy
sl/kzmmroxoztll
sl/kuvknkxxvpvu
sl/git-objects
settings-router
session-refresh
revert-telemetry
repotab-icons
repo-deletion
render-markup
remove-set-repo-at
release-artifacts
refactor-ks-client
reduce-dids
push-zxovstvplnok
push-zwoymssunvsp
push-zvuqkxuqzmlx
push-zvtqtworuppk
push-zvkrywwskknq
push-zunxqvqoqmoq
push-zrvypwzlqtvz
push-zrkqwwrtxvzn
push-zqrmrxxvrylu
push-zqqvxroypxvy
push-zomkuzmzokvl
push-znrkktvllqsp
push-znlsowpxwvvq
push-zlzkyrrnylxr
push-zklmrltkvxkq
push-yytstxoqlwmx
push-yynslowxntql
push-ywynwzosnrox
push-yuwoytzsmkvm
push-yulzrzunkznl
push-ytzxyplmvunn
push-ytpxvpknlxun
push-ytkuzknmmrmn
push-yssxzpkyorwv
push-yqnqquktxqpx
push-ylvkxyvlmkyq
push-ykxtvrlpxwrl
push-ykwytywspowp
push-xwxnuorpzwsu
push-xwotmtuuvokm
push-xvuvxolnykpl
push-xvrvnuvsmslv
push-xvnwyvuwwylt
push-xtzkpqpkmvvr
push-xtuxqqlytkws
push-xttmuzqzktwv
push-xsmwulupylxx
push-xpolynpvotzt
push-xopyykmxuqzw
push-xmplzvpqtnru
push-xlupzwtslltp
push-wzlvlnywrxrs
push-wzkqymznotmx
push-wyonpyyzurwl
push-wykupplnxytq
push-wxwpnnytwxsq
push-wxvwuxxzswmv
push-wvywrqlppxqo
push-wvwxxlyxrxlm
push-wukyptkosoux
push-wtmrprmomuus
push-wsxnywmtsnrp
push-wqzwpukkvnuw
push-wpkykovtqxnx
push-wnwpznvpzwqu
push-wnotmtoqlnvl
push-wmkmzrsvlkmk
push-wkwyolovuxvq
push-vyyrpsmynrwo
push-vyusnwqnmxwy
push-vynsusnqpmus
push-vwyomovpppwp
push-vwuuzxwwsvow
push-vvluoqoywsvp
push-vuzywsvmkwqn
push-vtunvupqotuo
push-vssvkowylwvx
push-vquoltwpkuny
push-vnxxyxursomy
push-vmxtqnzznxvk
push-vkywmxzkrnqx
push-uylxxnsupvlk
push-uwuymmkuwsrs
push-uwotwontksrz
push-uuyqkqmkmlsn
push-uruyyqupsmro
push-upnknovvnoqr
push-uotxnqrqnlyl
push-ummlkpsrvrvn
push-ukznmmplmlul
push-ukmxnzrlrpuo
push-tzyltlmtsznq
push-tyxzqwosmzuv
push-twskywooslyl
push-twnpklmstzor
push-twlxnytrpyuk
push-tvvnxqyykkwr
push-tvtwutsrtoxt
push-tultwuoxrmun
push-ttyzwxqtkpsl
push-tnwwtqxpoqpk
push-tlxunysxvxwk
push-tkxmuvxkylmy
push-tkusxvwknltw
push-synlttxvkzox
push-svxqqzsxxkor
push-svtkmrzmrwky
push-stktrzvlltpw
push-sssuxsytslts
push-spvnpqlqqpkw
push-snyqozvrosmk
push-snktzuwttuvu
push-smsrusztuvwt
push-rvtqynpmozzy
push-rurrtsvmnvku
push-ruoqnsmttnxx
push-rtwrkkyllvkm
push-rtvxspsprvuv
push-rsrowlxunurk
push-rrsulrtnsmtw
push-rqzvvtnzkzvq
push-rqxyyurmpkps
push-rnztkppsvskw
push-rlorkkyzokzr
push-rlktrmqtttnn
push-qwnqkqnmovyn
push-qtrkvornpykm
push-qsmrouptzqkq
push-qruszvywputp
push-qrltzqmlrlln
push-qqwquvlysrxt
push-qqsspmnuxqwk
push-qqrowoolzppw
push-qoplqnlvlqqo
push-qnstkrpltlqu
push-qlzpkvltqlzm
push-qkpqsrknozxs
push-qkkmntrmxqly
push-pzwzzlwkkpnw
push-pzrysszkwwtk
push-pymywyqyumlo
push-pyllqzrprrny
push-pwqwlvnsqtqr
push-pvpnsqvvzuky
push-ptrrwwvnkmxq
push-psqwruuwvwlo
push-prszwlupsoqz
push-ppstsspzpxrx
push-potvrpwlpwsl
push-pnknzwntuuwx
push-pmtpswykplzr
push-plpuqtyrprou
push-plmznxvyqrqw
push-pkyzqzmqunnl
push-pkqzkqmxotyz
push-pkklqwlqwoml
push-owwtwvxyzowl
push-ovsuzpqkpmlt
push-ovrqrxnpvroz
push-ossmlnsnvstq
push-ormxrmmzomqu
push-oovokvlkywly
push-okymwlupkook
push-nwslswprzvmx
push-nwrnkztxkovl
push-nupxprsykpzq
push-nsolyvvsyvzw
push-nqrqutkzxskl
push-nozqtwvsrvkx
push-nnuvlyptxsqy
push-nmwononozzxz
push-nlvzxwtrmryl
push-nlnnyywnokyn
push-mzupsvxpvqvx
push-mzsvpkottnnt
push-myqmppunmplu
push-mwkwusmyymno
push-mvmrzuxwmzvs
push-mutuoxwuokqw
push-mtsxyxnkznyy
push-mtqkyzxrqnnn
push-msvlvnlwuyxy
push-mrwusmywonor
push-mqkxnymrqmzz
push-mozrrovxmlou
push-mopllyvuuuls
push-monplorvolvl
push-momltwttmuyq
push-mntstlmzqrvs
push-mmxkmyvyuzlk
push-mmrtnyoskwoo
push-mmnuoyuqplyk
push-mlxxvvqpzvlw
push-mllunlokmuxp
push-lzkzortpuyyq
push-lyrpkknpnrus
push-lxxtrqtnnoxy
push-lvuknrupsyox
push-lstutzylzylk
push-lrzzmtxokrxw
push-lnvnxmxlqkux
push-lnupvsypkuow
push-llqkvunvvzyv
push-ktsnmppqsnls
push-kstnoynmspqp
push-ksrsmmytwuul
push-kslplloylmlp
push-kpmvvnzwyosv
push-kltomzxpyoxq
pull-from-fork-pill
profile-timeline
profile-customizations
pr-actions
patch-requests
paginate-issues
packages-small
packages
oplog
opengraph
op/zxpquyouulmx
oauth
more-diff-stats
master
local-fragments
knot-xrpc
knot-local-clone
knot-cli
issues-edit
interdiff
improve-styling
improve-repo-name-checks
icy/tolqpt
group-profile-timeline
format-patch
fork-repo
fork-pulls
fix/follow-button-count-sync
fix/chip-inherit-styles
fix-tw-dark
fix-resubmit
fix-refresh-issues
fix-knot-forks
fix-jetstream-bugs
fix-env-test
file-tree
feat/pulls-pagination
feat/notifications-poll-interval
feat/fork-default-branch-only
feat/clickable-user-refs
feat/cleanup-notifications-on-undone
fast-dev
enable-html
drop-at
diff-improvements
did-email-assoc
crash-repro-knot
consolidate-syntax-styles
compare-merge-base
commits-page
commit-verification
ci-statuses
ci
camo
cache-busting
branch-prs-2
branch-prs
branch-fmt-patch
atprotate-pubkeys
all-repositories-page
add-migrations
v1.11.0-alpha
v1.10.0-alpha
v1.9.1-alpha
v1.9.0-alpha
v1.8.1-alpha
v1.8.0-alpha
v1.7.0-alpha
v1.6.0-alpha
v1.5.0-alpha
v1.4.0-alpha
v1.3.0-alpha
v1.2.2-alpha
v1.2.1-alpha
v1.2.0-alpha
v1.1.2-alpha
v1.1.1-alpha
v1.1.0-alpha
v1.0.6-alpha
v1.0.5-alpha
v1.0.4-alpha
v1.0.3-alpha
v1.0.2-alpha
v1.0.1-alpha
v1.0.0-alpha
compare:
txns
two-way-comms
tracing
three-easy-steps
test-pr-link
test-ci
tags-and-releases
summarize-rounds
stars
stacked-prs
spindle
sl/wnrvrwyvrlzo
sl/uvpzuszrulvq
sl/spindle-rewrite
sl/okmkyytolvko
sl/lwwtkzxrqksy
sl/kzmmroxoztll
sl/kuvknkxxvpvu
sl/git-objects
settings-router
session-refresh
revert-telemetry
repotab-icons
repo-deletion
render-markup
remove-set-repo-at
release-artifacts
refactor-ks-client
reduce-dids
push-zxovstvplnok
push-zwoymssunvsp
push-zvuqkxuqzmlx
push-zvtqtworuppk
push-zvkrywwskknq
push-zunxqvqoqmoq
push-zrvypwzlqtvz
push-zrkqwwrtxvzn
push-zqrmrxxvrylu
push-zqqvxroypxvy
push-zomkuzmzokvl
push-znrkktvllqsp
push-znlsowpxwvvq
push-zlzkyrrnylxr
push-zklmrltkvxkq
push-yytstxoqlwmx
push-yynslowxntql
push-ywynwzosnrox
push-yuwoytzsmkvm
push-yulzrzunkznl
push-ytzxyplmvunn
push-ytpxvpknlxun
push-ytkuzknmmrmn
push-yssxzpkyorwv
push-yqnqquktxqpx
push-ylvkxyvlmkyq
push-ykxtvrlpxwrl
push-ykwytywspowp
push-xwxnuorpzwsu
push-xwotmtuuvokm
push-xvuvxolnykpl
push-xvrvnuvsmslv
push-xvnwyvuwwylt
push-xtzkpqpkmvvr
push-xtuxqqlytkws
push-xttmuzqzktwv
push-xsmwulupylxx
push-xpolynpvotzt
push-xopyykmxuqzw
push-xmplzvpqtnru
push-xlupzwtslltp
push-wzlvlnywrxrs
push-wzkqymznotmx
push-wyonpyyzurwl
push-wykupplnxytq
push-wxwpnnytwxsq
push-wxvwuxxzswmv
push-wvywrqlppxqo
push-wvwxxlyxrxlm
push-wukyptkosoux
push-wtmrprmomuus
push-wsxnywmtsnrp
push-wqzwpukkvnuw
push-wpkykovtqxnx
push-wnwpznvpzwqu
push-wnotmtoqlnvl
push-wmkmzrsvlkmk
push-wkwyolovuxvq
push-vyyrpsmynrwo
push-vyusnwqnmxwy
push-vynsusnqpmus
push-vwyomovpppwp
push-vwuuzxwwsvow
push-vvluoqoywsvp
push-vuzywsvmkwqn
push-vtunvupqotuo
push-vssvkowylwvx
push-vquoltwpkuny
push-vnxxyxursomy
push-vmxtqnzznxvk
push-vkywmxzkrnqx
push-uylxxnsupvlk
push-uwuymmkuwsrs
push-uwotwontksrz
push-uuyqkqmkmlsn
push-uruyyqupsmro
push-upnknovvnoqr
push-uotxnqrqnlyl
push-ummlkpsrvrvn
push-ukznmmplmlul
push-ukmxnzrlrpuo
push-tzyltlmtsznq
push-tyxzqwosmzuv
push-twskywooslyl
push-twnpklmstzor
push-twlxnytrpyuk
push-tvvnxqyykkwr
push-tvtwutsrtoxt
push-tultwuoxrmun
push-ttyzwxqtkpsl
push-tnwwtqxpoqpk
push-tlxunysxvxwk
push-tkxmuvxkylmy
push-tkusxvwknltw
push-synlttxvkzox
push-svxqqzsxxkor
push-svtkmrzmrwky
push-stktrzvlltpw
push-sssuxsytslts
push-spvnpqlqqpkw
push-snyqozvrosmk
push-snktzuwttuvu
push-smsrusztuvwt
push-rvtqynpmozzy
push-rurrtsvmnvku
push-ruoqnsmttnxx
push-rtwrkkyllvkm
push-rtvxspsprvuv
push-rsrowlxunurk
push-rrsulrtnsmtw
push-rqzvvtnzkzvq
push-rqxyyurmpkps
push-rnztkppsvskw
push-rlorkkyzokzr
push-rlktrmqtttnn
push-qwnqkqnmovyn
push-qtrkvornpykm
push-qsmrouptzqkq
push-qruszvywputp
push-qrltzqmlrlln
push-qqwquvlysrxt
push-qqsspmnuxqwk
push-qqrowoolzppw
push-qoplqnlvlqqo
push-qnstkrpltlqu
push-qlzpkvltqlzm
push-qkpqsrknozxs
push-qkkmntrmxqly
push-pzwzzlwkkpnw
push-pzrysszkwwtk
push-pymywyqyumlo
push-pyllqzrprrny
push-pwqwlvnsqtqr
push-pvpnsqvvzuky
push-ptrrwwvnkmxq
push-psqwruuwvwlo
push-prszwlupsoqz
push-ppstsspzpxrx
push-potvrpwlpwsl
push-pnknzwntuuwx
push-pmtpswykplzr
push-plpuqtyrprou
push-plmznxvyqrqw
push-pkyzqzmqunnl
push-pkqzkqmxotyz
push-pkklqwlqwoml
push-owwtwvxyzowl
push-ovsuzpqkpmlt
push-ovrqrxnpvroz
push-ossmlnsnvstq
push-ormxrmmzomqu
push-oovokvlkywly
push-okymwlupkook
push-nwslswprzvmx
push-nwrnkztxkovl
push-nupxprsykpzq
push-nsolyvvsyvzw
push-nqrqutkzxskl
push-nozqtwvsrvkx
push-nnuvlyptxsqy
push-nmwononozzxz
push-nlvzxwtrmryl
push-nlnnyywnokyn
push-mzupsvxpvqvx
push-mzsvpkottnnt
push-myqmppunmplu
push-mwkwusmyymno
push-mvmrzuxwmzvs
push-mutuoxwuokqw
push-mtsxyxnkznyy
push-mtqkyzxrqnnn
push-msvlvnlwuyxy
push-mrwusmywonor
push-mqkxnymrqmzz
push-mozrrovxmlou
push-mopllyvuuuls
push-monplorvolvl
push-momltwttmuyq
push-mntstlmzqrvs
push-mmxkmyvyuzlk
push-mmrtnyoskwoo
push-mmnuoyuqplyk
push-mlxxvvqpzvlw
push-mllunlokmuxp
push-lzkzortpuyyq
push-lyrpkknpnrus
push-lxxtrqtnnoxy
push-lvuknrupsyox
push-lstutzylzylk
push-lrzzmtxokrxw
push-lnvnxmxlqkux
push-lnupvsypkuow
push-llqkvunvvzyv
push-ktsnmppqsnls
push-kstnoynmspqp
push-ksrsmmytwuul
push-kslplloylmlp
push-kpmvvnzwyosv
push-kltomzxpyoxq
pull-from-fork-pill
profile-timeline
profile-customizations
pr-actions
patch-requests
paginate-issues
packages-small
packages
oplog
opengraph
op/zxpquyouulmx
oauth
more-diff-stats
master
local-fragments
knot-xrpc
knot-local-clone
knot-cli
issues-edit
interdiff
improve-styling
improve-repo-name-checks
icy/tolqpt
group-profile-timeline
format-patch
fork-repo
fork-pulls
fix/follow-button-count-sync
fix/chip-inherit-styles
fix-tw-dark
fix-resubmit
fix-refresh-issues
fix-knot-forks
fix-jetstream-bugs
fix-env-test
file-tree
feat/pulls-pagination
feat/notifications-poll-interval
feat/fork-default-branch-only
feat/clickable-user-refs
feat/cleanup-notifications-on-undone
fast-dev
enable-html
drop-at
diff-improvements
did-email-assoc
crash-repro-knot
consolidate-syntax-styles
compare-merge-base
commits-page
commit-verification
ci-statuses
ci
camo
cache-busting
branch-prs-2
branch-prs
branch-fmt-patch
atprotate-pubkeys
all-repositories-page
add-migrations
v1.11.0-alpha
v1.10.0-alpha
v1.9.1-alpha
v1.9.0-alpha
v1.8.1-alpha
v1.8.0-alpha
v1.7.0-alpha
v1.6.0-alpha
v1.5.0-alpha
v1.4.0-alpha
v1.3.0-alpha
v1.2.2-alpha
v1.2.1-alpha
v1.2.0-alpha
v1.1.2-alpha
v1.1.1-alpha
v1.1.0-alpha
v1.0.6-alpha
v1.0.5-alpha
v1.0.4-alpha
v1.0.3-alpha
v1.0.2-alpha
v1.0.1-alpha
v1.0.0-alpha
go
+128
-109
5 changed files
expand all
collapse all
unified
split
appview
pages
pages.go
templates
user
fragments
follow-oob.html
followCard.html
profileCard.html
state
follow.go
+4
-3
appview/pages/pages.go
···
601
}
602
603
type FollowFragmentParams struct {
604
-
UserDid string
605
-
FollowStatus models.FollowStatus
0
606
}
607
608
func (p *Pages) FollowFragment(w io.Writer, params FollowFragmentParams) error {
609
-
return p.executePlain("user/fragments/follow", w, params)
610
}
611
612
type EditBioParams struct {
···
601
}
602
603
type FollowFragmentParams struct {
604
+
UserDid string
605
+
FollowStatus models.FollowStatus
606
+
FollowersCount int64
607
}
608
609
func (p *Pages) FollowFragment(w io.Writer, params FollowFragmentParams) error {
610
+
return p.executePlain("user/fragments/follow-oob", w, params)
611
}
612
613
type EditBioParams struct {
+6
appview/pages/templates/user/fragments/follow-oob.html
···
0
0
0
0
0
0
···
1
+
{{ define "user/fragments/follow-oob" }}
2
+
{{ template "user/fragments/follow" . }}
3
+
<span hx-swap-oob='innerHTML:[data-followers-did="{{ .UserDid }}"]'>
4
+
<a href="/{{ resolve .UserDid }}?tab=followers">{{ .FollowersCount }} followers</a>
5
+
</span>
6
+
{{ end }}
+5
-3
appview/pages/templates/user/fragments/followCard.html
···
9
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-2 w-full min-w-0">
10
<div class="flex-1 min-h-0 justify-around flex flex-col">
11
<a href="/{{ $userIdent }}">
12
-
<span class="font-bold dark:text-white overflow-hidden text-ellipsis whitespace-nowrap max-w-full">{{ $userIdent | truncateAt30 }}</span>
0
13
</a>
14
{{ with .Profile }}
15
<p class="text-sm pb-2 md:pb-2 break-words">{{.Description}}</p>
16
{{ end }}
17
<div class="text-sm flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full">
18
<span class="flex-shrink-0">{{ i "users" "size-4" }}</span>
19
-
<span id="followers"><a href="/{{ $userIdent }}?tab=followers">{{ .FollowersCount }} followers</a></span>
0
20
<span class="select-none after:content-['ยท']"></span>
21
<span id="following"><a href="/{{ $userIdent }}?tab=following">{{ .FollowingCount }} following</a></span>
22
</div>
···
29
</div>
30
</div>
31
</div>
32
-
{{ end }}
···
9
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-2 w-full min-w-0">
10
<div class="flex-1 min-h-0 justify-around flex flex-col">
11
<a href="/{{ $userIdent }}">
12
+
<span class="font-bold dark:text-white overflow-hidden text-ellipsis whitespace-nowrap max-w-full">{{
13
+
$userIdent | truncateAt30 }}</span>
14
</a>
15
{{ with .Profile }}
16
<p class="text-sm pb-2 md:pb-2 break-words">{{.Description}}</p>
17
{{ end }}
18
<div class="text-sm flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full">
19
<span class="flex-shrink-0">{{ i "users" "size-4" }}</span>
20
+
<span id="followers" data-followers-did="{{ .UserDid }}"><a href="/{{ $userIdent }}?tab=followers">{{
21
+
.FollowersCount }} followers</a></span>
22
<span class="select-none after:content-['ยท']"></span>
23
<span id="following"><a href="/{{ $userIdent }}?tab=following">{{ .FollowingCount }} following</a></span>
24
</div>
···
31
</div>
32
</div>
33
</div>
34
+
{{ end }}
+97
-99
appview/pages/templates/user/fragments/profileCard.html
···
1
{{ define "user/fragments/profileCard" }}
2
-
{{ $userIdent := resolve .UserDid }}
3
-
<div class="grid grid-cols-3 md:grid-cols-1 gap-1 items-center">
4
-
<div id="avatar" class="col-span-1 flex justify-center items-center">
5
-
<div class="w-3/4 aspect-square relative">
6
-
<img class="absolute inset-0 w-full h-full object-cover rounded-full p-2" src="{{ fullAvatar .UserDid }}" />
7
-
</div>
8
-
</div>
9
-
<div class="col-span-2">
10
-
<div class="flex items-center flex-row flex-nowrap gap-2">
11
-
<p title="{{ $userIdent }}"
12
-
class="text-lg font-bold dark:text-white overflow-hidden text-ellipsis whitespace-nowrap">
13
-
{{ $userIdent }}
14
-
</p>
15
-
{{ with .Profile }}
16
-
{{ if .Pronouns }}
17
-
<p class="text-gray-500 dark:text-gray-400">{{ .Pronouns }}</p>
18
-
{{ end }}
19
-
{{ end }}
20
-
</div>
21
22
-
<div class="md:hidden">
23
-
{{ block "followerFollowing" (list . $userIdent) }} {{ end }}
24
-
</div>
25
-
</div>
26
-
<div class="col-span-3 md:col-span-full">
27
-
<div id="profile-bio" class="text-sm">
28
-
{{ $profile := .Profile }}
29
-
{{ with .Profile }}
30
31
-
{{ if .Description }}
32
-
<p class="text-base pb-4 md:pb-2">{{ .Description }}</p>
33
-
{{ end }}
34
35
-
<div class="hidden md:block">
36
-
{{ block "followerFollowing" (list $ $userIdent) }} {{ end }}
37
-
</div>
38
39
-
<div class="flex flex-col gap-2 mb-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full">
40
-
{{ if .Location }}
41
-
<div class="flex items-center gap-2">
42
-
<span class="flex-shrink-0">{{ i "map-pin" "size-4" }}</span>
43
-
<span>{{ .Location }}</span>
44
-
</div>
45
-
{{ end }}
46
-
{{ if .IncludeBluesky }}
47
-
<div class="flex items-center gap-2">
48
-
<span class="flex-shrink-0">{{ template "user/fragments/bluesky" "w-4 h-4 text-black dark:text-white" }}</span>
49
-
<a id="bluesky-link" href="https://bsky.app/profile/{{ $.UserDid }}">{{ $userIdent }}</a>
50
-
</div>
51
-
{{ end }}
52
-
{{ range $link := .Links }}
53
-
{{ if $link }}
54
-
<div class="flex items-center gap-2">
55
-
<span class="flex-shrink-0">{{ i "link" "size-4" }}</span>
56
-
<a href="{{ $link }}">{{ $link }}</a>
57
-
</div>
58
-
{{ end }}
59
-
{{ end }}
60
-
{{ if not $profile.IsStatsEmpty }}
61
-
<div class="flex items-center justify-evenly gap-2 py-2">
62
-
{{ range $stat := .Stats }}
63
-
{{ if $stat.Kind }}
64
-
<div class="flex flex-col items-center gap-2">
65
-
<span class="text-xl font-bold">{{ $stat.Value }}</span>
66
-
<span>{{ $stat.Kind.String }}</span>
67
-
</div>
68
-
{{ end }}
69
-
{{ end }}
70
-
</div>
71
-
{{ end }}
72
</div>
73
{{ end }}
0
0
0
0
0
74
75
-
<div class="flex mt-2 items-center gap-2">
76
-
{{ if ne .FollowStatus.String "IsSelf" }}
77
-
{{ template "user/fragments/follow" . }}
78
-
{{ else }}
79
-
<button id="editBtn"
80
-
class="btn w-full flex items-center gap-2 group"
81
-
hx-target="#profile-bio"
82
-
hx-get="/profile/edit-bio"
83
-
hx-swap="innerHTML">
84
-
{{ i "pencil" "w-4 h-4" }}
85
-
edit
86
-
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
87
-
</button>
88
-
{{ end }}
89
-
90
-
<a class="btn text-sm no-underline hover:no-underline flex items-center gap-2 group"
91
-
href="/{{ $userIdent }}/feed.atom">
92
-
{{ i "rss" "size-4" }}
93
-
</a>
94
-
</div>
95
96
-
</div>
97
-
<div id="update-profile" class="text-red-400 dark:text-red-500"></div>
0
0
98
</div>
0
99
</div>
0
0
0
100
{{ end }}
101
102
{{ define "followerFollowing" }}
103
-
{{ $root := index . 0 }}
104
-
{{ $userIdent := index . 1 }}
105
-
{{ with $root }}
106
-
<div class="flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full text-sm">
107
-
<span class="flex-shrink-0">{{ i "users" "size-4" }}</span>
108
-
<span id="followers"><a href="/{{ $userIdent }}?tab=followers">{{ .Stats.FollowersCount }} followers</a></span>
109
-
<span class="select-none after:content-['ยท']"></span>
110
-
<span id="following"><a href="/{{ $userIdent }}?tab=following">{{ .Stats.FollowingCount }} following</a></span>
111
-
</div>
112
-
{{ end }}
113
{{ end }}
114
-
···
1
{{ define "user/fragments/profileCard" }}
2
+
{{ $userIdent := resolve .UserDid }}
3
+
<div class="grid grid-cols-3 md:grid-cols-1 gap-1 items-center">
4
+
<div id="avatar" class="col-span-1 flex justify-center items-center">
5
+
<div class="w-3/4 aspect-square relative">
6
+
<img class="absolute inset-0 w-full h-full object-cover rounded-full p-2" src="{{ fullAvatar .UserDid }}" />
7
+
</div>
8
+
</div>
9
+
<div class="col-span-2">
10
+
<div class="flex items-center flex-row flex-nowrap gap-2">
11
+
<p title="{{ $userIdent }}"
12
+
class="text-lg font-bold dark:text-white overflow-hidden text-ellipsis whitespace-nowrap">
13
+
{{ $userIdent }}
14
+
</p>
15
+
{{ with .Profile }}
16
+
{{ if .Pronouns }}
17
+
<p class="text-gray-500 dark:text-gray-400">{{ .Pronouns }}</p>
18
+
{{ end }}
19
+
{{ end }}
20
+
</div>
21
22
+
<div class="md:hidden">
23
+
{{ block "followerFollowing" (list . $userIdent) }} {{ end }}
24
+
</div>
25
+
</div>
26
+
<div class="col-span-3 md:col-span-full">
27
+
<div id="profile-bio" class="text-sm">
28
+
{{ $profile := .Profile }}
29
+
{{ with .Profile }}
30
31
+
{{ if .Description }}
32
+
<p class="text-base pb-4 md:pb-2">{{ .Description }}</p>
33
+
{{ end }}
34
35
+
<div class="hidden md:block">
36
+
{{ block "followerFollowing" (list $ $userIdent) }} {{ end }}
37
+
</div>
38
39
+
<div class="flex flex-col gap-2 mb-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full">
40
+
{{ if .Location }}
41
+
<div class="flex items-center gap-2">
42
+
<span class="flex-shrink-0">{{ i "map-pin" "size-4" }}</span>
43
+
<span>{{ .Location }}</span>
44
+
</div>
45
+
{{ end }}
46
+
{{ if .IncludeBluesky }}
47
+
<div class="flex items-center gap-2">
48
+
<span class="flex-shrink-0">{{ template "user/fragments/bluesky" "w-4 h-4 text-black dark:text-white"
49
+
}}</span>
50
+
<a id="bluesky-link" href="https://bsky.app/profile/{{ $.UserDid }}">{{ $userIdent }}</a>
51
+
</div>
52
+
{{ end }}
53
+
{{ range $link := .Links }}
54
+
{{ if $link }}
55
+
<div class="flex items-center gap-2">
56
+
<span class="flex-shrink-0">{{ i "link" "size-4" }}</span>
57
+
<a href="{{ $link }}">{{ $link }}</a>
58
+
</div>
59
+
{{ end }}
60
+
{{ end }}
61
+
{{ if not $profile.IsStatsEmpty }}
62
+
<div class="flex items-center justify-evenly gap-2 py-2">
63
+
{{ range $stat := .Stats }}
64
+
{{ if $stat.Kind }}
65
+
<div class="flex flex-col items-center gap-2">
66
+
<span class="text-xl font-bold">{{ $stat.Value }}</span>
67
+
<span>{{ $stat.Kind.String }}</span>
0
0
0
0
68
</div>
69
{{ end }}
70
+
{{ end }}
71
+
</div>
72
+
{{ end }}
73
+
</div>
74
+
{{ end }}
75
76
+
<div class="flex mt-2 items-center gap-2">
77
+
{{ if ne .FollowStatus.String "IsSelf" }}
78
+
{{ template "user/fragments/follow" . }}
79
+
{{ else }}
80
+
<button id="editBtn" class="btn w-full flex items-center gap-2 group" hx-target="#profile-bio"
81
+
hx-get="/profile/edit-bio" hx-swap="innerHTML">
82
+
{{ i "pencil" "w-4 h-4" }}
83
+
edit
84
+
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
85
+
</button>
86
+
{{ end }}
0
0
0
0
0
0
0
0
0
87
88
+
<a class="btn text-sm no-underline hover:no-underline flex items-center gap-2 group"
89
+
href="/{{ $userIdent }}/feed.atom">
90
+
{{ i "rss" "size-4" }}
91
+
</a>
92
</div>
93
+
94
</div>
95
+
<div id="update-profile" class="text-red-400 dark:text-red-500"></div>
96
+
</div>
97
+
</div>
98
{{ end }}
99
100
{{ define "followerFollowing" }}
101
+
{{ $root := index . 0 }}
102
+
{{ $userIdent := index . 1 }}
103
+
{{ with $root }}
104
+
<div class="flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full text-sm">
105
+
<span class="flex-shrink-0">{{ i "users" "size-4" }}</span>
106
+
<span id="followers" data-followers-did="{{ .UserDid }}"><a href="/{{ $userIdent }}?tab=followers">{{
107
+
.Stats.FollowersCount }} followers</a></span>
108
+
<span class="select-none after:content-['ยท']"></span>
109
+
<span id="following"><a href="/{{ $userIdent }}?tab=following">{{ .Stats.FollowingCount }} following</a></span>
110
+
</div>
111
{{ end }}
112
+
{{ end }}
+16
-4
appview/state/follow.go
···
75
76
s.notifier.NewFollow(r.Context(), follow)
77
0
0
0
0
0
78
s.pages.FollowFragment(w, pages.FollowFragmentParams{
79
-
UserDid: subjectIdent.DID.String(),
80
-
FollowStatus: models.IsFollowing,
0
81
})
82
83
return
···
106
// this is not an issue, the firehose event might have already done this
107
}
108
0
0
0
0
0
109
s.pages.FollowFragment(w, pages.FollowFragmentParams{
110
-
UserDid: subjectIdent.DID.String(),
111
-
FollowStatus: models.IsNotFollowing,
0
112
})
113
114
s.notifier.DeleteFollow(r.Context(), follow)
···
75
76
s.notifier.NewFollow(r.Context(), follow)
77
78
+
followStats, err := db.GetFollowerFollowingCount(s.db, subjectIdent.DID.String())
79
+
if err != nil {
80
+
log.Println("failed to get follow stats", err)
81
+
}
82
+
83
s.pages.FollowFragment(w, pages.FollowFragmentParams{
84
+
UserDid: subjectIdent.DID.String(),
85
+
FollowStatus: models.IsFollowing,
86
+
FollowersCount: followStats.Followers,
87
})
88
89
return
···
112
// this is not an issue, the firehose event might have already done this
113
}
114
115
+
followStats, err := db.GetFollowerFollowingCount(s.db, subjectIdent.DID.String())
116
+
if err != nil {
117
+
log.Println("failed to get follow stats", err)
118
+
}
119
+
120
s.pages.FollowFragment(w, pages.FollowFragmentParams{
121
+
UserDid: subjectIdent.DID.String(),
122
+
FollowStatus: models.IsNotFollowing,
123
+
FollowersCount: followStats.Followers,
124
})
125
126
s.notifier.DeleteFollow(r.Context(), follow)