Monorepo for Tangled tangled.org

appview/pages/markup: smart commit autolink renderer #1003

merged opened by boltless.me targeting master from sl/uupvwnkzxzom
Labels

None yet.

assignee

None yet.

Participants 3
AT URI
at://did:plc:xasnlahkri4ewmbuzly2rlc5/sh.tangled.repo.pull/3mcufej76qu22
+336 -216
Diff #4
+11
appview/config/config.go
··· 25 25 TmpAltAppPassword string `env:"ALT_APP_PASSWORD"` 26 26 } 27 27 28 + func (c *CoreConfig) UseTLS() bool { 29 + return !c.Dev 30 + } 31 + 32 + func (c *CoreConfig) BaseUrl() string { 33 + if c.UseTLS() { 34 + return "https://" + c.AppviewHost 35 + } 36 + return "http://" + c.AppviewHost 37 + } 38 + 28 39 type OAuthConfig struct { 29 40 ClientSecret string `env:"CLIENT_SECRET"` 30 41 ClientKid string `env:"CLIENT_KID"`
+1 -4
appview/models/repo.go
··· 130 130 131 131 // current display mode 132 132 ShowingRendered bool // currently in rendered mode 133 + ShowingText bool // currently in text/code mode 133 134 134 135 // content type flags 135 136 ContentType BlobContentType ··· 150 151 // no view available, only raw 151 152 return !(b.HasRenderedView || b.HasTextView) 152 153 } 153 - 154 - func (b BlobView) ShowingText() bool { 155 - return !b.ShowingRendered 156 - }
-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() {
+149
appview/pages/markup/extension/tangledlink.go
··· 1 + package extension 2 + 3 + import ( 4 + "net/url" 5 + "strings" 6 + 7 + "github.com/yuin/goldmark" 8 + "github.com/yuin/goldmark/ast" 9 + "github.com/yuin/goldmark/parser" 10 + "github.com/yuin/goldmark/renderer" 11 + "github.com/yuin/goldmark/text" 12 + "github.com/yuin/goldmark/util" 13 + ) 14 + 15 + // KindTangledLink is a NodeKind of the TangledLink node. 16 + var KindTangledLink = ast.NewNodeKind("TangledLink") 17 + 18 + type TangledLinkNode struct { 19 + ast.BaseInline 20 + Destination string 21 + Commit *TangledCommitLink 22 + // TODO: add more Tangled-link types 23 + } 24 + 25 + type TangledCommitLink struct { 26 + Sha string 27 + } 28 + 29 + var _ ast.Node = new(TangledLinkNode) 30 + 31 + // Dump implements [ast.Node]. 32 + func (n *TangledLinkNode) Dump(source []byte, level int) { 33 + ast.DumpHelper(n, source, level, nil, nil) 34 + } 35 + 36 + // Kind implements [ast.Node]. 37 + func (n *TangledLinkNode) Kind() ast.NodeKind { 38 + return KindTangledLink 39 + } 40 + 41 + type tangledLinkTransformer struct { 42 + host string 43 + } 44 + 45 + var _ parser.ASTTransformer = new(tangledLinkTransformer) 46 + 47 + // Transform implements [parser.ASTTransformer]. 48 + func (t *tangledLinkTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) { 49 + ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) { 50 + if !entering { 51 + return ast.WalkContinue, nil 52 + } 53 + 54 + var dest string 55 + 56 + switch n := n.(type) { 57 + case *ast.AutoLink: 58 + dest = string(n.URL(reader.Source())) 59 + case *ast.Link: 60 + // maybe..? not sure 61 + default: 62 + return ast.WalkContinue, nil 63 + } 64 + 65 + if sha := t.parseLinkCommitSha(dest); sha != "" { 66 + newLink := &TangledLinkNode{ 67 + Destination: dest, 68 + Commit: &TangledCommitLink{ 69 + Sha: sha, 70 + }, 71 + } 72 + n.Parent().ReplaceChild(n.Parent(), n, newLink) 73 + } 74 + 75 + return ast.WalkContinue, nil 76 + }) 77 + } 78 + 79 + func (t *tangledLinkTransformer) parseLinkCommitSha(raw string) string { 80 + u, err := url.Parse(raw) 81 + if err != nil || u.Host != t.host { 82 + return "" 83 + } 84 + 85 + // /{owner}/{repo}/commit/<sha> 86 + parts := strings.Split(strings.Trim(u.Path, "/"), "/") 87 + if len(parts) != 4 || parts[2] != "commit" { 88 + return "" 89 + } 90 + 91 + sha := parts[3] 92 + 93 + // basic sha validation 94 + if len(sha) < 7 { 95 + return "" 96 + } 97 + for _, c := range sha { 98 + if !strings.ContainsRune("0123456789abcdef", c) { 99 + return "" 100 + } 101 + } 102 + 103 + return sha[:8] 104 + } 105 + 106 + type tangledLinkRenderer struct{} 107 + 108 + var _ renderer.NodeRenderer = new(tangledLinkRenderer) 109 + 110 + // RegisterFuncs implements [renderer.NodeRenderer]. 111 + func (r *tangledLinkRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { 112 + reg.Register(KindTangledLink, r.renderTangledLink) 113 + } 114 + 115 + func (r *tangledLinkRenderer) renderTangledLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { 116 + link := node.(*TangledLinkNode) 117 + 118 + if link.Commit != nil { 119 + if entering { 120 + w.WriteString(`<a href="`) 121 + w.WriteString(link.Destination) 122 + w.WriteString(`"><code>`) 123 + w.WriteString(link.Commit.Sha) 124 + } else { 125 + w.WriteString(`</code></a>`) 126 + } 127 + } 128 + 129 + return ast.WalkContinue, nil 130 + } 131 + 132 + type tangledLinkExt struct { 133 + host string 134 + } 135 + 136 + var _ goldmark.Extender = new(tangledLinkExt) 137 + 138 + func (e *tangledLinkExt) Extend(m goldmark.Markdown) { 139 + m.Parser().AddOptions(parser.WithASTTransformers( 140 + util.Prioritized(&tangledLinkTransformer{host: e.host}, 500), 141 + )) 142 + m.Renderer().AddOptions(renderer.WithNodeRenderers( 143 + util.Prioritized(&tangledLinkRenderer{}, 500), 144 + )) 145 + } 146 + 147 + func NewTangledLinkExt(host string) goldmark.Extender { 148 + return &tangledLinkExt{host} 149 + }
+4 -2
appview/pages/markup/markdown.go
··· 46 46 CamoSecret string 47 47 repoinfo.RepoInfo 48 48 IsDev bool 49 + Hostname string 49 50 RendererType RendererType 50 51 Sanitizer Sanitizer 51 52 Files fs.FS 52 53 } 53 54 54 - func NewMarkdown() goldmark.Markdown { 55 + func NewMarkdown(hostname string) goldmark.Markdown { 55 56 md := goldmark.New( 56 57 goldmark.WithExtensions( 57 58 extension.GFM, ··· 67 68 ), 68 69 callout.CalloutExtention, 69 70 textension.AtExt, 71 + textension.NewTangledLinkExt(hostname), 70 72 emoji.Emoji, 71 73 ), 72 74 goldmark.WithParserOptions( ··· 78 80 } 79 81 80 82 func (rctx *RenderContext) RenderMarkdown(source string) string { 81 - return rctx.RenderMarkdownWith(source, NewMarkdown()) 83 + return rctx.RenderMarkdownWith(source, NewMarkdown(rctx.Hostname)) 82 84 } 83 85 84 86 func (rctx *RenderContext) RenderMarkdownWith(source string, md goldmark.Markdown) string {
+2 -2
appview/pages/markup/markdown_test.go
··· 50 50 51 51 for _, tt := range tests { 52 52 t.Run(tt.name, func(t *testing.T) { 53 - md := NewMarkdown() 53 + md := NewMarkdown("tangled.org") 54 54 55 55 var buf bytes.Buffer 56 56 if err := md.Convert([]byte(tt.markdown), &buf); err != nil { ··· 105 105 106 106 for _, tt := range tests { 107 107 t.Run(tt.name, func(t *testing.T) { 108 - md := NewMarkdown() 108 + md := NewMarkdown("tangled.org") 109 109 110 110 var buf bytes.Buffer 111 111 if err := md.Convert([]byte(tt.markdown), &buf); err != nil {
+4 -7
appview/pages/markup/reference_link.go
··· 18 18 // like issues, PRs, comments or even @-mentions 19 19 // This funciton doesn't actually check for the existence of records in the DB 20 20 // or the PDS; it merely returns a list of what are presumed to be references. 21 - func FindReferences(baseUrl string, source string) ([]string, []models.ReferenceLink) { 21 + func FindReferences(host string, source string) ([]string, []models.ReferenceLink) { 22 22 var ( 23 23 refLinkSet = make(map[models.ReferenceLink]struct{}) 24 24 mentionsSet = make(map[string]struct{}) 25 - md = NewMarkdown() 25 + md = NewMarkdown(host) 26 26 sourceBytes = []byte(source) 27 27 root = md.Parser().Parse(text.NewReader(sourceBytes)) 28 28 ) 29 - // trim url scheme. the SSL shouldn't matter 30 - baseUrl = strings.TrimPrefix(baseUrl, "https://") 31 - baseUrl = strings.TrimPrefix(baseUrl, "http://") 32 29 33 30 ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) { 34 31 if !entering { ··· 41 38 return ast.WalkSkipChildren, nil 42 39 case ast.KindLink: 43 40 dest := string(n.(*ast.Link).Destination) 44 - ref := parseTangledLink(baseUrl, dest) 41 + ref := parseTangledLink(host, dest) 45 42 if ref != nil { 46 43 refLinkSet[*ref] = struct{}{} 47 44 } ··· 50 47 an := n.(*ast.AutoLink) 51 48 if an.AutoLinkType == ast.AutoLinkURL { 52 49 dest := string(an.URL(sourceBytes)) 53 - ref := parseTangledLink(baseUrl, dest) 50 + ref := parseTangledLink(host, dest) 54 51 if ref != nil { 55 52 refLinkSet[*ref] = struct{}{} 56 53 }
+1
appview/pages/pages.go
··· 53 53 // initialized with safe defaults, can be overriden per use 54 54 rctx := &markup.RenderContext{ 55 55 IsDev: config.Core.Dev, 56 + Hostname: config.Core.AppviewHost, 56 57 CamoUrl: config.Camo.Host, 57 58 CamoSecret: config.Camo.SharedSecret, 58 59 Sanitizer: markup.NewSanitizer(),
+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/repo/blob.html
··· 35 35 36 36 {{ if .BlobView.ShowingText }} 37 37 <span class="select-none px-1 md:px-2 [&:before]:content-['·']"></span> 38 - <span>{{ .BlobView.Lines }} lines</span> 38 + <span>{{ .Lines }} lines</span> 39 39 {{ end }} 40 40 41 41 {{ if .BlobView.SizeHint }}
+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 -2
appview/pages/templates/repo/issues/fragments/issueCommentHeader.html
··· 1 1 {{ define "repo/issues/fragments/issueCommentHeader" }} 2 2 <div class="flex flex-wrap items-center gap-2 text-sm text-gray-500 dark:text-gray-400 "> 3 - {{ $handle := resolve .Comment.Did }} 4 - <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="/{{ $handle }}">{{ $handle }}</a> 3 + {{ resolve .Comment.Did }} 5 4 {{ template "hats" $ }} 6 5 <span class="before:content-['·']"></span> 7 6 {{ template "timestamp" . }}
+1 -1
appview/pages/templates/repo/issues/fragments/newComment.html
··· 12 12 <textarea 13 13 id="comment-textarea" 14 14 name="body" 15 - class="w-full p-2 rounded" 15 + class="w-full p-2 rounded border border-gray-200 dark:border-gray-700" 16 16 placeholder="Add to the discussion. Markdown is supported." 17 17 onkeyup="updateCommentForm()" 18 18 rows="5"
+1 -1
appview/pages/templates/repo/issues/fragments/replyIssueCommentPlaceholder.html
··· 8 8 /> 9 9 {{ end }} 10 10 <input 11 - class="w-full p-0 border-none focus:outline-none bg-transparent" 11 + class="w-full p-0 border-none focus:outline-none" 12 12 placeholder="Leave a reply..." 13 13 hx-get="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/comment/{{ .Comment.Id }}/reply" 14 14 hx-trigger="focus"
+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 >
+58 -111
appview/pages/templates/repo/pulls/pull.html
··· 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 - document.body.style.overflow = 'hidden'; 35 - } else { 36 - backdrop.classList.remove('opacity-100', 'pointer-events-auto'); 37 - backdrop.classList.add('opacity-0', 'pointer-events-none'); 38 - document.body.style.overflow = ''; 39 - } 40 - } 41 - }; 42 - 43 27 // close on mobile initially 44 28 if (!isDesktop()) { 45 29 details.open = false; 46 30 } 47 - updateBackdrop(); // initialize backdrop 48 31 49 32 // prevent closing on desktop 50 33 details.addEventListener('toggle', function(e) { 51 34 if (isDesktop() && !this.open) { 52 35 this.open = true; 53 36 } 54 - updateBackdrop(); 55 37 }); 56 38 57 39 const mediaQuery = window.matchMedia('(min-width: 768px)'); ··· 63 45 // switched to mobile - close 64 46 details.open = false; 65 47 } 66 - updateBackdrop(); 67 48 }); 68 - 69 - // close when clicking backdrop 70 - if (backdrop) { 71 - backdrop.addEventListener('click', () => { 72 - if (!isDesktop()) { 73 - details.open = false; 74 - } 75 - }); 76 - } 77 49 })(); 78 50 </script> 79 51 {{ end }} ··· 137 109 {{ define "subsPanel" }} 138 110 {{ $root := index . 2 }} 139 111 {{ $pull := $root.Pull }} 112 + 140 113 <!-- backdrop overlay - only visible on mobile when open --> 141 - <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> 142 119 <!-- right panel - bottom sheet on mobile, side panel on desktop --> 143 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"> 144 - <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 drop-shadow-lg md:drop-shadow-none"> 145 122 <summary class=" 146 123 flex gap-4 items-center justify-between 147 124 rounded-t-2xl md:rounded-t cursor-pointer list-none p-4 md:h-12 ··· 150 127 md:bg-white md:dark:bg-gray-800 151 128 drop-shadow-sm 152 129 border-t md:border-x md:border-t-0 border-gray-200 dark:border-gray-700"> 153 - <h2 class="">History</h2> 130 + <h2 class="">Submissions</h2> 154 131 {{ template "subsPanelSummary" $ }} 155 132 </summary> 156 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 140 {{ define "subsPanelSummary" }} 164 141 {{ $root := index . 2 }} 165 142 {{ $pull := $root.Pull }} 166 - {{ $rounds := len $pull.Submissions }} 167 - {{ $comments := $pull.TotalComments }} 143 + {{ $latest := $pull.LastRoundNumber }} 168 144 <div class="flex items-center gap-2 text-sm"> 169 - <span> 170 - {{ $rounds }} round{{ if ne $rounds 1 }}s{{ end }} 171 - </span> 172 - <span class="select-none before:content-['\00B7']"></span> 173 - <span> 174 - {{ $comments }} comment{{ if ne $comments 1 }}s{{ end }} 175 - </span> 176 - 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 + {{ if ne $root.ActiveRound $latest }} 154 + <span>(outdated)</span> 155 + <span class="before:content-['·']"></span> 156 + <a class="underline" href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ $latest }}?{{ safeUrl $root.DiffOpts.Encode }}"> 157 + view latest 158 + </a> 159 + {{ end }} 160 + {{ end }}--> 177 161 <span class="md:hidden inline"> 178 162 <span class="inline group-open:hidden">{{ i "chevron-up" "size-4" }}</span> 179 163 <span class="hidden group-open:inline">{{ i "chevron-down" "size-4" }}</span> ··· 233 217 {{ $idx := index . 1 }} 234 218 {{ $lastIdx := index . 2 }} 235 219 {{ $root := index . 3 }} 236 - {{ $round := $item.RoundNumber }} 237 - <div class=" 238 - w-full shadow-sm bg-gray-50 dark:bg-gray-900 border-2 border-t-0 239 - {{ if eq $round 0 }}rounded-b{{ else }}rounded{{ end }} 240 - {{ if eq $round $root.ActiveRound }} 241 - border-blue-200 dark:border-blue-700 242 - {{ else }} 243 - border-gray-200 dark:border-gray-700 244 - {{ end }} 245 - "> 220 + <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-800/50"> 246 221 {{ template "submissionHeader" $ }} 247 222 {{ template "submissionComments" $ }} 223 + 224 + {{ if eq $lastIdx $item.RoundNumber }} 225 + {{ block "mergeStatus" $root }} {{ end }} 226 + {{ block "resubmitStatus" $root }} {{ end }} 227 + {{ end }} 228 + 229 + {{ if $root.LoggedInUser }} 230 + {{ template "repo/pulls/fragments/pullActions" 231 + (dict 232 + "LoggedInUser" $root.LoggedInUser 233 + "Pull" $root.Pull 234 + "RepoInfo" $root.RepoInfo 235 + "RoundNumber" $item.RoundNumber 236 + "MergeCheck" $root.MergeCheck 237 + "ResubmitCheck" $root.ResubmitCheck 238 + "BranchDeleteStatus" $root.BranchDeleteStatus 239 + "Stack" $root.Stack) }} 240 + {{ end }} 248 241 </div> 249 242 {{ end }} 250 243 ··· 256 249 <div class=" 257 250 {{ if eq $round 0 }}rounded-b{{ else }}rounded{{ end }} 258 251 px-6 py-4 pr-2 pt-2 259 - bg-white dark:bg-gray-800 260 - {{ if eq $round $root.ActiveRound }} 261 - border-t-2 border-blue-200 dark:border-blue-700 252 + {{ if eq $root.ActiveRound $round }} 253 + bg-blue-100 dark:bg-blue-900 border-b border-blue-200 dark:border-blue-700 262 254 {{ else }} 263 - border-b-2 border-gray-200 dark:border-gray-700 255 + bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 264 256 {{ end }} 265 257 flex gap-2 sticky top-0 z-20"> 266 258 <!-- left column: just profile picture --> ··· 290 282 {{ $round := $item.RoundNumber }} 291 283 <div class="flex gap-2 items-center justify-between mb-1"> 292 284 <span class="inline-flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 pt-2"> 293 - {{ $handle := resolve $root.Pull.OwnerDid }} 294 - <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="/{{ $handle }}">{{ $handle }}</a> 295 - submitted 296 - <span class="px-2 py-0.5 text-black dark:text-white bg-gray-100 dark:bg-gray-700 border-gray-300 dark:border-gray-600 rounded font-mono text-xs border"> 285 + {{ resolve $root.Pull.OwnerDid }} submitted 286 + <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"> 297 287 #{{ $round }} 298 288 </span> 299 289 <span class="select-none before:content-['\00B7']"></span> ··· 515 505 516 506 {{ define "submissionComments" }} 517 507 {{ $item := index . 0 }} 518 - {{ $idx := index . 1 }} 519 - {{ $lastIdx := index . 2 }} 520 - {{ $root := index . 3 }} 521 - {{ $round := $item.RoundNumber }} 522 - {{ $c := len $item.Comments }} 523 - <details class="relative ml-10 group/comments" {{ if or (eq $c 0) (eq $root.ActiveRound $round) }}open{{ end }}> 524 - <summary class="cursor-pointer list-none"> 525 - <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"> 526 - <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> 527 - </div> 528 - <div class="group-open/comments:hidden block relative group/summary py-4"> 529 - <div class="absolute -left-8 top-0 bottom-0 w-16 transition-colors flex items-center justify-center z-4"> 530 - <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> 531 - </div> 532 - <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"> 533 - {{ i "circle-plus" "size-4 z-5" }} 534 - expand {{ $c }} comment{{ if ne $c 1 }}s{{ end }} 535 - </span> 536 - </div> 537 - </summary> 538 - <div> 539 - {{ range $item.Comments }} 540 - {{ template "submissionComment" . }} 541 - {{ end }} 542 - </div> 543 - 544 - <div class="relative -ml-10"> 545 - {{ if eq $lastIdx $item.RoundNumber }} 546 - {{ block "mergeStatus" $root }} {{ end }} 547 - {{ block "resubmitStatus" $root }} {{ end }} 548 - {{ end }} 549 - </div> 550 - <div class="relative -ml-10 bg-gray-50 dark:bg-gray-900"> 551 - {{ if $root.LoggedInUser }} 552 - {{ template "repo/pulls/fragments/pullActions" 553 - (dict 554 - "LoggedInUser" $root.LoggedInUser 555 - "Pull" $root.Pull 556 - "RepoInfo" $root.RepoInfo 557 - "RoundNumber" $item.RoundNumber 558 - "MergeCheck" $root.MergeCheck 559 - "ResubmitCheck" $root.ResubmitCheck 560 - "BranchDeleteStatus" $root.BranchDeleteStatus 561 - "Stack" $root.Stack) }} 562 - {{ end }} 563 - </div> 564 - </details> 508 + <div class="relative ml-10 border-l-2 border-gray-200 dark:border-gray-700"> 509 + {{ range $item.Comments }} 510 + {{ template "submissionComment" . }} 511 + {{ end }} 512 + </div> 565 513 {{ end }} 566 514 567 515 {{ define "submissionComment" }} 568 516 <div id="comment-{{.ID}}" class="flex gap-2 -ml-4 py-4 w-full mx-auto"> 569 517 <!-- left column: profile picture --> 570 - <div class="flex-shrink-0 h-fit relative"> 518 + <div class="flex-shrink-0"> 571 519 <img 572 520 src="{{ tinyAvatar .OwnerDid }}" 573 521 alt="" 574 - class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900 z-5" 522 + class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900" 575 523 /> 576 524 </div> 577 525 <!-- right column: name and body in two rows --> 578 526 <div class="flex-1 min-w-0"> 579 527 <!-- Row 1: Author and timestamp --> 580 528 <div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1"> 581 - {{ $handle := resolve .OwnerDid }} 582 - <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="/{{ $handle }}">{{ $handle }}</a> 529 + <span>{{ resolve .OwnerDid }}</span> 583 530 <span class="before:content-['·']"></span> 584 531 <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.ID}}"> 585 - {{ template "repo/fragments/shortTime" .Created }} 532 + {{ template "repo/fragments/time" .Created }} 586 533 </a> 587 534 </div> 588 535 <!-- Row 2: Body text -->
+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 }}
+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,
+1 -1
appview/repo/archive.go
··· 66 66 if link := resp.Header.Get("Link"); link != "" { 67 67 if resolvedRef, err := extractImmutableLink(link); err == nil { 68 68 newLink := fmt.Sprintf("<%s/%s/archive/%s.tar.gz>; rel=\"immutable\"", 69 - rp.config.Core.AppviewHost, f.DidSlashRepo(), resolvedRef) 69 + rp.config.Core.BaseUrl(), f.DidSlashRepo(), resolvedRef) 70 70 w.Header().Set("Link", newLink) 71 71 } 72 72 }
+2 -17
appview/repo/blob.go
··· 219 219 if resp.Content != nil { 220 220 bytes, _ := base64.StdEncoding.DecodeString(*resp.Content) 221 221 view.Contents = string(bytes) 222 - view.Lines = countLines(view.Contents) 222 + view.Lines = strings.Count(view.Contents, "\n") + 1 223 223 } 224 224 225 225 case ".mp4", ".webm", ".ogg", ".mov", ".avi": ··· 238 238 239 239 if resp.Content != nil { 240 240 view.Contents = *resp.Content 241 - view.Lines = countLines(view.Contents) 241 + view.Lines = strings.Count(view.Contents, "\n") + 1 242 242 } 243 243 244 244 // with text, we may be dealing with markdown ··· 291 291 } 292 292 return slices.Contains(textualTypes, mimeType) 293 293 } 294 - 295 - // TODO: dedup with strings 296 - func countLines(content string) int { 297 - if content == "" { 298 - return 0 299 - } 300 - 301 - count := strings.Count(content, "\n") 302 - 303 - if !strings.HasSuffix(content, "\n") { 304 - count++ 305 - } 306 - 307 - return count 308 - }
+4 -4
appview/repo/feed.go
··· 37 37 38 38 feed := &feeds.Feed{ 39 39 Title: fmt.Sprintf("activity feed for @%s", ownerSlashRepo), 40 - Link: &feeds.Link{Href: fmt.Sprintf("%s/%s", rp.config.Core.AppviewHost, ownerSlashRepo), Type: "text/html", Rel: "alternate"}, 40 + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s", rp.config.Core.BaseUrl(), ownerSlashRepo), Type: "text/html", Rel: "alternate"}, 41 41 Items: make([]*feeds.Item, 0), 42 42 Updated: time.UnixMilli(0), 43 43 } ··· 86 86 mainItem := &feeds.Item{ 87 87 Title: fmt.Sprintf("[PR #%d] %s", pull.PullId, pull.Title), 88 88 Description: description, 89 - Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d", rp.config.Core.AppviewHost, ownerSlashRepo, pull.PullId)}, 89 + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d", rp.config.Core.BaseUrl(), ownerSlashRepo, pull.PullId)}, 90 90 Created: pull.Created, 91 91 Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)}, 92 92 } ··· 100 100 roundItem := &feeds.Item{ 101 101 Title: fmt.Sprintf("[PR #%d] %s (round #%d)", pull.PullId, pull.Title, round.RoundNumber), 102 102 Description: fmt.Sprintf("@%s submitted changes (at round #%d) on PR #%d in @%s", owner.Handle, round.RoundNumber, pull.PullId, ownerSlashRepo), 103 - Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d/round/%d/", rp.config.Core.AppviewHost, ownerSlashRepo, pull.PullId, round.RoundNumber)}, 103 + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d/round/%d/", rp.config.Core.BaseUrl(), ownerSlashRepo, pull.PullId, round.RoundNumber)}, 104 104 Created: round.Created, 105 105 Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)}, 106 106 } ··· 124 124 return &feeds.Item{ 125 125 Title: fmt.Sprintf("[Issue #%d] %s", issue.IssueId, issue.Title), 126 126 Description: fmt.Sprintf("@%s %s issue #%d in @%s", owner.Handle, state, issue.IssueId, ownerSlashRepo), 127 - Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/issues/%d", rp.config.Core.AppviewHost, ownerSlashRepo, issue.IssueId)}, 127 + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/issues/%d", rp.config.Core.BaseUrl(), ownerSlashRepo, issue.IssueId)}, 128 128 Created: issue.Created, 129 129 Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)}, 130 130 }, nil
+7 -8
appview/settings/settings.go
··· 298 298 } 299 299 300 300 func (s *Settings) verifyUrl(did string, email string, code string) string { 301 - var appUrl string 302 - if s.Config.Core.Dev { 303 - appUrl = "http://" + s.Config.Core.ListenAddr 304 - } else { 305 - appUrl = s.Config.Core.AppviewHost 306 - } 307 - 308 - return fmt.Sprintf("%s/settings/emails/verify?did=%s&email=%s&code=%s", appUrl, url.QueryEscape(did), url.QueryEscape(email), url.QueryEscape(code)) 301 + return fmt.Sprintf( 302 + "%s/settings/emails/verify?did=%s&email=%s&code=%s", 303 + s.Config.Core.BaseUrl(), 304 + url.QueryEscape(did), 305 + url.QueryEscape(email), 306 + url.QueryEscape(code), 307 + ) 309 308 } 310 309 311 310 func (s *Settings) emailsVerify(w http.ResponseWriter, r *http.Request) {
+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 }
+4 -4
appview/state/profile.go
··· 415 415 416 416 feed := feeds.Feed{ 417 417 Title: fmt.Sprintf("%s's timeline", author.Name), 418 - Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s", s.config.Core.AppviewHost, id.Handle), Type: "text/html", Rel: "alternate"}, 418 + Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s", s.config.Core.BaseUrl(), id.Handle), Type: "text/html", Rel: "alternate"}, 419 419 Items: make([]*feeds.Item, 0), 420 420 Updated: time.UnixMilli(0), 421 421 Author: author, ··· 483 483 func (s *State) createPullRequestItem(pull *models.Pull, owner *identity.Identity, author *feeds.Author) *feeds.Item { 484 484 return &feeds.Item{ 485 485 Title: fmt.Sprintf("%s created pull request '%s' in @%s/%s", author.Name, pull.Title, owner.Handle, pull.Repo.Name), 486 - Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/pulls/%d", s.config.Core.AppviewHost, owner.Handle, pull.Repo.Name, pull.PullId), Type: "text/html", Rel: "alternate"}, 486 + Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/pulls/%d", s.config.Core.BaseUrl(), owner.Handle, pull.Repo.Name, pull.PullId), Type: "text/html", Rel: "alternate"}, 487 487 Created: pull.Created, 488 488 Author: author, 489 489 } ··· 492 492 func (s *State) createIssueItem(issue *models.Issue, owner *identity.Identity, author *feeds.Author) *feeds.Item { 493 493 return &feeds.Item{ 494 494 Title: fmt.Sprintf("%s created issue '%s' in @%s/%s", author.Name, issue.Title, owner.Handle, issue.Repo.Name), 495 - Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/issues/%d", s.config.Core.AppviewHost, owner.Handle, issue.Repo.Name, issue.IssueId), Type: "text/html", Rel: "alternate"}, 495 + Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/issues/%d", s.config.Core.BaseUrl(), owner.Handle, issue.Repo.Name, issue.IssueId), Type: "text/html", Rel: "alternate"}, 496 496 Created: issue.Created, 497 497 Author: author, 498 498 } ··· 512 512 513 513 return &feeds.Item{ 514 514 Title: title, 515 - Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s", s.config.Core.AppviewHost, author.Name[1:], repo.Repo.Name), Type: "text/html", Rel: "alternate"}, // Remove @ prefix 515 + Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s", s.config.Core.BaseUrl(), author.Name[1:], repo.Repo.Name), Type: "text/html", Rel: "alternate"}, // Remove @ prefix 516 516 Created: repo.Repo.Created, 517 517 Author: author, 518 518 }, nil
+3 -3
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 ```
+3 -3
go.mod
··· 5 5 require ( 6 6 github.com/Blank-Xu/sql-adapter v1.1.1 7 7 github.com/alecthomas/assert/v2 v2.11.0 8 - github.com/alecthomas/chroma/v2 v2.23.1 8 + github.com/alecthomas/chroma/v2 v2.15.0 9 9 github.com/avast/retry-go/v4 v4.6.1 10 10 github.com/blevesearch/bleve/v2 v2.5.3 11 11 github.com/bluekeyes/go-gitdiff v0.8.1 ··· 61 61 github.com/Microsoft/go-winio v0.6.2 // indirect 62 62 github.com/ProtonMail/go-crypto v1.3.0 // indirect 63 63 github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect 64 - github.com/alecthomas/repr v0.5.2 // indirect 64 + github.com/alecthomas/repr v0.4.0 // indirect 65 65 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect 66 66 github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 67 67 github.com/aymerick/douceur v0.2.0 // indirect ··· 224 224 225 225 replace github.com/bluekeyes/go-gitdiff => tangled.sh/oppi.li/go-gitdiff v0.8.2 226 226 227 - replace github.com/alecthomas/chroma/v2 => github.com/oppiliappan/chroma/v2 v2.24.2 227 + replace github.com/alecthomas/chroma/v2 => github.com/oppiliappan/chroma/v2 v2.19.0 228 228 229 229 // from bluesky-social/indigo 230 230 replace github.com/gocql/gocql => github.com/scylladb/gocql v1.14.4
+3 -4
go.sum
··· 13 13 github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0= 14 14 github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= 15 15 github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= 16 + github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= 16 17 github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 17 - github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= 18 - github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 19 18 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= 20 19 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= 21 20 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= ··· 413 412 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 414 413 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= 415 414 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= 416 - github.com/oppiliappan/chroma/v2 v2.24.2 h1:lHB9tWQxDoHa6sYEDdFep8SX6FPMmAF+ocGUffFwujE= 417 - github.com/oppiliappan/chroma/v2 v2.24.2/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= 415 + github.com/oppiliappan/chroma/v2 v2.19.0 h1:PN7/pb+6JRKCva30NPTtRJMlrOyzgpPpIroNzy4ekHU= 416 + github.com/oppiliappan/chroma/v2 v2.19.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk= 418 417 github.com/oppiliappan/go-git/v5 v5.17.0 h1:CuJnpcIDxr0oiNaSHMconovSWnowHznVDG+AhjGuSEo= 419 418 github.com/oppiliappan/go-git/v5 v5.17.0/go.mod h1:q/FE8C3SPMoRN7LoH9vRFiBzidAOBWJPS1CqVS8DN+w= 420 419 github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
+2 -2
input.css
··· 93 93 @apply block text-gray-900 text-sm font-bold py-2 uppercase dark:text-gray-100; 94 94 } 95 95 input { 96 - @apply p-3 border border-gray-100 block rounded bg-gray-50 focus:outline-none focus:ring-1 focus:ring-gray-200 p-3 dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:focus:ring-gray-400;; 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-100 block rounded bg-gray-50 focus:outline-none focus:ring-1 focus:ring-gray-200 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 -4
nix/gomod2nix.toml
··· 20 20 version = "v2.11.0" 21 21 hash = "sha256-tDJCDKZ0R4qNA7hgMKWrpDyogt1802LCJDBCExxdqaU=" 22 22 [mod."github.com/alecthomas/chroma/v2"] 23 - version = "v2.24.2" 24 - hash = "sha256-Xz4DLZpn98rwaLmNNztK3PJu9MVxDLSrhJI82ZzyFZo=" 23 + version = "v2.19.0" 24 + hash = "sha256-dxsu43a+PvHg2jYR0Tfys6a8x6IVR+9oCGAh+fvL3SM=" 25 25 replaced = "github.com/oppiliappan/chroma/v2" 26 26 [mod."github.com/alecthomas/repr"] 27 - version = "v0.5.2" 28 - hash = "sha256-PfIeyHh7xTbDN0g2otuDyUOQqbgS4KftVC1JKZ+6sdM=" 27 + version = "v0.4.0" 28 + hash = "sha256-CyAzMSTfLGHDtfGXi91y7XMVpPUDNOKjsznb+osl9dU=" 29 29 [mod."github.com/anmitsu/go-shlex"] 30 30 version = "v0.0.0-20200514113438-38f4b401e2be" 31 31 hash = "sha256-L3Ak4X2z7WXq7vMKuiHCOJ29nlpajUQ08Sfb9T0yP54="
+2 -2
nix/modules/appview.nix
··· 41 41 42 42 appviewHost = mkOption { 43 43 type = types.str; 44 - default = "https://tangled.org"; 45 - example = "https://example.com"; 44 + default = "tangled.org"; 45 + example = "example.com"; 46 46 description = "Public host URL for the appview instance"; 47 47 }; 48 48

History

5 rounds 11 comments
sign up or login to add to the discussion
2 commits
expand
appview/pages/markup: smart commit autolink renderer
appview: strip scheme from CoreConfig.AppviewHost
3/3 success
expand
expand 2 comments

@oppi.li makes sense. I completely forgot the indigo oauth behavior. I reverted the change.

lgtm, this bug is still present:

next, in appview/config/config.go, the default value for AppivewHost should be set to tangled.org

but should be a quick fix, i can apply that on master!

pull request successfully merged
2 commits
expand
appview/pages/markup: smart commit autolink renderer
appview: strip scheme from CoreConfig.AppviewHost
expand 3 comments

when using dev, the callback URL should always be http://127.0.0.1/oauth/callback (or http://localhost), regardless of what the AppviewHost is set to. the port is ignored iirc.

next, in appview/config/config.go, the default value for AppivewHost should be set to tangled.org (without the scheme).

i think BaseUrl is good naming choice. happy with that!

when using dev, the callback URL should always be http://127.0.0.1/oauth/callback (or http://localhost), regardless of what the AppviewHost is set to. the port is ignored iirc.

If someone set AppviewHost, I suspect they are testing on that host. I used to test on my own domain long ago. http://127.0.0.1:3000/oauth/callback is what was used in original code.

next, in appview/config/config.go, the default value for AppivewHost should be set to tangled.org

will do!

if you are using oauth.NewLocalhostConfig (as we do in dev), the redirect URL's host cannot be anything other than localhost or 127.0.0.1, if it is something else, you will get an error when performing the PAR request:

URL must use \"localhost\", \"127.0.0.1\" or \"[::1]\" as hostname at body.redirect_uri]"

when in dev, we should just ignore AppviewHost and use one of the predefined hosts. in the original code, the host is 127.0.0.1 which is one of the predefined hosts. the port is ignored anyway.

2 commits
expand
appview/pages/markup: smart commit autolink renderer
appview: strip scheme from CoreConfig.AppviewHost
3/3 success
expand
expand 5 comments
  • here, i think Url is a bit ambiguous to have on config, could use a better name here

rest of the changeset lgtm, will give this a test, thanks!

after some local testing, this seems to oauth, i am unable to login!

2026/01/21 06:25:06 WARN auth server request failed request=PAR statusCode=400 body="map[error:invalid_request error_description:Invalid authorization request: URL must use \"localhost\", \"127.0.0.1\" or \"[::1]\" as hostname at body.redirect_uri]"
2026/01/21 06:25:06 ERRO appview: failed to start auth handler=Login err="auth request failed: PAR request failed (HTTP 400): invalid_request"

@oppi.li I can't reproduce the oauth issue. Have you tried after setting TANGLED_APPVIEWHOST=127.0.0.1? you should remove the scheme now.

for Url() naming, would BaseUrl() be fine enough?

Also naming/consistency nit: tangledlink.gotangled_link.go? Keeping in line with reference_link.go. Rest looks OK!

@anirudh.fi I'm matching with extension/atlink.go. Doesn't reference_link.go violates the golang conventions? yes, I'm the one who wrote both... inconsistencies all over the place 🙈

1 commit
expand
appview/pages/markup: smart commit autolink renderer
3/3 success
expand
expand 1 comment

would be good to avoid hardcoding the appview host here!

1 commit
expand
appview/pages/markup: smart commit autolink renderer
1/3 failed, 2/3 success
expand
expand 0 comments