From 6b8ecf838125038442927842c30ef6fcad021365 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L de Mello" Date: Tue, 30 Sep 2025 21:35:12 -0300 Subject: [PATCH] feat(lored,user): "pinned" repositories feature on user profile overview --- routers/web/user/profile.go | 25 +++++++++++ templates/shared/repo/grid.tmpl | 64 ++++++++++++++++++++++++++++ templates/user/profile.tmpl | 7 ++- web_src/css/index.css | 1 + web_src/css/shared/grid-list.css | 73 ++++++++++++++++++++++++++++++++ 5 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 templates/shared/repo/grid.tmpl create mode 100644 web_src/css/shared/grid-list.css diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index dae2f90f1a..3a8812a1e1 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -229,6 +229,31 @@ func prepareUserProfileTabData(ctx *context.Context, profileDbRepo *repo_model.R } } + // HACK(contact@guz.one): "Pinned" repositories feature. + // + // Pinned user repositories is based with whether or not the owner starred their + // own repository. This method was choose so we don't have any incompatibility + // with upstream's database, since making something similar with GitHub's pinned + // repositories feature would need a new column or table to store said information. + // + // Maybe we could in the future properly implement this feature, if upstream wants + // this feature also, if not, it is not worth it. + repos, count, err = repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{ + ListOptions: db.ListOptions{ + PageSize: 6, + Page: 0, + }, + Actor: ctx.Doer, + OwnerID: ctx.ContextUser.ID, + OrderBy: db.SearchOrderBy(fmt.Sprintf("%s, %s", db.SearchOrderByStarsReverse, db.SearchOrderByRecentUpdated)), + Private: ctx.IsSigned, + StarredByID: ctx.ContextUser.ID, + }) + if err != nil { + ctx.ServerError("SearchRepository", err) + return + } + // prepare heatmap data if setting.Service.EnableUserHeatmap { data, err := activities_model.GetUserHeatmapDataByUser(ctx, ctx.ContextUser, ctx.Doer) diff --git a/templates/shared/repo/grid.tmpl b/templates/shared/repo/grid.tmpl new file mode 100644 index 0000000000..7ec47acf86 --- /dev/null +++ b/templates/shared/repo/grid.tmpl @@ -0,0 +1,64 @@ +
+ {{range .Repos}} +
+
+
+
+
+ {{if $.ShowRepoOwnerAvatar}} + {{ctx.AvatarUtils.Avatar .Owner 24}} + {{else}} + {{template "repo/icon" .}} + {{end}} +
+ {{if or (and $.ShowRepoOwnerOnList .Owner) (ne .OwnerID $.ContextUser.ID)}} + {{.Owner.Name}}/ + {{end}} + {{.Name}} + + {{if .IsArchived}} + {{ctx.Locale.Tr "repo.desc.archived"}} + {{end}} + {{if .IsPrivate}} + {{ctx.Locale.Tr "repo.desc.private"}} + {{else}} + {{if .Owner.Visibility.IsPrivate}} + {{ctx.Locale.Tr "repo.desc.internal"}} + {{end}} + {{end}} + {{if .IsTemplate}} + {{ctx.Locale.Tr "repo.desc.template"}} + {{end}} + {{if eq .ObjectFormatName "sha256"}} + {{ctx.Locale.Tr "repo.desc.sha256"}} + {{end}} + +
+ +
+ {{$description := .DescriptionHTML ctx}} + {{if $description}} +
{{$description}}
+ {{end}} + {{if .Topics}} +
+ {{range .Topics}} + {{if ne . ""}}{{.}}{{end}} + {{end}} +
+ {{end}} +
+
+ {{else}} +
+ {{ctx.Locale.Tr "search.no_results"}} +
+ {{end}} +
diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index 79bf1cafd0..2e145a81dd 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -27,7 +27,12 @@ {{template "repo/user_cards" .}} {{else if eq .TabName "overview"}} {{if .HasUserProfileReadme}} -
{{.ProfileReadmeContent}}
+
+ {{.ProfileReadmeContent}} +
+ {{end}} + {{if .Repos}} + {{template "shared/repo/grid" .}} {{end}} {{if (and (or .HeatmapData .Feeds) (not .ContextUser.KeepActivityPrivate))}}
diff --git a/web_src/css/index.css b/web_src/css/index.css index 291cd04b2b..0c3420089e 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -29,6 +29,7 @@ @import "./modules/flexcontainer.css"; @import "./shared/flex-list.css"; +@import "./shared/grid-list.css"; @import "./shared/milestone.css"; @import "./shared/repoorg.css"; @import "./shared/settings.css"; diff --git a/web_src/css/shared/grid-list.css b/web_src/css/shared/grid-list.css new file mode 100644 index 0000000000..a1255bfab5 --- /dev/null +++ b/web_src/css/shared/grid-list.css @@ -0,0 +1,73 @@ +.grid-list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); + gap: 1rem; +} + +.grid-item { + border-radius: 0.5rem; + padding: 1em; + display: grid; + gap: 8px; + align-items: flex-start; + border: 1px solid var(--color-secondary); + background: var(--color-box-body); + grid-template-rows: 1fr min-content; +} + +.grid-item .grid-item-main { + display: grid; + gap: 4px; +} + +.grid-item .grid-item-header { + display: flex; + gap: 0.25rem; + justify-content: space-between; + flex-wrap: wrap; +} + +.grid-item .grid-item-trailing { + display: flex; + gap: 0.5rem; + align-items: center; + flex-grow: 0; + flex-wrap: wrap; + justify-content: end; +} + +.grid-item .grid-item-title { + display: inline-flex; + flex-wrap: wrap; + align-items: center; + gap: 0.25rem; + max-width: 100%; + color: var(--color-text); + font-size: 16px; + font-weight: var(--font-weight-semibold); + word-break: break-word; + min-width: 0; +} + +.grid-item .grid-item-body { + font-size: 13px; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.25rem; + color: var(--color-text-light-2); + word-break: break-word; +} + +.grid-item .grid-item-footer { + grid-column: 1 / -1; +} + +.grid-item .grid-item-trailing { + display: flex; + gap: 0.5rem; + align-items: center; + flex-grow: 0; + flex-wrap: wrap; + justify-content: end; +}