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}}
+
+
+
+ {{$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;
+}