Monorepo for Tangled tangled.org

Compare changes

Choose any two refs to compare.

+196 -448
-23
appview/db/profile.go
··· 98 98 }) 99 99 } 100 100 101 - punchcard, err := MakePunchcard( 102 - e, 103 - orm.FilterEq("did", forDid), 104 - orm.FilterGte("date", time.Now().AddDate(0, -TimeframeMonths, 0)), 105 - ) 106 - if err != nil { 107 - return nil, fmt.Errorf("error getting commits by did: %w", err) 108 - } 109 - for _, punch := range punchcard.Punches { 110 - if punch.Date.After(now) { 111 - continue 112 - } 113 - 114 - monthsAgo := monthsBetween(punch.Date, now) 115 - if monthsAgo >= TimeframeMonths { 116 - // shouldn't happen; but times are weird 117 - continue 118 - } 119 - 120 - idx := monthsAgo 121 - timeline.ByMonth[idx].Commits += punch.Count 122 - } 123 - 124 101 return &timeline, nil 125 102 } 126 103
-18
appview/pages/funcmap.go
··· 332 332 } 333 333 return dict, nil 334 334 }, 335 - "queryParams": func(params ...any) (url.Values, error) { 336 - if len(params)%2 != 0 { 337 - return nil, errors.New("invalid queryParams call") 338 - } 339 - vals := make(url.Values, len(params)/2) 340 - for i := 0; i < len(params); i += 2 { 341 - key, ok := params[i].(string) 342 - if !ok { 343 - return nil, errors.New("queryParams keys must be strings") 344 - } 345 - v, ok := params[i+1].(string) 346 - if !ok { 347 - return nil, errors.New("queryParams values must be strings") 348 - } 349 - vals.Add(key, v) 350 - } 351 - return vals, nil 352 - }, 353 335 "deref": func(v any) any { 354 336 val := reflect.ValueOf(v) 355 337 if val.Kind() == reflect.Pointer && !val.IsNil() {
+1 -1
appview/pages/templates/banner.html
··· 30 30 <div class="mx-6"> 31 31 These services may not be fully accessible until upgraded. 32 32 <a class="underline text-red-800 dark:text-red-200" 33 - href="https://docs.tangled.org/migrating-knots-and-spindles.html"> 33 + href="https://docs.tangled.org/migrating-knots-spindles.html#migrating-knots-spindles"> 34 34 Click to read the upgrade guide</a>. 35 35 </div> 36 36 </details>
+2 -2
appview/pages/templates/fragments/pagination.html
··· 1 1 {{ define "fragments/pagination" }} 2 - {{/* Params: Page (pagination.Page), TotalCount (int), BasePath (string), QueryParams (url.Values) */}} 2 + {{/* Params: Page (pagination.Page), TotalCount (int), BasePath (string), QueryParams (string) */}} 3 3 {{ $page := .Page }} 4 4 {{ $totalCount := .TotalCount }} 5 5 {{ $basePath := .BasePath }} 6 - {{ $queryParams := safeUrl .QueryParams.Encode }} 6 + {{ $queryParams := .QueryParams }} 7 7 8 8 {{ $prev := $page.Previous.Offset }} 9 9 {{ $next := $page.Next.Offset }}
+1 -1
appview/pages/templates/labels/fragments/label.html
··· 24 24 {{ $rhs = printf "%s" $v }} 25 25 {{ end }} 26 26 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 text-inherit" }} 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" }} 28 28 29 29 {{ if $isDid }} 30 30 <a href="/{{ $resolvedVal }}" class="{{ $chipClasses }} no-underline hover:underline">
+28 -35
appview/pages/templates/layouts/fragments/topbar.html
··· 53 53 /> 54 54 <span class="hidden md:inline">{{ $user | resolve | truncateAt30 }}</span> 55 55 </summary> 56 - <div class="absolute right-0 mt-4 rounded bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700 shadow-lg z-50 text-sm" style="width: 14rem;"> 56 + <div class="absolute right-0 mt-4 p-4 rounded bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700 shadow-lg z-50" style="width: 14rem;"> 57 57 {{ $active := .Active.Did }} 58 - {{ $linkStyle := "flex items-center gap-3 px-4 py-2 hover:no-underline hover:bg-gray-50 hover:dark:bg-gray-700/50" }} 58 + 59 + <div class="pb-2 mb-2 border-b border-gray-200 dark:border-gray-700"> 60 + <div class="flex items-center gap-2"> 61 + <img src="{{ tinyAvatar $active }}" alt="" class="rounded-full h-8 w-8 flex-shrink-0 border border-gray-300 dark:border-gray-700" /> 62 + <div class="flex-1 overflow-hidden"> 63 + <p class="font-medium text-sm truncate">{{ $active | resolve }}</p> 64 + <p class="text-xs text-green-600 dark:text-green-400">active</p> 65 + </div> 66 + </div> 67 + </div> 59 68 60 69 {{ $others := .Accounts | otherAccounts $active }} 61 70 {{ if $others }} 62 - <div class="text-sm text-gray-500 dark:text-gray-400 px-3 py-1 pt-2">switch account</div> 63 - {{ range $others }} 71 + <div class="pb-2 mb-2 border-b border-gray-200 dark:border-gray-700"> 72 + <p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-1">Switch Account</p> 73 + {{ range $others }} 64 74 <button 65 75 type="button" 66 76 hx-post="/account/switch" 67 77 hx-vals='{"did": "{{ .Did }}"}' 68 78 hx-swap="none" 69 - class="{{$linkStyle}} w-full text-left pl-3" 79 + class="flex items-center gap-2 w-full py-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-left" 70 80 > 71 - <img src="{{ tinyAvatar .Did }}" alt="" class="rounded-full size-6 flex-shrink-0 border border-gray-300 dark:border-gray-700" /> 72 - <span class="truncate flex-1">{{ .Did | resolve }}</span> 81 + <img src="{{ tinyAvatar .Did }}" alt="" class="rounded-full h-6 w-6 flex-shrink-0 border border-gray-300 dark:border-gray-700" /> 82 + <span class="text-sm truncate flex-1">{{ .Did | resolve }}</span> 73 83 </button> 74 - {{ end }} 84 + {{ end }} 85 + </div> 75 86 {{ end }} 76 87 77 - <a href="/login?mode=add_account" class="{{$linkStyle}} pl-3"> 78 - <div class="size-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> 79 - {{ i "plus" "size-3" }} 80 - </div> 81 - 82 - <div class="text-left flex-1 min-w-0 block truncate"> 83 - add account 84 - </div> 88 + <a href="/login?mode=add_account" class="flex items-center gap-2 py-1 text-sm"> 89 + {{ i "plus" "w-4 h-4 flex-shrink-0" }} 90 + <span>Add another account</span> 85 91 </a> 86 92 87 - <div class="border-t border-gray-200 dark:border-gray-700"> 88 - <a href="/{{ $active }}" class="{{$linkStyle}}"> 89 - {{ i "user" "size-4" }} 90 - profile 91 - </a> 92 - <a href="/{{ $active }}?tab=repos" class="{{$linkStyle}}"> 93 - {{ i "book-marked" "size-4" }} 94 - repositories 95 - </a> 96 - <a href="/{{ $active }}?tab=strings" class="{{$linkStyle}}"> 97 - {{ i "line-squiggle" "size-4" }} 98 - strings 99 - </a> 100 - <a href="/settings" class="{{$linkStyle}}"> 101 - {{ i "cog" "size-4" }} 102 - settings 103 - </a> 93 + <div class="pt-2 mt-2 border-t border-gray-200 dark:border-gray-700 space-y-1"> 94 + <a href="/{{ $active }}" class="block py-1 text-sm">profile</a> 95 + <a href="/{{ $active }}?tab=repos" class="block py-1 text-sm">repositories</a> 96 + <a href="/{{ $active }}?tab=strings" class="block py-1 text-sm">strings</a> 97 + <a href="/settings" class="block py-1 text-sm">settings</a> 104 98 <a href="#" 105 99 hx-post="/logout" 106 100 hx-swap="none" 107 - class="{{$linkStyle}} text-red-400 hover:text-red-400 hover:bg-red-100 dark:hover:bg-red-700/20 pb-2"> 108 - {{ i "log-out" "size-4" }} 101 + class="block py-1 text-sm text-red-400 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"> 109 102 logout 110 103 </a> 111 104 </div>
+4 -38
appview/pages/templates/repo/fragments/diff.html
··· 4 4 #filesToggle:checked ~ div label[for="filesToggle"] .hide-text { display: inline; } 5 5 #filesToggle:not(:checked) ~ div label[for="filesToggle"] .hide-text { display: none; } 6 6 #filesToggle:checked ~ div div#files { width: fit-content; max-width: 15vw; margin-right: 1rem; } 7 - #filesToggle:not(:checked) ~ div div#files { width: 0; display: none; margin-right: 0; } 7 + #filesToggle:not(:checked) ~ div div#files { width: 0; display: hidden; margin-right: 0; } 8 8 </style> 9 9 10 10 {{ template "diffTopbar" . }} ··· 14 14 {{ define "diffTopbar" }} 15 15 {{ $diff := index . 0 }} 16 16 {{ $opts := index . 1 }} 17 - {{ $root := "" }} 18 - {{ if gt (len .) 2 }} 19 - {{ $root = index . 2 }} 20 - {{ end }} 21 17 22 18 {{ block "filesCheckbox" $ }} {{ end }} 23 19 {{ block "subsCheckbox" $ }} {{ end }} 24 20 25 21 <!-- top bar --> 26 - <div class="sticky top-0 z-30 bg-slate-100 dark:bg-gray-900 flex items-center gap-2 col-span-full h-12 p-2 {{ if $root }}mt-4{{ end }}"> 22 + <div class="sticky top-0 z-30 bg-slate-100 dark:bg-gray-900 flex items-center gap-2 col-span-full h-12 p-2"> 27 23 <!-- left panel toggle --> 28 24 {{ template "filesToggle" . }} 29 25 ··· 31 27 {{ $stat := $diff.Stats }} 32 28 {{ $count := len $diff.ChangedFiles }} 33 29 {{ template "repo/fragments/diffStatPill" $stat }} 34 - <span class="text-xs text-gray-600 dark:text-gray-400 hidden md:inline-flex">{{ $count }} changed file{{ if ne $count 1 }}s{{ end }}</span> 35 - 36 - {{ if $root }} 37 - {{ if $root.IsInterdiff }} 38 - <!-- interdiff indicator --> 39 - <div class="flex items-center gap-2 before:content-['|'] before:text-gray-300 dark:before:text-gray-600 before:mr-2"> 40 - <span class="text-xs text-gray-600 dark:text-gray-400 uppercase tracking-wide">Interdiff</span> 41 - <a 42 - href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ sub $root.ActiveRound 1 }}" 43 - class="px-2 py-0.5 bg-white dark:bg-gray-700 rounded font-mono text-xs hover:bg-gray-50 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-600" 44 - > 45 - #{{ sub $root.ActiveRound 1 }} 46 - </a> 47 - <span class="text-gray-400 text-xs">โ†’</span> 48 - <a 49 - href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ $root.ActiveRound }}" 50 - class="px-2 py-0.5 bg-white dark:bg-gray-700 rounded font-mono text-xs hover:bg-gray-50 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-600" 51 - > 52 - #{{ $root.ActiveRound }} 53 - </a> 54 - </div> 55 - {{ else if ne $root.ActiveRound nil }} 56 - <!-- diff round indicator --> 57 - <div class="flex items-center gap-2 before:content-['|'] before:text-gray-300 dark:before:text-gray-600 before:mr-2"> 58 - <span class="text-xs text-gray-600 dark:text-gray-400 uppercase tracking-wide">Diff</span> 59 - <span class="px-2 py-0.5 bg-white dark:bg-gray-700 rounded font-mono text-xs border border-gray-300 dark:border-gray-600"> 60 - <span class="hidden md:inline">round </span>#{{ $root.ActiveRound }} 61 - </span> 62 - </div> 63 - {{ end }} 64 - {{ end }} 30 + {{ $count }} changed file{{ if ne $count 1 }}s{{ end }} 65 31 66 32 <!-- spacer --> 67 33 <div class="flex-grow"></div> ··· 171 137 {{ end }} 172 138 173 139 {{ define "collapseToggle" }} 174 - <label 140 + <label 175 141 title="Expand/Collapse diffs" 176 142 for="collapseToggle" 177 143 class="btn font-normal normal-case p-2"
+3 -3
appview/pages/templates/repo/fragments/splitDiff.html
··· 1 1 {{ define "repo/fragments/splitDiff" }} 2 2 {{ $name := .Id }} 3 - {{- $lineNrStyle := "min-w-[3.5rem] flex-shrink-0 select-none text-right bg-white dark:bg-gray-800 group-target/line:bg-yellow-200/30 group-target/line:dark:bg-yellow-600/30" -}} 4 - {{- $linkStyle := "text-gray-400 dark:text-gray-500 hover:underline group-target/line:text-black group-target/line:dark:text-white" -}} 3 + {{- $lineNrStyle := "min-w-[3.5rem] flex-shrink-0 select-none text-right bg-white dark:bg-gray-800" -}} 4 + {{- $linkStyle := "text-gray-400 dark:text-gray-500 hover:underline" -}} 5 5 {{- $lineNrSepStyle := "pr-2 border-r border-gray-200 dark:border-gray-700" -}} 6 - {{- $containerStyle := "inline-flex w-full items-center target:bg-yellow-200/50 target:dark:bg-yellow-700/50 scroll-mt-48 group/line" -}} 6 + {{- $containerStyle := "inline-flex w-full items-center target:bg-yellow-200 target:dark:bg-yellow-700 scroll-mt-48" -}} 7 7 {{- $emptyStyle := "bg-gray-200/30 dark:bg-gray-700/30" -}} 8 8 {{- $addStyle := "bg-green-100 dark:bg-green-800/30 text-green-700 dark:text-green-400" -}} 9 9 {{- $delStyle := "bg-red-100 dark:bg-red-800/30 text-red-700 dark:text-red-400 " -}}
+3 -3
appview/pages/templates/repo/fragments/unifiedDiff.html
··· 3 3 <div class="overflow-x-auto font-mono leading-normal"><div class="overflow-x-auto"><div class="inline-flex flex-col min-w-full">{{- range .TextFragments -}}<span class="block bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 select-none text-center">&middot;&middot;&middot;</span> 4 4 {{- $oldStart := .OldPosition -}} 5 5 {{- $newStart := .NewPosition -}} 6 - {{- $lineNrStyle := "min-w-[3.5rem] flex-shrink-0 select-none text-right bg-white dark:bg-gray-800 group-target/line:bg-yellow-200/30 group-target/line:dark:bg-yellow-600/30" -}} 7 - {{- $linkStyle := "text-gray-400 dark:text-gray-500 hover:underline group-target/line:text-black group-target/line:dark:text-white" -}} 6 + {{- $lineNrStyle := "min-w-[3.5rem] flex-shrink-0 select-none text-right bg-white dark:bg-gray-800 target:bg-yellow-200 target:dark:bg-yellow-600" -}} 7 + {{- $linkStyle := "text-gray-400 dark:text-gray-500 hover:underline" -}} 8 8 {{- $lineNrSepStyle1 := "" -}} 9 9 {{- $lineNrSepStyle2 := "pr-2 border-r border-gray-200 dark:border-gray-700" -}} 10 - {{- $containerStyle := "inline-flex w-full items-center target:bg-yellow-200/30 target:dark:bg-yellow-700/30 scroll-mt-48 group/line" -}} 10 + {{- $containerStyle := "inline-flex w-full items-center target:bg-yellow-200 target:dark:bg-yellow-700 scroll-mt-48" -}} 11 11 {{- $addStyle := "bg-green-100 dark:bg-green-800/30 text-green-700 dark:text-green-400 " -}} 12 12 {{- $delStyle := "bg-red-100 dark:bg-red-800/30 text-red-700 dark:text-red-400 " -}} 13 13 {{- $ctxStyle := "bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400" -}}
+1 -1
appview/pages/templates/repo/issues/issues.html
··· 80 80 "Page" .Page 81 81 "TotalCount" .IssueCount 82 82 "BasePath" (printf "/%s/issues" .RepoInfo.FullName) 83 - "QueryParams" (queryParams "state" $state "q" .FilterQuery) 83 + "QueryParams" (printf "state=%s&q=%s" $state .FilterQuery) 84 84 ) }} 85 85 {{ end }} 86 86 {{ end }}
+1 -1
appview/pages/templates/repo/pulls/fragments/pullNewComment.html
··· 12 12 > 13 13 <textarea 14 14 name="body" 15 - class="w-full p-2 rounded border" 15 + class="w-full p-2 rounded border border-gray-200" 16 16 rows=8 17 17 placeholder="Add to the discussion..."></textarea 18 18 >
+70 -106
appview/pages/templates/repo/pulls/pull.html
··· 8 8 9 9 {{ define "mainLayout" }} 10 10 <div class="px-1 flex-grow flex flex-col gap-4"> 11 - <div class="max-w-full md:max-w-screen-lg mx-auto"> 11 + <div class="max-w-screen-lg mx-auto"> 12 12 {{ block "contentLayout" . }} 13 13 {{ block "content" . }}{{ end }} 14 14 {{ end }} ··· 22 22 <script> 23 23 (function() { 24 24 const details = document.getElementById('bottomSheet'); 25 - const backdrop = document.getElementById('bottomSheetBackdrop'); 26 25 const isDesktop = () => window.matchMedia('(min-width: 768px)').matches; 27 26 28 - // function to update backdrop 29 - const updateBackdrop = () => { 30 - if (backdrop) { 31 - if (details.open && !isDesktop()) { 32 - backdrop.classList.remove('opacity-0', 'pointer-events-none'); 33 - backdrop.classList.add('opacity-100', 'pointer-events-auto'); 34 - } else { 35 - backdrop.classList.remove('opacity-100', 'pointer-events-auto'); 36 - backdrop.classList.add('opacity-0', 'pointer-events-none'); 37 - } 38 - } 39 - }; 40 - 41 27 // close on mobile initially 42 28 if (!isDesktop()) { 43 29 details.open = false; 44 30 } 45 - updateBackdrop(); // initialize backdrop 46 31 47 32 // prevent closing on desktop 48 33 details.addEventListener('toggle', function(e) { 49 34 if (isDesktop() && !this.open) { 50 35 this.open = true; 51 36 } 52 - updateBackdrop(); 53 37 }); 54 38 55 39 const mediaQuery = window.matchMedia('(min-width: 768px)'); ··· 61 45 // switched to mobile - close 62 46 details.open = false; 63 47 } 64 - updateBackdrop(); 65 48 }); 66 - 67 - // close when clicking backdrop 68 - if (backdrop) { 69 - backdrop.addEventListener('click', () => { 70 - if (!isDesktop()) { 71 - details.open = false; 72 - } 73 - }); 74 - } 75 49 })(); 76 50 </script> 77 51 {{ end }} 78 52 79 53 {{ define "repoContentLayout" }} 80 - <div class="grid grid-cols-1 md:grid-cols-10 gap-4 w-full"> 54 + <div class="grid grid-cols-1 md:grid-cols-10 gap-4"> 81 55 <section class="bg-white col-span-1 md:col-span-8 dark:bg-gray-800 p-6 rounded relative w-full mx-auto dark:text-white h-full flex-shrink"> 82 56 {{ block "repoContent" . }}{{ end }} 83 57 </section> ··· 117 91 <div class="flex col-span-full"> 118 92 <!-- left panel --> 119 93 <div id="files" class="w-0 hidden md:block overflow-hidden sticky top-12 max-h-screen overflow-y-auto pb-12"> 120 - <section class="overflow-x-auto text-sm px-6 py-2 border-b border-x border-gray-200 dark:border-gray-700 w-full mx-auto min-h-full rounded-b rounded-t-none bg-white dark:bg-gray-800 drop-shadow-sm"> 94 + <section class="overflow-x-auto text-sm px-6 py-2 border border-gray-200 dark:border-gray-700 w-full mx-auto min-h-full rounded bg-white dark:bg-gray-800 drop-shadow-sm"> 121 95 {{ template "repo/fragments/fileTree" $diff.FileTree }} 122 96 </section> 123 97 </div> ··· 135 109 {{ define "subsPanel" }} 136 110 {{ $root := index . 2 }} 137 111 {{ $pull := $root.Pull }} 112 + 138 113 <!-- backdrop overlay - only visible on mobile when open --> 139 - <div id="bottomSheetBackdrop" class="fixed inset-0 bg-black/50 md:hidden opacity-0 pointer-events-none transition-opacity duration-300 z-40"></div> 114 + <div class=" 115 + fixed inset-0 bg-black/50 z-50 md:hidden opacity-0 116 + pointer-events-none transition-opacity duration-300 117 + has-[~#subs_details[open]]:opacity-100 has-[~#subs_details[open]]:pointer-events-auto"> 118 + </div> 140 119 <!-- right panel - bottom sheet on mobile, side panel on desktop --> 141 120 <div id="subs" class="fixed bottom-0 left-0 right-0 z-50 w-full md:static md:z-auto md:max-h-screen md:sticky md:top-12 overflow-hidden"> 142 - <details open id="bottomSheet" class="rounded-t-2xl md:rounded-t drop-shadow-lg md:drop-shadow-none group/panel"> 121 + <details open id="bottomSheet" class="group rounded-t-2xl md:rounded-t-sm drop-shadow-lg md:drop-shadow-none"> 143 122 <summary class=" 144 123 flex gap-4 items-center justify-between 145 - rounded-t-2xl md:rounded-t cursor-pointer list-none p-4 md:h-12 124 + rounded-t-2xl md:rounded-t-sm cursor-pointer list-none p-4 md:h-12 146 125 text-white md:text-black md:dark:text-white 147 - bg-green-600 dark:bg-green-700 126 + bg-green-600 dark:bg-green-600 148 127 md:bg-white md:dark:bg-gray-800 149 128 drop-shadow-sm 150 - border-t md:border-x md:border-t-0 border-gray-200 dark:border-gray-700"> 151 - <h2 class="">Comments</h2> 129 + md:border-b md:border-x border-gray-200 dark:border-gray-700"> 130 + <h2 class="">Review Panel </h2> 152 131 {{ template "subsPanelSummary" $ }} 153 132 </summary> 154 133 <div class="max-h-[85vh] md:max-h-[calc(100vh-3rem-3rem)] w-full flex flex-col-reverse gap-4 overflow-y-auto bg-slate-100 dark:bg-gray-900 md:bg-transparent"> ··· 163 142 {{ $pull := $root.Pull }} 164 143 {{ $latest := $pull.LastRoundNumber }} 165 144 <div class="flex items-center gap-2 text-sm"> 145 + {{ if $root.IsInterdiff }} 146 + <span> 147 + viewing interdiff of 148 + <span class="font-mono">#{{ $root.ActiveRound }}</span> 149 + and 150 + <span class="font-mono">#{{ sub $root.ActiveRound 1 }}</span> 151 + </span> 152 + {{ else }} 153 + <span> 154 + viewing round 155 + <span class="font-mono">#{{ $root.ActiveRound }}</span> 156 + </span> 157 + {{ if ne $root.ActiveRound $latest }} 158 + <span>(outdated)</span> 159 + <span class="before:content-['ยท']"></span> 160 + <a class="underline" href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ $latest }}?{{ safeUrl $root.DiffOpts.Encode }}"> 161 + view latest 162 + </a> 163 + {{ end }} 164 + {{ end }} 166 165 <span class="md:hidden inline"> 167 - <span class="inline group-open/panel:hidden">{{ i "chevron-up" "size-4" }}</span> 168 - <span class="hidden group-open/panel:inline">{{ i "chevron-down" "size-4" }}</span> 166 + <span class="inline group-open:hidden">{{ i "chevron-up" "size-4" }}</span> 167 + <span class="hidden group-open:inline">{{ i "chevron-down" "size-4" }}</span> 169 168 </span> 170 169 </div> 171 170 {{ end }} ··· 177 176 {{ define "subsToggle" }} 178 177 <style> 179 178 /* Mobile: full width */ 180 - #subsToggle:checked ~ div div#subs { 179 + #subsToggle:checked ~ div div#subs { 181 180 width: 100%; 182 181 margin-left: 0; 183 182 } ··· 187 186 188 187 /* Desktop: 25vw with left margin */ 189 188 @media (min-width: 768px) { 190 - #subsToggle:checked ~ div div#subs { 189 + #subsToggle:checked ~ div div#subs { 191 190 width: 25vw; 192 191 margin-left: 1rem; 193 192 } ··· 208 207 209 208 {{ define "submissions" }} 210 209 {{ $lastIdx := sub (len .Pull.Submissions) 1 }} 211 - {{ if not .LoggedInUser }} 212 - {{ template "loginPrompt" $ }} 213 - {{ end }} 214 210 {{ range $ridx, $item := reverse .Pull.Submissions }} 215 211 {{ $idx := sub $lastIdx $ridx }} 216 212 {{ template "submission" (list $item $idx $lastIdx $) }} ··· 222 218 {{ $idx := index . 1 }} 223 219 {{ $lastIdx := index . 2 }} 224 220 {{ $root := index . 3 }} 225 - <div class="{{ if eq $item.RoundNumber 0 }}rounded-b border-t-0{{ else }}rounded{{ end }} border border-gray-200 dark:border-gray-700 w-full shadow-sm bg-gray-50 dark:bg-gray-900"> 221 + <div class="rounded border border-gray-200 dark:border-gray-700 w-full shadow-sm bg-gray-50 dark:bg-gray-800/50"> 226 222 {{ template "submissionHeader" $ }} 227 223 {{ template "submissionComments" $ }} 224 + 225 + {{ if eq $lastIdx $item.RoundNumber }} 226 + {{ block "mergeStatus" $root }} {{ end }} 227 + {{ block "resubmitStatus" $root }} {{ end }} 228 + {{ end }} 229 + 230 + {{ if $root.LoggedInUser }} 231 + {{ template "repo/pulls/fragments/pullActions" 232 + (dict 233 + "LoggedInUser" $root.LoggedInUser 234 + "Pull" $root.Pull 235 + "RepoInfo" $root.RepoInfo 236 + "RoundNumber" $item.RoundNumber 237 + "MergeCheck" $root.MergeCheck 238 + "ResubmitCheck" $root.ResubmitCheck 239 + "BranchDeleteStatus" $root.BranchDeleteStatus 240 + "Stack" $root.Stack) }} 241 + {{ else }} 242 + {{ template "loginPrompt" $ }} 243 + {{ end }} 228 244 </div> 229 245 {{ end }} 230 246 ··· 233 249 {{ $lastIdx := index . 2 }} 234 250 {{ $root := index . 3 }} 235 251 {{ $round := $item.RoundNumber }} 236 - <div class=" 237 - {{ if eq $round 0 }}rounded-b{{ else }}rounded{{ end }} 238 - px-6 py-4 pr-2 pt-2 239 - {{ if eq $root.ActiveRound $round }} 240 - bg-blue-100 dark:bg-blue-900 border-b border-blue-200 dark:border-blue-700 241 - {{ else }} 242 - bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 243 - {{ end }} 244 - flex gap-2 sticky top-0 z-20"> 252 + <div class="rounded px-6 py-4 pr-2 pt-2 bg-white dark:bg-gray-800 flex gap-2 sticky top-0 z-20 border-b border-gray-200 dark:border-gray-700"> 245 253 <!-- left column: just profile picture --> 246 254 <div class="flex-shrink-0 pt-2"> 247 255 <img ··· 269 277 {{ $round := $item.RoundNumber }} 270 278 <div class="flex gap-2 items-center justify-between mb-1"> 271 279 <span class="inline-flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 pt-2"> 272 - {{ resolve $root.Pull.OwnerDid }} submitted 273 - <span class="px-2 py-0.5 {{ if eq $root.ActiveRound $round }}text-white bg-blue-600 dark:bg-blue-500 border-blue-700 dark:border-blue-600{{ else }}text-black dark:text-white bg-gray-100 dark:bg-gray-700 border-gray-300 dark:border-gray-600{{ end }} rounded font-mono text-xs border"> 274 - #{{ $round }} 275 - </span> 280 + {{ resolve $root.Pull.OwnerDid }} submitted v{{ $round }} 276 281 <span class="select-none before:content-['\00B7']"></span> 277 282 <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500" href="#round-#{{ $round }}"> 278 - {{ template "repo/fragments/shortTime" $item.Created }} 283 + {{ template "repo/fragments/shortTimeAgo" $item.Created }} 279 284 </a> 280 285 </span> 281 286 <div class="flex gap-2 items-center"> 282 287 {{ if ne $root.ActiveRound $round }} 283 288 <a class="btn-flat flex items-center gap-2 no-underline hover:no-underline text-sm" 284 - href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ $round }}?{{ safeUrl $root.DiffOpts.Encode }}#round-#{{ $round }}"> 289 + href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ $round }}?{{ safeUrl $root.DiffOpts.Encode }}"> 285 290 {{ i "diff" "w-4 h-4" }} 286 291 diff 287 292 </a> ··· 492 497 493 498 {{ define "submissionComments" }} 494 499 {{ $item := index . 0 }} 495 - {{ $idx := index . 1 }} 496 - {{ $lastIdx := index . 2 }} 497 - {{ $root := index . 3 }} 498 - {{ $c := len $item.Comments }} 499 - <details class="relative ml-10 group/comments" open> 500 - <summary class="cursor-pointer list-none"> 501 - <div class="hidden group-open/comments:block absolute -left-8 top-0 bottom-0 w-16 transition-colors flex items-center justify-center group/border z-4"> 502 - <div class="absolute left-1/2 -translate-x-1/2 top-0 bottom-0 w-0.5 group-open/comments:bg-gray-200 dark:group-open/comments:bg-gray-700 group-hover/border:bg-gray-400 dark:group-hover/border:bg-gray-500 transition-colors"> </div> 503 - </div> 504 - <div class="group-open/comments:hidden block relative group/summary py-4"> 505 - <div class="absolute -left-8 top-0 bottom-0 w-16 transition-colors flex items-center justify-center z-4"> 506 - <div class="absolute left-1/2 -translate-x-1/2 h-1/3 top-0 bottom-0 w-0.5 bg-gray-200 dark:bg-gray-700 group-hover/summary:bg-gray-400 dark:group-hover/summary:bg-gray-500 transition-colors"></div> 507 - </div> 508 - <span class="text-gray-500 dark:text-gray-400 text-sm group-hover/summary:text-gray-600 dark:group-hover/summary:text-gray-300 transition-colors flex items-center gap-2 -ml-2 relative"> 509 - {{ i "circle-plus" "size-4 z-5" }} 510 - expand {{ $c }} comment{{ if ne $c 1 }}s{{ end }} 511 - </span> 512 - </div> 513 - </summary> 514 - <div> 515 - {{ range $item.Comments }} 516 - {{ template "submissionComment" . }} 517 - {{ end }} 518 - </div> 519 - 520 - <div class="relative -ml-10"> 521 - {{ if eq $lastIdx $item.RoundNumber }} 522 - {{ block "mergeStatus" $root }} {{ end }} 523 - {{ block "resubmitStatus" $root }} {{ end }} 524 - {{ end }} 525 - </div> 526 - <div class="relative -ml-10 bg-gray-50 dark:bg-gray-900"> 527 - {{ if $root.LoggedInUser }} 528 - {{ template "repo/pulls/fragments/pullActions" 529 - (dict 530 - "LoggedInUser" $root.LoggedInUser 531 - "Pull" $root.Pull 532 - "RepoInfo" $root.RepoInfo 533 - "RoundNumber" $item.RoundNumber 534 - "MergeCheck" $root.MergeCheck 535 - "ResubmitCheck" $root.ResubmitCheck 536 - "BranchDeleteStatus" $root.BranchDeleteStatus 537 - "Stack" $root.Stack) }} 538 - {{ end }} 539 - </div> 540 - </details> 500 + <div class="relative ml-10 border-l-2 border-gray-200 dark:border-gray-700"> 501 + {{ range $item.Comments }} 502 + {{ template "submissionComment" . }} 503 + {{ end }} 504 + </div> 541 505 {{ end }} 542 506 543 507 {{ define "submissionComment" }} 544 508 <div id="comment-{{.ID}}" class="flex gap-2 -ml-4 py-4 w-full mx-auto"> 545 509 <!-- left column: profile picture --> 546 - <div class="flex-shrink-0 h-fit relative"> 510 + <div class="flex-shrink-0"> 547 511 <img 548 512 src="{{ tinyAvatar .OwnerDid }}" 549 513 alt="" 550 - class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900 z-5" 514 + class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900" 551 515 /> 552 516 </div> 553 517 <!-- right column: name and body in two rows -->
+1 -1
appview/pages/templates/repo/pulls/pulls.html
··· 166 166 "Page" .Page 167 167 "TotalCount" .PullCount 168 168 "BasePath" (printf "/%s/pulls" .RepoInfo.FullName) 169 - "QueryParams" (queryParams "state" .FilteringBy.String "q" .FilterQuery) 169 + "QueryParams" (printf "state=%s&q=%s" .FilteringBy.String .FilterQuery) 170 170 ) }} 171 171 {{ end }} 172 172 {{ end }}
+16 -24
appview/pages/templates/strings/string.html
··· 10 10 11 11 {{ define "content" }} 12 12 {{ $ownerId := resolve .Owner.DID.String }} 13 - <section id="string-header" class="mb-2 py-2 px-4 dark:text-white"> 14 - <div class="text-lg flex flex-col sm:flex-row items-start gap-4 justify-between"> 15 - <!-- left items --> 16 - <div class="flex flex-col gap-2"> 17 - <!-- string owner / string name --> 18 - <div class="flex items-center gap-2 flex-wrap"> 19 - {{ template "user/fragments/picHandleLink" .Owner.DID.String }} 20 - <span class="select-none">/</span> 21 - <a href="/strings/{{ $ownerId }}/{{ .String.Rkey }}" class="font-bold">{{ .String.Filename }}</a> 22 - </div> 23 - 24 - <span class="flex flex-wrap items-center gap-x-4 gap-y-2 text-sm text-gray-600 dark:text-gray-300"> 25 - {{ if .String.Description }} 26 - {{ .String.Description }} 27 - {{ else }} 28 - <span class="italic">this string has no description</span> 29 - {{ end }} 30 - </span> 13 + <section id="string-header" class="mb-4 py-2 px-6 dark:text-white"> 14 + <div class="text-lg flex items-center justify-between"> 15 + <div> 16 + <a href="/strings/{{ $ownerId }}">{{ $ownerId }}</a> 17 + <span class="select-none">/</span> 18 + <a href="/strings/{{ $ownerId }}/{{ .String.Rkey }}" class="font-bold">{{ .String.Filename }}</a> 31 19 </div> 32 - 33 - <div class="w-full sm:w-fit grid grid-cols-3 gap-2 z-auto"> 20 + <div class="flex gap-2 items-stretch text-base"> 34 21 {{ if and .LoggedInUser (eq .LoggedInUser.Did .String.Did) }} 35 - <a class="btn text-sm no-underline hover:no-underline flex items-center gap-2 group" 22 + <a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group" 36 23 hx-boost="true" 37 24 href="/strings/{{ .String.Did }}/{{ .String.Rkey }}/edit"> 38 - {{ i "pencil" "w-4 h-4" }} 25 + {{ i "pencil" "size-4" }} 39 26 <span class="hidden md:inline">edit</span> 40 27 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 41 28 </a> 42 29 <button 43 - class="btn text-sm text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 flex items-center gap-2 group" 30 + class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group p-2" 44 31 title="Delete string" 45 32 hx-delete="/strings/{{ .String.Did }}/{{ .String.Rkey }}/" 46 33 hx-swap="none" 47 34 hx-confirm="Are you sure you want to delete the string `{{ .String.Filename }}`?" 48 35 > 49 - {{ i "trash-2" "w-4 h-4" }} 36 + {{ i "trash-2" "size-4" }} 50 37 <span class="hidden md:inline">delete</span> 51 38 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 52 39 </button> ··· 57 44 "StarCount" .StarCount) }} 58 45 </div> 59 46 </div> 47 + <span> 48 + {{ with .String.Description }} 49 + {{ . }} 50 + {{ end }} 51 + </span> 60 52 </section> 61 53 <section class="bg-white dark:bg-gray-800 px-6 py-4 rounded relative w-full dark:text-white"> 62 54 <div class="flex flex-col md:flex-row md:justify-between md:items-center text-gray-500 dark:text-gray-400 text-sm md:text-base pb-2 mb-3 text-base border-b border-gray-200 dark:border-gray-700">
+1 -1
appview/pulls/opengraph.go
··· 199 199 currentX += commentTextWidth + 40 200 200 201 201 // Draw files changed 202 - err = statusStatsArea.DrawLucideIcon("file-diff", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor) 202 + err = statusStatsArea.DrawLucideIcon("static/icons/file-diff", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor) 203 203 if err != nil { 204 204 log.Printf("failed to draw file diff icon: %v", err) 205 205 }
+1 -1
appview/pulls/pulls.go
··· 228 228 reactionMap, err := db.GetReactionMap(s.db, 20, pull.AtUri()) 229 229 if err != nil { 230 230 log.Println("failed to get pull reactions") 231 + s.pages.Notice(w, "pulls", "Failed to load pull. Try again later.") 231 232 } 232 233 233 234 userReactions := map[models.ReactionKind]bool{} ··· 1874 1875 record := pull.AsRecord() 1875 1876 record.PatchBlob = blob.Blob 1876 1877 record.CreatedAt = time.Now().Format(time.RFC3339) 1877 - record.Source.Sha = newSourceRev 1878 1878 1879 1879 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1880 1880 Collection: tangled.RepoPullNSID,
+19 -64
appview/repo/archive.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 - "io" 6 5 "net/http" 7 6 "net/url" 8 7 "strings" 9 8 9 + "tangled.org/core/api/tangled" 10 + xrpcclient "tangled.org/core/appview/xrpcclient" 11 + 12 + indigoxrpc "github.com/bluesky-social/indigo/xrpc" 10 13 "github.com/go-chi/chi/v5" 14 + "github.com/go-git/go-git/v5/plumbing" 11 15 ) 12 16 13 17 func (rp *Repo) DownloadArchive(w http.ResponseWriter, r *http.Request) { ··· 25 29 scheme = "https" 26 30 } 27 31 host := fmt.Sprintf("%s://%s", scheme, f.Knot) 32 + xrpcc := &indigoxrpc.Client{ 33 + Host: host, 34 + } 28 35 didSlashRepo := f.DidSlashRepo() 29 - 30 - // build the xrpc url 31 - u, err := url.Parse(host) 32 - if err != nil { 33 - l.Error("failed to parse host URL", "err", err) 36 + archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", ref, didSlashRepo) 37 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 38 + l.Error("failed to call XRPC repo.archive", "err", xrpcerr) 34 39 rp.pages.Error503(w) 35 40 return 36 41 } 37 - 38 - u.Path = "/xrpc/sh.tangled.repo.archive" 39 - query := url.Values{} 40 - query.Set("format", "tar.gz") 41 - query.Set("prefix", r.URL.Query().Get("prefix")) 42 - query.Set("ref", ref) 43 - query.Set("repo", didSlashRepo) 44 - u.RawQuery = query.Encode() 45 - 46 - xrpcURL := u.String() 47 - 48 - // make the get request 49 - resp, err := http.Get(xrpcURL) 50 - if err != nil { 51 - l.Error("failed to call XRPC repo.archive", "err", err) 52 - rp.pages.Error503(w) 53 - return 54 - } 55 - 56 - // pass through headers from upstream response 57 - if contentDisposition := resp.Header.Get("Content-Disposition"); contentDisposition != "" { 58 - w.Header().Set("Content-Disposition", contentDisposition) 59 - } 60 - if contentType := resp.Header.Get("Content-Type"); contentType != "" { 61 - w.Header().Set("Content-Type", contentType) 62 - } 63 - if contentLength := resp.Header.Get("Content-Length"); contentLength != "" { 64 - w.Header().Set("Content-Length", contentLength) 65 - } 66 - if link := resp.Header.Get("Link"); link != "" { 67 - if resolvedRef, err := extractImmutableLink(link); err == nil { 68 - newLink := fmt.Sprintf("<%s/%s/archive/%s.tar.gz>; rel=\"immutable\"", 69 - rp.config.Core.AppviewHost, f.DidSlashRepo(), resolvedRef) 70 - w.Header().Set("Link", newLink) 71 - } 72 - } 73 - 74 - // stream the archive data directly 75 - if _, err := io.Copy(w, resp.Body); err != nil { 76 - l.Error("failed to write response", "err", err) 77 - } 78 - } 79 - 80 - func extractImmutableLink(linkHeader string) (string, error) { 81 - trimmed := strings.TrimPrefix(linkHeader, "<") 82 - trimmed = strings.TrimSuffix(trimmed, ">; rel=\"immutable\"") 83 - 84 - parsedLink, err := url.Parse(trimmed) 85 - if err != nil { 86 - return "", err 87 - } 88 - 89 - resolvedRef := parsedLink.Query().Get("ref") 90 - if resolvedRef == "" { 91 - return "", fmt.Errorf("no ref found in link header") 92 - } 93 - 94 - return resolvedRef, nil 42 + // Set headers for file download, just pass along whatever the knot specifies 43 + safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-") 44 + filename := fmt.Sprintf("%s-%s.tar.gz", f.Name, safeRefFilename) 45 + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) 46 + w.Header().Set("Content-Type", "application/gzip") 47 + w.Header().Set("Content-Length", fmt.Sprintf("%d", len(archiveBytes))) 48 + // Write the archive data directly 49 + w.Write(archiveBytes) 95 50 }
+1 -1
appview/state/knotstream.go
··· 122 122 if ce == nil { 123 123 continue 124 124 } 125 - if ce.Email == ke.Address || ce.Email == record.CommitterDid { 125 + if ce.Email == ke.Address { 126 126 count += int(ce.Count) 127 127 } 128 128 }
+11
appview/state/profile.go
··· 162 162 l.Error("failed to create timeline", "err", err) 163 163 } 164 164 165 + // populate commit counts in the timeline, using the punchcard 166 + now := time.Now() 167 + for _, p := range profile.Punchcard.Punches { 168 + years := now.Year() - p.Date.Year() 169 + months := int(now.Month() - p.Date.Month()) 170 + monthsAgo := years*12 + months 171 + if monthsAgo >= 0 && monthsAgo < len(timeline.ByMonth) { 172 + timeline.ByMonth[monthsAgo].Commits += p.Count 173 + } 174 + } 175 + 165 176 s.pages.ProfileOverview(w, pages.ProfileOverviewParams{ 166 177 LoggedInUser: s.oauth.GetMultiAccountUser(r), 167 178 Card: profile,
+10 -23
cmd/dolly/main.go
··· 2 2 3 3 import ( 4 4 "bytes" 5 - _ "embed" 6 5 "flag" 7 6 "fmt" 8 7 "image" ··· 17 16 "github.com/srwiley/oksvg" 18 17 "github.com/srwiley/rasterx" 19 18 "golang.org/x/image/draw" 19 + "tangled.org/core/appview/pages" 20 20 "tangled.org/core/ico" 21 21 ) 22 22 23 23 func main() { 24 24 var ( 25 - size string 26 - fillColor string 27 - output string 28 - templatePath string 25 + size string 26 + fillColor string 27 + output string 29 28 ) 30 29 31 - flag.StringVar(&templatePath, "template", "", "Path to dolly go-html template") 32 30 flag.StringVar(&size, "size", "512x512", "Output size in format WIDTHxHEIGHT (e.g., 512x512)") 33 31 flag.StringVar(&fillColor, "color", "#000000", "Fill color in hex format (e.g., #FF5733)") 34 32 flag.StringVar(&output, "output", "dolly.svg", "Output file path (format detected from extension: .svg, .png, or .ico)") 35 33 flag.Parse() 36 - 37 - if templatePath == "" { 38 - fmt.Fprintf(os.Stderr, "Empty template path") 39 - os.Exit(1) 40 - } 41 34 42 35 width, height, err := parseSize(size) 43 36 if err != nil { ··· 59 52 os.Exit(1) 60 53 } 61 54 62 - tpl, err := os.ReadFile(templatePath) 63 - if err != nil { 64 - fmt.Fprintf(os.Stderr, "Failed to read template from path %s: %v\n", templatePath, err) 65 - os.Exit(1) 66 - } 67 - 68 - svgData, err := dolly(string(tpl), fillColor) 55 + svgData, err := dolly(fillColor) 69 56 if err != nil { 70 57 fmt.Fprintf(os.Stderr, "Error generating SVG: %v\n", err) 71 58 os.Exit(1) ··· 97 84 fmt.Printf("Successfully generated %s (%dx%d)\n", output, width, height) 98 85 } 99 86 100 - func dolly(tplString, hexColor string) ([]byte, error) { 101 - tpl, err := template.New("dolly").Parse(tplString) 87 + func dolly(hexColor string) ([]byte, error) { 88 + tpl, err := template.New("dolly"). 89 + ParseFS(pages.Files, "templates/fragments/dolly/logo.html") 102 90 if err != nil { 103 91 return nil, err 104 92 } 105 93 106 94 var svgData bytes.Buffer 107 - if err := tpl.ExecuteTemplate(&svgData, "fragments/dolly/logo", map[string]any{ 108 - "FillColor": hexColor, 109 - "Classes": "", 95 + if err := tpl.ExecuteTemplate(&svgData, "fragments/dolly/logo", pages.DollyParams{ 96 + FillColor: hexColor, 110 97 }); err != nil { 111 98 return nil, err 112 99 }
+3 -36
docs/DOCS.md
··· 375 375 KNOT_SERVER_LISTEN_ADDR=127.0.0.1:5555 376 376 ``` 377 377 378 - If you run a Linux distribution that uses systemd, you can 379 - use the provided service file to run the server. Copy 380 - [`knotserver.service`](https://tangled.org/tangled.org/core/blob/master/systemd/knotserver.service) 378 + If you run a Linux distribution that uses systemd, you can use the provided 379 + service file to run the server. Copy 380 + [`knotserver.service`](/systemd/knotserver.service) 381 381 to `/etc/systemd/system/`. Then, run: 382 382 383 383 ``` ··· 692 692 NODE_ENV: "production" 693 693 MY_ENV_VAR: "MY_ENV_VALUE" 694 694 ``` 695 - 696 - By default, the following environment variables set: 697 - 698 - - `CI` - Always set to `true` to indicate a CI environment 699 - - `TANGLED_PIPELINE_ID` - The AT URI of the current pipeline 700 - - `TANGLED_REPO_KNOT` - The repository's knot hostname 701 - - `TANGLED_REPO_DID` - The DID of the repository owner 702 - - `TANGLED_REPO_NAME` - The name of the repository 703 - - `TANGLED_REPO_DEFAULT_BRANCH` - The default branch of the 704 - repository 705 - - `TANGLED_REPO_URL` - The full URL to the repository 706 - 707 - These variables are only available when the pipeline is 708 - triggered by a push: 709 - 710 - - `TANGLED_REF` - The full git reference (e.g., 711 - `refs/heads/main` or `refs/tags/v1.0.0`) 712 - - `TANGLED_REF_NAME` - The short name of the reference 713 - (e.g., `main` or `v1.0.0`) 714 - - `TANGLED_REF_TYPE` - The type of reference, either 715 - `branch` or `tag` 716 - - `TANGLED_SHA` - The commit SHA that triggered the pipeline 717 - - `TANGLED_COMMIT_SHA` - Alias for `TANGLED_SHA` 718 - 719 - These variables are only available when the pipeline is 720 - triggered by a pull request: 721 - 722 - - `TANGLED_PR_SOURCE_BRANCH` - The source branch of the pull 723 - request 724 - - `TANGLED_PR_TARGET_BRANCH` - The target branch of the pull 725 - request 726 - - `TANGLED_PR_SOURCE_SHA` - The commit SHA of the source 727 - branch 728 695 729 696 ### Steps 730 697
+1 -1
input.css
··· 96 96 @apply border border-gray-400 block rounded bg-gray-50 focus:ring-black p-3 dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:focus:ring-gray-400; 97 97 } 98 98 textarea { 99 - @apply border border-gray-400 block rounded bg-gray-50 focus:outline-none focus:ring-1 focus:ring-black p-3 dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:focus:ring-gray-400; 99 + @apply border border-gray-400 block rounded bg-gray-50 focus:ring-black p-3 dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:focus:ring-gray-400; 100 100 } 101 101 details summary::-webkit-details-marker { 102 102 display: none;
-4
knotserver/git/git.go
··· 76 76 return &g, nil 77 77 } 78 78 79 - func (g *GitRepo) Hash() plumbing.Hash { 80 - return g.h 81 - } 82 - 83 79 // re-open a repository and update references 84 80 func (g *GitRepo) Refresh() error { 85 81 refreshed, err := PlainOpen(g.path)
-35
knotserver/xrpc/repo_archive.go
··· 4 4 "compress/gzip" 5 5 "fmt" 6 6 "net/http" 7 - "net/url" 8 7 "strings" 9 8 10 9 "github.com/go-git/go-git/v5/plumbing" 11 10 12 - "tangled.org/core/api/tangled" 13 11 "tangled.org/core/knotserver/git" 14 12 xrpcerr "tangled.org/core/xrpc/errors" 15 13 ) ··· 49 47 repoParts := strings.Split(repo, "/") 50 48 repoName := repoParts[len(repoParts)-1] 51 49 52 - immutableLink, err := x.buildImmutableLink(repo, format, gr.Hash().String(), prefix) 53 - if err != nil { 54 - x.Logger.Error( 55 - "failed to build immutable link", 56 - "err", err.Error(), 57 - "repo", repo, 58 - "format", format, 59 - "ref", gr.Hash().String(), 60 - "prefix", prefix, 61 - ) 62 - } 63 - 64 50 safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-") 65 51 66 52 var archivePrefix string ··· 73 59 filename := fmt.Sprintf("%s-%s.tar.gz", repoName, safeRefFilename) 74 60 w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) 75 61 w.Header().Set("Content-Type", "application/gzip") 76 - w.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"immutable\"", immutableLink)) 77 62 78 63 gw := gzip.NewWriter(w) 79 64 defer gw.Close() ··· 94 79 return 95 80 } 96 81 } 97 - 98 - func (x *Xrpc) buildImmutableLink(repo string, format string, ref string, prefix string) (string, error) { 99 - scheme := "https" 100 - if x.Config.Server.Dev { 101 - scheme = "http" 102 - } 103 - 104 - u, err := url.Parse(scheme + "://" + x.Config.Server.Hostname + "/xrpc/" + tangled.RepoArchiveNSID) 105 - if err != nil { 106 - return "", err 107 - } 108 - 109 - params := url.Values{} 110 - params.Set("repo", repo) 111 - params.Set("format", format) 112 - params.Set("ref", ref) 113 - params.Set("prefix", prefix) 114 - 115 - return fmt.Sprintf("%s?%s", u.String(), params.Encode()), nil 116 - }
+18 -25
nix/pkgs/dolly.nix
··· 1 1 { 2 - lib, 3 2 buildGoApplication, 4 3 modules, 5 - writeShellScriptBin, 6 - }: let 7 - src = lib.fileset.toSource { 8 - root = ../..; 9 - fileset = lib.fileset.unions [ 10 - ../../go.mod 11 - ../../ico 12 - ../../cmd/dolly/main.go 13 - ../../appview/pages/templates/fragments/dolly/logo.html 14 - ]; 15 - }; 16 - dolly-unwrapped = buildGoApplication { 17 - pname = "dolly-unwrapped"; 18 - version = "0.1.0"; 19 - inherit src modules; 20 - doCheck = false; 21 - subPackages = ["cmd/dolly"]; 22 - }; 23 - in 24 - writeShellScriptBin "dolly" '' 25 - exec ${dolly-unwrapped}/bin/dolly \ 26 - -template ${src}/appview/pages/templates/fragments/dolly/logo.html \ 27 - "$@" 28 - '' 4 + src, 5 + }: 6 + buildGoApplication { 7 + pname = "dolly"; 8 + version = "0.1.0"; 9 + inherit src modules; 10 + 11 + # patch the static dir 12 + postUnpack = '' 13 + pushd source 14 + mkdir -p appview/pages/static 15 + touch appview/pages/static/x 16 + popd 17 + ''; 18 + 19 + doCheck = false; 20 + subPackages = ["cmd/dolly"]; 21 + }