From 51f11a9897e48d48b84edec0b15f0940d3597dbe Mon Sep 17 00:00:00 2001 From: jolheiser Date: Wed, 4 Jun 2025 10:51:03 -0500 Subject: [PATCH] permalink --- internal/git/git.go | 15 +++++++++++++++ internal/html/repo_file.go | 7 ++++++- internal/html/repo_file.js | 13 +++++++++++++ internal/http/repo.go | 10 ++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/internal/git/git.go b/internal/git/git.go index 72140d8..fcdb9e3 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -127,6 +127,21 @@ func (r Repo) Dir(ref, path string) ([]FileInfo, error) { return fis, nil } +// GetCommitFromRef returns the commit object for a given ref +func (r Repo) GetCommitFromRef(ref string) (*object.Commit, error) { + g, err := r.Git() + if err != nil { + return nil, err + } + + hash, err := g.ResolveRevision(plumbing.Revision(ref)) + if err != nil { + return nil, err + } + + return g.CommitObject(*hash) +} + // FileContent returns the content of a file in the git tree at a given ref/rev func (r Repo) FileContent(ref, file string) (string, error) { t, err := r.Tree(ref) diff --git a/internal/html/repo_file.go b/internal/html/repo_file.go index 6dd3cb1..45f78a5 100644 --- a/internal/html/repo_file.go +++ b/internal/html/repo_file.go @@ -2,6 +2,7 @@ package html import ( _ "embed" + "fmt" . "maragu.dev/gomponents" . "maragu.dev/gomponents/html" @@ -11,7 +12,9 @@ type RepoFileContext struct { BaseContext RepoHeaderComponentContext RepoBreadcrumbComponentContext - Code string + Code string + Commit string + Path string } //go:embed repo_file.js @@ -24,6 +27,8 @@ func RepoFileTemplate(rfc RepoFileContext) Node { repoBreadcrumbComponent(rfc.RepoBreadcrumbComponentContext), Text(" - "), A(Class("text-text underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href("?raw"), Text("raw")), + Text(" - "), + A(Class("text-text underline decoration-text/50 decoration-dashed hover:decoration-solid"), ID("permalink"), Href(fmt.Sprintf("/%s/tree/%s/%s", rfc.RepoBreadcrumbComponentContext.Repo, rfc.Commit, rfc.Path)), Text("permalink")), Div(Class("code relative"), Raw(rfc.Code), Button(ID("copy"), Class("absolute top-0 right-0 rounded bg-base hover:bg-surface0")), diff --git a/internal/html/repo_file.js b/internal/html/repo_file.js index 42d5a24..ebc4ad8 100644 --- a/internal/html/repo_file.js +++ b/internal/html/repo_file.js @@ -2,6 +2,7 @@ const lineRe = /#L(\d+)(?:-L(\d+))?/g const $lineLines = document.querySelectorAll(".chroma .lntable .lnt"); const $codeLines = document.querySelectorAll(".chroma .lntable .line"); const $copyButton = document.getElementById('copy'); +const $permalinkButton = document.getElementById('permalink'); const $copyIcon = "📋"; const $copiedIcon = "✅"; let $code = "" @@ -48,6 +49,18 @@ $copyButton.addEventListener("click", () => { }, 1000); }); +$permalinkButton.addEventListener("click", (event) => { + event.preventDefault(); + const url = $permalinkButton.getAttribute("href"); + navigator.clipboard.writeText(window.location.origin + url + location.hash); + + const originalText = $permalinkButton.innerText; + $permalinkButton.innerText = "copied!"; + setTimeout(() => { + $permalinkButton.innerText = originalText; + }, 1000); +}); + function activateLines(start, end) { if (end < start) end = start; for (let idx = start - 1; idx < end; idx++) { diff --git a/internal/http/repo.go b/internal/http/repo.go index 5382b7d..c47811a 100644 --- a/internal/http/repo.go +++ b/internal/http/repo.go @@ -92,11 +92,21 @@ func (rh repoHandler) repoFile(w http.ResponseWriter, r *http.Request, repo *git return httperr.Error(err) } + commit := ref + if len(ref) < 40 { + commitObj, err := repo.GetCommitFromRef(ref) + if err == nil { + commit = commitObj.Hash.String() + } + } + if err := html.RepoFileTemplate(html.RepoFileContext{ BaseContext: rh.baseContext(), RepoHeaderComponentContext: rh.repoHeaderContext(repo, r), RepoBreadcrumbComponentContext: rh.repoBreadcrumbContext(repo, r, path), Code: buf.String(), + Commit: commit, + Path: path, }).Render(w); err != nil { return httperr.Error(err) }