forked from tangled.org/core
Monorepo for Tangled

Compare changes

Choose any two refs to compare.

Changed files
+280 -218
appview
pages
templates
reporesolver
state
docs
lexicons
pulls
+3 -4
appview/pages/pages.go
··· 601 601 } 602 602 603 603 type FollowFragmentParams struct { 604 - UserDid string 605 - FollowStatus models.FollowStatus 606 - FollowersCount int64 604 + UserDid string 605 + FollowStatus models.FollowStatus 607 606 } 608 607 609 608 func (p *Pages) FollowFragment(w io.Writer, params FollowFragmentParams) error { 610 - return p.executePlain("user/fragments/follow-oob", w, params) 609 + return p.executePlain("user/fragments/follow", w, params) 611 610 } 612 611 613 612 type EditBioParams struct {
+7 -5
appview/pages/templates/fragments/tinyAvatarList.html
··· 5 5 <div class="inline-flex items-center -space-x-3"> 6 6 {{ $c := "z-50 z-40 z-30 z-20 z-10" }} 7 7 {{ range $i, $p := $ps }} 8 - <img 9 - src="{{ tinyAvatar . }}" 10 - alt="" 11 - class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900 z-{{sub 5 $i}}0 {{ $classes }}" 12 - /> 8 + <a href="/{{ resolve . }}" title="{{ resolve . }}"> 9 + <img 10 + src="{{ tinyAvatar . }}" 11 + alt="" 12 + class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900 z-{{sub 5 $i}}0 {{ $classes }}" 13 + /> 14 + </a> 13 15 {{ end }} 14 16 15 17 {{ if gt (len $all) 5 }}
+30 -17
appview/pages/templates/labels/fragments/label.html
··· 2 2 {{ $d := .def }} 3 3 {{ $v := .val }} 4 4 {{ $withPrefix := .withPrefix }} 5 - <span class="w-fit flex items-center gap-2 font-normal normal-case rounded py-1 px-2 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm"> 6 - {{ template "repo/fragments/colorBall" (dict "color" $d.GetColor) }} 7 5 8 - {{ $lhs := printf "%s" $d.Name }} 9 - {{ $rhs := "" }} 6 + {{ $lhs := printf "%s" $d.Name }} 7 + {{ $rhs := "" }} 8 + {{ $isDid := false }} 9 + {{ $resolvedVal := "" }} 10 10 11 - {{ if not $d.ValueType.IsNull }} 12 - {{ if $d.ValueType.IsDidFormat }} 13 - {{ $v = resolve $v }} 14 - {{ end }} 11 + {{ if not $d.ValueType.IsNull }} 12 + {{ $isDid = $d.ValueType.IsDidFormat }} 13 + {{ if $isDid }} 14 + {{ $resolvedVal = resolve $v }} 15 + {{ $v = $resolvedVal }} 16 + {{ end }} 17 + 18 + {{ if not $withPrefix }} 19 + {{ $lhs = "" }} 20 + {{ else }} 21 + {{ $lhs = printf "%s/" $d.Name }} 22 + {{ end }} 15 23 16 - {{ if not $withPrefix }} 17 - {{ $lhs = "" }} 18 - {{ else }} 19 - {{ $lhs = printf "%s/" $d.Name }} 20 - {{ end }} 24 + {{ $rhs = printf "%s" $v }} 25 + {{ end }} 21 26 22 - {{ $rhs = printf "%s" $v }} 23 - {{ end }} 27 + {{ $chipClasses := "w-fit flex items-center gap-2 font-normal normal-case rounded py-1 px-2 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm" }} 24 28 25 - {{ printf "%s%s" $lhs $rhs }} 26 - </span> 29 + {{ if $isDid }} 30 + <a href="/{{ $resolvedVal }}" class="{{ $chipClasses }} no-underline hover:underline"> 31 + {{ template "repo/fragments/colorBall" (dict "color" $d.GetColor) }} 32 + {{ printf "%s%s" $lhs $rhs }} 33 + </a> 34 + {{ else }} 35 + <span class="{{ $chipClasses }}"> 36 + {{ template "repo/fragments/colorBall" (dict "color" $d.GetColor) }} 37 + {{ printf "%s%s" $lhs $rhs }} 38 + </span> 39 + {{ end }} 27 40 {{ end }} 28 41 29 42
-6
appview/pages/templates/user/fragments/follow-oob.html
··· 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 }}
+3 -5
appview/pages/templates/user/fragments/followCard.html
··· 9 9 <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-2 w-full min-w-0"> 10 10 <div class="flex-1 min-h-0 justify-around flex flex-col"> 11 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> 12 + <span class="font-bold dark:text-white overflow-hidden text-ellipsis whitespace-nowrap max-w-full">{{ $userIdent | truncateAt30 }}</span> 14 13 </a> 15 14 {{ with .Profile }} 16 15 <p class="text-sm pb-2 md:pb-2 break-words">{{.Description}}</p> 17 16 {{ end }} 18 17 <div class="text-sm flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full"> 19 18 <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> 19 + <span id="followers"><a href="/{{ $userIdent }}?tab=followers">{{ .FollowersCount }} followers</a></span> 22 20 <span class="select-none after:content-['ยท']"></span> 23 21 <span id="following"><a href="/{{ $userIdent }}?tab=following">{{ .FollowingCount }} following</a></span> 24 22 </div> ··· 31 29 </div> 32 30 </div> 33 31 </div> 34 - {{ end }} 32 + {{ end }}
+99 -97
appview/pages/templates/user/fragments/profileCard.html
··· 1 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> 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 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 }} 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 30 31 - {{ if .Description }} 32 - <p class="text-base pb-4 md:pb-2">{{ .Description }}</p> 33 - {{ end }} 31 + {{ if .Description }} 32 + <p class="text-base pb-4 md:pb-2">{{ .Description }}</p> 33 + {{ end }} 34 34 35 - <div class="hidden md:block"> 36 - {{ block "followerFollowing" (list $ $userIdent) }} {{ end }} 37 - </div> 35 + <div class="hidden md:block"> 36 + {{ block "followerFollowing" (list $ $userIdent) }} {{ end }} 37 + </div> 38 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> 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 }} 68 72 </div> 69 73 {{ end }} 70 - {{ end }} 71 - </div> 72 - {{ end }} 73 - </div> 74 - {{ end }} 75 74 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 }} 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 }} 87 89 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> 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> 93 95 96 + </div> 97 + <div id="update-profile" class="text-red-400 dark:text-red-500"></div> 98 + </div> 94 99 </div> 95 - <div id="update-profile" class="text-red-400 dark:text-red-500"></div> 96 - </div> 97 - </div> 98 100 {{ end }} 99 101 100 102 {{ 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> 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 }} 111 113 {{ end }} 112 - {{ end }} 114 +
+26 -1
appview/reporesolver/resolver.go
··· 63 63 } 64 64 65 65 // get dir/ref 66 - currentDir := path.Dir(extractPathAfterRef(r.URL.EscapedPath())) 66 + currentDir := extractCurrentDir(r.URL.EscapedPath()) 67 67 ref := chi.URLParam(r, "ref") 68 68 69 69 repoAt := repo.RepoAt() ··· 130 130 } 131 131 132 132 return repoInfo 133 + } 134 + 135 + // extractCurrentDir gets the current directory for markdown link resolution. 136 + // for blob paths, returns the parent dir. for tree paths, returns the path itself. 137 + // 138 + // /@user/repo/blob/main/docs/README.md => docs 139 + // /@user/repo/tree/main/docs => docs 140 + func extractCurrentDir(fullPath string) string { 141 + fullPath = strings.TrimPrefix(fullPath, "/") 142 + 143 + blobPattern := regexp.MustCompile(`blob/[^/]+/(.*)$`) 144 + if matches := blobPattern.FindStringSubmatch(fullPath); len(matches) > 1 { 145 + return path.Dir(matches[1]) 146 + } 147 + 148 + treePattern := regexp.MustCompile(`tree/[^/]+/(.*)$`) 149 + if matches := treePattern.FindStringSubmatch(fullPath); len(matches) > 1 { 150 + dir := strings.TrimSuffix(matches[1], "/") 151 + if dir == "" { 152 + return "." 153 + } 154 + return dir 155 + } 156 + 157 + return "." 133 158 } 134 159 135 160 // extractPathAfterRef gets the actual repository path
+22
appview/reporesolver/resolver_test.go
··· 1 + package reporesolver 2 + 3 + import "testing" 4 + 5 + func TestExtractCurrentDir(t *testing.T) { 6 + tests := []struct { 7 + path string 8 + want string 9 + }{ 10 + {"/@user/repo/blob/main/docs/README.md", "docs"}, 11 + {"/@user/repo/blob/main/README.md", "."}, 12 + {"/@user/repo/tree/main/docs", "docs"}, 13 + {"/@user/repo/tree/main/docs/", "docs"}, 14 + {"/@user/repo/tree/main", "."}, 15 + } 16 + 17 + for _, tt := range tests { 18 + if got := extractCurrentDir(tt.path); got != tt.want { 19 + t.Errorf("extractCurrentDir(%q) = %q, want %q", tt.path, got, tt.want) 20 + } 21 + } 22 + }
+4 -16
appview/state/follow.go
··· 75 75 76 76 s.notifier.NewFollow(r.Context(), follow) 77 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 78 s.pages.FollowFragment(w, pages.FollowFragmentParams{ 84 - UserDid: subjectIdent.DID.String(), 85 - FollowStatus: models.IsFollowing, 86 - FollowersCount: followStats.Followers, 79 + UserDid: subjectIdent.DID.String(), 80 + FollowStatus: models.IsFollowing, 87 81 }) 88 82 89 83 return ··· 112 106 // this is not an issue, the firehose event might have already done this 113 107 } 114 108 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 109 s.pages.FollowFragment(w, pages.FollowFragmentParams{ 121 - UserDid: subjectIdent.DID.String(), 122 - FollowStatus: models.IsNotFollowing, 123 - FollowersCount: followStats.Followers, 110 + UserDid: subjectIdent.DID.String(), 111 + FollowStatus: models.IsNotFollowing, 124 112 }) 125 113 126 114 s.notifier.DeleteFollow(r.Context(), follow)
+23 -25
docs/DOCS.md
··· 2 2 title: Tangled docs 3 3 author: The Tangled Contributors 4 4 date: 21 Sun, Dec 2025 5 - --- 6 - 7 - # Introduction 8 - 9 - Tangled is a decentralized code hosting and collaboration 10 - platform. Every component of Tangled is open-source and 11 - self-hostable. [tangled.org](https://tangled.org) also 12 - provides hosting and CI services that are free to use. 5 + abstract: | 6 + Tangled is a decentralized code hosting and collaboration 7 + platform. Every component of Tangled is open-source and 8 + self-hostable. [tangled.org](https://tangled.org) also 9 + provides hosting and CI services that are free to use. 13 10 14 - There are several models for decentralized code 15 - collaboration platforms, ranging from ActivityPubโ€™s 16 - (Forgejo) federated model, to Radicleโ€™s entirely P2P model. 17 - Our approach attempts to be the best of both worlds by 18 - adopting the AT Protocolโ€”a protocol for building decentralized 19 - social applications with a central identity 11 + There are several models for decentralized code 12 + collaboration platforms, ranging from ActivityPubโ€™s 13 + (Forgejo) federated model, to Radicleโ€™s entirely P2P model. 14 + Our approach attempts to be the best of both worlds by 15 + adopting the AT Protocolโ€”a protocol for building decentralized 16 + social applications with a central identity 20 17 21 - Our approach to this is the idea of โ€œknotsโ€. Knots are 22 - lightweight, headless servers that enable users to host Git 23 - repositories with ease. Knots are designed for either single 24 - or multi-tenant use which is perfect for self-hosting on a 25 - Raspberry Pi at home, or larger โ€œcommunityโ€ servers. By 26 - default, Tangled provides managed knots where you can host 27 - your repositories for free. 18 + Our approach to this is the idea of โ€œknotsโ€. Knots are 19 + lightweight, headless servers that enable users to host Git 20 + repositories with ease. Knots are designed for either single 21 + or multi-tenant use which is perfect for self-hosting on a 22 + Raspberry Pi at home, or larger โ€œcommunityโ€ servers. By 23 + default, Tangled provides managed knots where you can host 24 + your repositories for free. 28 25 29 - The appview at tangled.org acts as a consolidated "view" 30 - into the whole network, allowing users to access, clone and 31 - contribute to repositories hosted across different knots 32 - seamlessly. 26 + The appview at tangled.org acts as a consolidated "view" 27 + into the whole network, allowing users to access, clone and 28 + contribute to repositories hosted across different knots 29 + seamlessly. 30 + --- 33 31 34 32 # Quick start guide 35 33
+7
docs/search.html
··· 1 + <form action="https://google.com/search" role="search" aria-label="Sitewide" class="w-full"> 2 + <input type="hidden" name="q" value="+[inurl:https://docs.tangled.org]"> 3 + <label> 4 + <span style="display:none;">Search</span> 5 + <input type="text" name="q" placeholder="Search docs ..." class="w-full font-normal"> 6 + </label> 7 + </form>
+53 -41
docs/template.html
··· 37 37 <link rel="preload" href="/static/fonts/InterVariable.woff2" as="font" type="font/woff2" crossorigin /> 38 38 39 39 </head> 40 - <body class="bg-white dark:bg-gray-900 min-h-screen flex flex-col min-h-screen"> 40 + <body class="bg-white dark:bg-gray-900 flex flex-col min-h-svh"> 41 41 $for(include-before)$ 42 42 $include-before$ 43 43 $endfor$ ··· 60 60 id="mobile-toc-popover" 61 61 popover 62 62 class="mobile-toc-popover 63 - bg-white dark:bg-gray-800 64 - border-b border-gray-200 dark:border-gray-700 65 - h-full overflow-y-auto 63 + bg-gray-50 dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 64 + h-full overflow-y-auto shadow-sm 66 65 px-6 py-4 fixed inset-x-0 top-0 w-fit max-w-4/5 m-0" 67 66 > 68 - <button 69 - type="button" 70 - popovertarget="mobile-toc-popover" 71 - popovertargetaction="toggle" 72 - class="w-full flex gap-2 items-center text-sm font-semibold dark:text-white mb-4"> 73 - ${ x.svg() } 74 - $if(toc-title)$$toc-title$$else$Table of Contents$endif$ 75 - </button> 76 - ${ table-of-contents:toc.html() } 77 - ${ single-page:mode.html() } 67 + <div class="flex flex-col min-h-full"> 68 + <div class="flex-1 space-y-4"> 69 + <button 70 + type="button" 71 + popovertarget="mobile-toc-popover" 72 + popovertargetaction="toggle" 73 + class="w-full flex gap-2 items-center text-sm font-semibold dark:text-white mb-4"> 74 + ${ x.svg() } 75 + $if(toc-title)$$toc-title$$else$Table of Contents$endif$ 76 + </button> 77 + ${ search.html() } 78 + ${ table-of-contents:toc.html() } 79 + </div> 80 + ${ single-page:mode.html() } 81 + </div> 78 82 </div> 79 - 80 83 81 84 <!-- desktop sidebar toc --> 82 - <nav id="$idprefix$TOC" role="doc-toc" class="hidden md:block fixed left-0 top-0 w-80 h-screen bg-gray-50 dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 overflow-y-auto p-4 z-50"> 83 - $if(toc-title)$ 84 - <h2 id="$idprefix$toc-title" class="text-lg font-semibold mb-4 text-gray-900">$toc-title$</h2> 85 - $endif$ 86 - ${ table-of-contents:toc.html() } 85 + <nav 86 + id="$idprefix$TOC" 87 + role="doc-toc" 88 + class="hidden md:flex md:flex-col gap-4 fixed left-0 top-0 w-80 h-screen 89 + bg-gray-50 dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 90 + p-4 z-50 overflow-y-auto"> 91 + ${ search.html() } 92 + <div class="flex-1"> 93 + $if(toc-title)$ 94 + <h2 id="$idprefix$toc-title" class="text-lg font-semibold mb-4 text-gray-900">$toc-title$</h2> 95 + $endif$ 96 + ${ table-of-contents:toc.html() } 97 + </div> 87 98 ${ single-page:mode.html() } 88 99 </nav> 89 100 $endif$ ··· 91 102 <div class="$if(toc)$md:ml-80$endif$ flex-1 flex flex-col"> 92 103 <main class="max-w-4xl w-full mx-auto p-6 flex-1"> 93 104 $if(top)$ 94 - $-- only print title block if this is NOT the top page 105 + $-- only print title block if this is NOT the top page 95 106 $else$ 96 107 $if(title)$ 97 - <header id="title-block-header" class="mb-8 pb-8 border-b border-gray-200 dark:border-gray-700"> 98 - <h1 class="text-4xl font-bold mb-2 text-black dark:text-white">$title$</h1> 99 - $if(subtitle)$ 100 - <p class="text-xl text-gray-500 dark:text-gray-400 mb-2">$subtitle$</p> 101 - $endif$ 102 - $for(author)$ 103 - <p class="text-sm text-gray-500 dark:text-gray-400">$author$</p> 104 - $endfor$ 105 - $if(date)$ 106 - <p class="text-sm text-gray-500 dark:text-gray-400">Updated on $date$</p> 107 - $endif$ 108 - $if(abstract)$ 109 - <div class="mt-6 p-4 bg-gray-50 rounded-lg"> 110 - <div class="text-sm font-semibold text-gray-700 uppercase mb-2">$abstract-title$</div> 111 - <div class="text-gray-700">$abstract$</div> 112 - </div> 113 - $endif$ 114 - $endif$ 115 - </header> 108 + <header id="title-block-header" class="mb-8 pb-8 border-b border-gray-200 dark:border-gray-700"> 109 + <h1 class="text-4xl font-bold mb-2 text-black dark:text-white">$title$</h1> 110 + $if(subtitle)$ 111 + <p class="text-xl text-gray-500 dark:text-gray-400 mb-2">$subtitle$</p> 112 + $endif$ 113 + $for(author)$ 114 + <p class="text-sm text-gray-500 dark:text-gray-400">$author$</p> 115 + $endfor$ 116 + $if(date)$ 117 + <p class="text-sm text-gray-500 dark:text-gray-400">Updated on $date$</p> 118 + $endif$ 119 + $endif$ 120 + </header> 121 + $endif$ 122 + 123 + $if(abstract)$ 124 + <article class="prose dark:prose-invert max-w-none"> 125 + $abstract$ 126 + </article> 116 127 $endif$ 128 + 117 129 <article class="prose dark:prose-invert max-w-none"> 118 130 $body$ 119 131 </article> 120 132 </main> 121 - <nav id="sitenav" class="border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 "> 133 + <nav id="sitenav" class="border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800"> 122 134 <div class="max-w-4xl mx-auto px-8 py-4"> 123 135 <div class="flex justify-between gap-4"> 124 136 <span class="flex-1">
+3 -1
lexicons/pulls/pull.json
··· 32 32 }, 33 33 "patchBlob": { 34 34 "type": "blob", 35 - "accept": "text/x-patch", 35 + "accept": [ 36 + "text/x-patch" 37 + ], 36 38 "description": "patch content" 37 39 }, 38 40 "source": {