~gitpocket/gitpocket

4

feat(index): link the importer from the dashboard

commit
2e56210536c1a3ca9a5f12e93181246dbc95baa9 browse tree
parent
b0461c8f8942977ade0020d163974ec804692297
author
obscurejames <354576047+obscurejames@users.noreply.github.com>
signature
Signed by: ~james SHA256:KlzJ0P0gvJcnOYpaxD5yCUw52ImHttTKRjVAEnnYnKg
date
2026-11-13 17:47 UTC

5 files changed, +42 −12

forge/flow_test.go modified

@@ -14,6 +14,7 @@ import (
1414 "path/filepath"
1515 "strings"
1616 "testing"
17+ "time"
1718 )
1819
1920 func testServer(t *testing.T) (*Server, http.Handler) {
@@ -289,9 +290,11 @@ func TestPins(t *testing.T) {
289290 }
290291 pin(ids[2], own)
291292 pin(ids[0], own)
293+ past := time.Now().Add(-time.Hour)
294+ os.Chtimes(filepath.Join(s.root, "repos", "alice", "c.git"), past, past)
292295 body := get(h, "/~alice", "").Body.String()
293- if !strings.Contains(body, `href="/~alice/c"`) || strings.Index(body, `href="/~alice/c"`) > strings.Index(body, `href="/~alice/a"`) || strings.Contains(body, `href="/~alice/b"`) {
294- t.Error("profile does not show exactly the pinned repos in pin order")
296+ if !strings.Contains(body, `href="/~alice/c"`) || strings.Index(body, `href="/~alice/a"`) > strings.Index(body, `href="/~alice/c"`) || strings.Contains(body, `href="/~alice/b"`) {
297+ t.Error("profile does not show exactly the pinned repos, most recently updated first")
295298 }
296299 for _, id := range ids[1:] {
297300 pin(id, own)

forge/web.go modified

@@ -147,6 +147,7 @@ func newServer(store *Store, root, host, name string, sshUser string) *Server {
147147 s.policy.AllowElements("video", "source")
148148 s.policy.AllowAttrs("src", "type").OnElements("video", "source")
149149 s.policy.AllowAttrs("controls", "muted", "loop", "playsinline", "poster", "width", "height").OnElements("video")
150+ s.policy.AllowAttrs("align").Matching(regexp.MustCompile(`^(center|left|right)$`)).OnElements("p", "div", "h1", "h2", "h3", "h4")
150151 s.writeSigners()
151152 s.writeKeyring()
152153 s.installHooks()
@@ -588,10 +589,12 @@ func repoUpdated(dir string) time.Time {
588589 }
589590 consider(filepath.Join(dir, "packed-refs"))
590591 for _, sub := range []string{"heads", "tags"} {
591- entries, _ := os.ReadDir(filepath.Join(dir, "refs", sub))
592- for _, e := range entries {
593- consider(filepath.Join(dir, "refs", sub, e.Name()))
594- }
592+ filepath.WalkDir(filepath.Join(dir, "refs", sub), func(p string, d fs.DirEntry, err error) error {
593+ if err == nil && !d.IsDir() {
594+ consider(p)
595+ }
596+ return nil
597+ })
595598 }
596599 if latest.IsZero() {
597600 consider(dir)
@@ -1188,7 +1191,18 @@ func (s *Server) renderMarkdown(src []byte) template.HTML {
11881191 return s.renderMarkdownAt(src, "", "")
11891192 }
11901193
1191-var relLink = regexp.MustCompile(`(src|href)="([^"]+)"`)
1194+var (
1195+ relLink = regexp.MustCompile(`(src|href|poster)="([^"]+)"`)
1196+ posterLink = regexp.MustCompile(`<a href="([^"]+\.(?i:mp4|webm|ogv|mov))"[^>]*>\s*<img src="([^"]+)"[^>]*>\s*</a>`)
1197+ videoImg = regexp.MustCompile(`<img src="([^"]+\.(?i:mp4|webm|ogv|mov))"[^>]*>`)
1198+ videoLine = regexp.MustCompile(`<p>\s*<a href="([^"]+\.(?i:mp4|webm|ogv|mov))"[^>]*>[^<]*</a>\s*</p>`)
1199+)
1200+
1201+func embedVideos(out []byte) []byte {
1202+ out = posterLink.ReplaceAll(out, []byte(`<video controls playsinline preload="metadata" src="$1" poster="$2"></video>`))
1203+ out = videoImg.ReplaceAll(out, []byte(`<video controls playsinline preload="metadata" src="$1"></video>`))
1204+ return videoLine.ReplaceAll(out, []byte(`<p><video controls playsinline preload="metadata" src="$1"></video></p>`))
1205+}
11921206
11931207 func (s *Server) renderMarkdownAt(src []byte, rawBase, treeBase string) template.HTML {
11941208 src = displayMath.ReplaceAll(src, []byte("$1$$$$\n$2\n$$$$"))
@@ -1196,7 +1210,7 @@ func (s *Server) renderMarkdownAt(src []byte, rawBase, treeBase string) template
11961210 if err := s.markdown.Convert(src, &buf); err != nil {
11971211 return template.HTML("<pre>" + template.HTMLEscapeString(string(src)) + "</pre>")
11981212 }
1199- out := s.policy.SanitizeBytes(buf.Bytes())
1213+ out := embedVideos(s.policy.SanitizeBytes(buf.Bytes()))
12001214 if rawBase != "" {
12011215 out = relLink.ReplaceAllFunc(out, func(m []byte) []byte {
12021216 parts := relLink.FindSubmatch(m)
@@ -1206,7 +1220,7 @@ func (s *Server) renderMarkdownAt(src []byte, rawBase, treeBase string) template
12061220 }
12071221 target = strings.TrimPrefix(target, "./")
12081222 base := treeBase
1209- if attr == "src" {
1223+ if attr != "href" {
12101224 base = rawBase
12111225 }
12121226 return []byte(attr + `="` + html.EscapeString(escapePath(base)) + target + `"`)
@@ -1286,6 +1300,12 @@ func (s *Server) tree(w http.ResponseWriter, r *http.Request, repo Repo, spec st
12861300 case "blob":
12871301 size := g.Size(rp.Commit, filePath)
12881302 data["Size"] = size
1303+ if kind, ok := mediaTypes[strings.ToLower(filepath.Ext(filePath))]; ok && size <= maxMedia {
1304+ data["File"] = true
1305+ data["Media"], _, _ = strings.Cut(kind, "/")
1306+ s.render(w, r, "blob", page{Title: repo.Path() + "/" + filePath, Data: data})
1307+ return
1308+ }
12891309 var content []byte
12901310 if size <= maxRender {
12911311 if content, err = g.Blob(rp.Commit, filePath); err != nil {
@@ -1720,7 +1740,6 @@ func pinnedRepos(repos []Repo, pins map[int64]int) []Repo {
17201740 out = append(out, r)
17211741 }
17221742 }
1723- slices.SortFunc(out, func(a, b Repo) int { return pins[a.ID] - pins[b.ID] })
17241743 return out
17251744 }
17261745

web/static/style.css modified

@@ -333,6 +333,13 @@ h3.featured{font-size:1.3rem;font-weight:400;margin:2rem 0 .5rem}
333333 .landing .more .btn{min-width:20rem;text-align:center}
334334 @media (max-width:700px){.featuredgrid{grid-template-columns:1fr}.landing .more .btn{min-width:0;width:100%}}
335335 .readme video,.readme img{max-width:100%;height:auto}
336+.readme video{display:block;margin:.6rem auto;background:#000}
337+.readme [align=center]{text-align:center}
338+.readme [align=right]{text-align:right}
339+.media{display:flex;justify-content:center;padding:1rem 0}
340+.media video,.media img{max-width:100%;max-height:80vh;height:auto}
341+.media video{background:#000}
342+.media audio{width:min(100%,40rem)}
336343 .auth h3{white-space:nowrap}
337344 .pickaxe{display:flex;gap:.5rem;margin:0 0 1rem;flex-wrap:wrap}.pickaxe input{flex:1;min-width:14rem}
338345 .pickaxe select{font:inherit;color:var(--fg);background:var(--bg);border:1px solid var(--edge);padding:.3rem .4rem}

web/templates/blob.tmpl modified

@@ -1,7 +1,8 @@
11 {{define "content"}}
22 {{$p := .Data.Page}}
33 {{template "filebar" .Data}}
4-{{if .Data.Binary}}<p class="muted">Binary file, <a href="/{{$p.Repo.Path}}/raw/{{$p.Ref}}/{{.Data.Path}}">view raw</a>.</p>
4+{{if .Data.Media}}{{$src := printf "/%s/raw/%s/%s" $p.Repo.Path $p.Ref .Data.Path}}<div class="media">{{if eq .Data.Media "video"}}<video controls playsinline preload="metadata" src="{{$src}}"></video>{{else if eq .Data.Media "audio"}}<audio controls preload="metadata" src="{{$src}}"></audio>{{else}}<img src="{{$src}}" alt="{{.Data.Path}}">{{end}}</div>
5+{{else if .Data.Binary}}<p class="muted">Binary file, <a href="/{{$p.Repo.Path}}/raw/{{$p.Ref}}/{{.Data.Path}}">view raw</a>.</p>
56 {{else if .Data.Large}}<p class="muted">File too large to display, <a href="/{{$p.Repo.Path}}/raw/{{$p.Ref}}/{{.Data.Path}}">view raw</a>.</p>
67 {{else if .Data.Markdown}}<p class="muted small"><a href="?source=1">View source</a></p><article class="readme">{{.Data.Markdown}}</article>
78 {{else if .Data.Highlighted}}<div class="code">{{.Data.Highlighted}}</div>

web/templates/index.tmpl modified

@@ -1,7 +1,7 @@
11 {{define "content"}}
22 {{if .User}}
33 <div class="dash">
4-<section><div class="dashhead"><h3>Your repositories</h3><span><a class="btn" href="/orgs/new">New organization</a> <a class="btn primary" href="/create">New repository</a></span></div>
4+<section><div class="dashhead"><h3>Your repositories</h3><span><a class="btn" href="/orgs/new">New organization</a> <a class="btn" href="/import">Import repository</a> <a class="btn primary" href="/create">New repository</a></span></div>
55 {{if .Data.Repos}}{{template "repolist" .Data.Repos}}{{else}}<p class="muted">You have no repositories yet.</p>{{end}}
66 {{if .Data.Shared}}<h3>Shared with you</h3>{{template "repolist" .Data.Shared}}{{end}}</section>
77 {{else}}