From 508da0897f256786ed1b45b00f576d39b2dd753f Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Mar 2026 14:18:18 +0200 Subject: [PATCH 1/5] Add watch-sqlite target to Makefile for SQLite testing --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 824bfac10c..0c10f6cfbb 100644 --- a/Makefile +++ b/Makefile @@ -379,6 +379,10 @@ lint-json-fix: node_modules ## lint and fix json files watch: ## watch everything and continuously rebuild @bash tools/watch.sh +.PHONY: watch-sqlite +watch-sqlite: ## watch and use sqlite for testing + @TAGS="sqlite sqlite_unlock_notify" $(MAKE) watch + .PHONY: watch-frontend watch-frontend: node_modules ## start vite dev server for frontend NODE_ENV=development $(NODE_VARS) pnpm exec vite From 74c1dff948fc237639676d7c6463ef14d53113c9 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 1 Apr 2026 11:53:29 +0200 Subject: [PATCH 2/5] add avatar and iocn gallery to devtest --- modules/svg/svg.go | 11 ++++ routers/web/devtest/devtest.go | 33 ++++++++++++ templates/devtest/avatar.tmpl | 83 +++++++++++++++++++++++++++++ templates/devtest/icon-gallery.tmpl | 45 ++++++++++++++++ 4 files changed, 172 insertions(+) create mode 100644 templates/devtest/avatar.tmpl create mode 100644 templates/devtest/icon-gallery.tmpl diff --git a/modules/svg/svg.go b/modules/svg/svg.go index 234b1f8c13..917df9439a 100644 --- a/modules/svg/svg.go +++ b/modules/svg/svg.go @@ -7,6 +7,7 @@ import ( "fmt" "html/template" "path" + "sort" "strings" "sync" @@ -78,6 +79,16 @@ func MockIcon(icon string) func() { } } +// DiscoveredIconNames returns the sorted list of all discovered SVG icon names +func DiscoveredIconNames() []string { + names := make([]string, 0, len(svgIcons)) + for name := range svgIcons { + names = append(names, name) + } + sort.Strings(names) + return names +} + // RenderHTML renders icons - arguments icon name (string), size (int), class (string) func RenderHTML(icon string, others ...any) template.HTML { result, _ := renderHTML(icon, others...) diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go index 8283d3ad9d..7b1d2d1271 100644 --- a/routers/web/devtest/devtest.go +++ b/routers/web/devtest/devtest.go @@ -18,6 +18,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/badge" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/svg" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/context" @@ -180,6 +181,34 @@ func prepareMockDataRelativeTime(ctx *context.Context) { ctx.Data["TimeFuture1y"] = now.Add(366 * 24 * time.Hour) } +func prepareMockDataAvatar(ctx *context.Context) { + mockUsers, _ := db.Find[user_model.User](ctx, user_model.SearchUserOptions{ListOptions: db.ListOptions{PageSize: 1}}) + ctx.Data["MockUser"] = mockUsers[0] + ctx.Data["AvatarSizes"] = []int{16, 20, 24, 28, 32, 40, 48, 64, 100, 128} + ctx.Data["SampleEmails"] = []string{ + "alice@example.com", + "bob@example.org", + "charlie@test.io", + "devtest@gitea.io", + "noreply@example.com", + } +} + +func prepareMockDataIconGallery(ctx *context.Context) { + allNames := svg.DiscoveredIconNames() + grouped := map[string][]string{} + for _, name := range allNames { + prefix := "other" + if before, _, ok := strings.Cut(name, "-"); ok { + prefix = before + } + grouped[prefix] = append(grouped[prefix], name) + } + ctx.Data["IconGroups"] = grouped + ctx.Data["IconGroupOrder"] = []string{"octicon", "gitea", "fontawesome", "material", "other"} + ctx.Data["IconCount"] = len(allNames) +} + func prepareMockData(ctx *context.Context) { switch ctx.Req.URL.Path { case "/devtest/gitea-ui": @@ -190,6 +219,10 @@ func prepareMockData(ctx *context.Context) { prepareMockDataBadgeActionsSvg(ctx) case "/devtest/relative-time": prepareMockDataRelativeTime(ctx) + case "/devtest/avatar": + prepareMockDataAvatar(ctx) + case "/devtest/icon-gallery": + prepareMockDataIconGallery(ctx) } } diff --git a/templates/devtest/avatar.tmpl b/templates/devtest/avatar.tmpl new file mode 100644 index 0000000000..3c48675f63 --- /dev/null +++ b/templates/devtest/avatar.tmpl @@ -0,0 +1,83 @@ +{{template "devtest/devtest-header"}} +
+

Avatar

+ +

Sizes (via AvatarByEmail)

+

Using ctx.AvatarUtils.AvatarByEmail at various pixel sizes:

+
+ {{range $size := .AvatarSizes}} +
+ {{ctx.AvatarUtils.AvatarByEmail "devtest@example.com" "Dev Test" $size}} + {{$size}}px +
+ {{end}} +
+ +

User Avatars

+

Using ctx.AvatarUtils.Avatar with a real user and with nil (fallback):

+
+
+ {{ctx.AvatarUtils.Avatar .MockUser 28}} + User (28px) +
+
+ {{ctx.AvatarUtils.Avatar .MockUser 40}} + User (40px) +
+
+ {{ctx.AvatarUtils.Avatar .MockUser 100}} + User (100px) +
+
+ {{ctx.AvatarUtils.Avatar nil 28}} + nil fallback (28px) +
+
+ {{ctx.AvatarUtils.Avatar nil 40}} + nil fallback (40px) +
+
+ +

Custom CSS Classes

+
+
+ {{ctx.AvatarUtils.Avatar .MockUser 28 "ui avatar tw-align-middle"}} + default class +
+
+ {{ctx.AvatarUtils.Avatar .MockUser 28 "ui avatar tw-align-middle tw-rounded-full"}} + tw-rounded-full +
+
+ +

Inline with Text

+
+ {{ctx.AvatarUtils.Avatar .MockUser 20 "ui avatar tw-align-middle"}} {{.MockUser.Name}} opened this issue +
+
+ {{ctx.AvatarUtils.AvatarByEmail "user1@example.com" "User One" 16}} User One and {{ctx.AvatarUtils.AvatarByEmail "user2@example.com" "User Two" 16}} User Two +
+ +

Avatar with Link (typical pattern)

+ + +

Avatar Upload Cropper

+

The cropper requires index.js (global init). The HTML structure is shown below for reference:

+
+ {{template "shared/avatar_upload_crop" dict "LabelText" "Choose an avatar"}} +
+ +

Multiple Email Avatars (gravatar hashing)

+

Different emails produce different fallback avatars:

+
+ {{range $email := .SampleEmails}} +
+ {{ctx.AvatarUtils.AvatarByEmail $email $email 28}} + {{$email}} +
+ {{end}} +
+
+{{template "devtest/devtest-footer"}} diff --git a/templates/devtest/icon-gallery.tmpl b/templates/devtest/icon-gallery.tmpl new file mode 100644 index 0000000000..fd24bf7b34 --- /dev/null +++ b/templates/devtest/icon-gallery.tmpl @@ -0,0 +1,45 @@ +{{template "devtest/devtest-header"}} +
+

Icon Gallery

+

All {{.IconCount}} SVG icons available in templates.

+

+ + +

+ + + + {{range $prefix := .IconGroupOrder}} + {{$icons := index $.IconGroups $prefix}} + {{if $icons}} +

{{$prefix}} {{len $icons}}

+
+ {{range $name := $icons}} +
+
{{svg $name 16}}
+ {{$name}} +
+ {{end}} +
+ {{end}} + {{end}} +
+{{template "devtest/devtest-footer"}} From bb7f671ec90226245c5b97b7a90ab8924878ea56 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 1 Apr 2026 12:00:39 +0200 Subject: [PATCH 3/5] remove avatar --- routers/web/devtest/devtest.go | 15 ------ templates/devtest/avatar.tmpl | 83 ---------------------------------- 2 files changed, 98 deletions(-) delete mode 100644 templates/devtest/avatar.tmpl diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go index 7b1d2d1271..c9964eac45 100644 --- a/routers/web/devtest/devtest.go +++ b/routers/web/devtest/devtest.go @@ -181,19 +181,6 @@ func prepareMockDataRelativeTime(ctx *context.Context) { ctx.Data["TimeFuture1y"] = now.Add(366 * 24 * time.Hour) } -func prepareMockDataAvatar(ctx *context.Context) { - mockUsers, _ := db.Find[user_model.User](ctx, user_model.SearchUserOptions{ListOptions: db.ListOptions{PageSize: 1}}) - ctx.Data["MockUser"] = mockUsers[0] - ctx.Data["AvatarSizes"] = []int{16, 20, 24, 28, 32, 40, 48, 64, 100, 128} - ctx.Data["SampleEmails"] = []string{ - "alice@example.com", - "bob@example.org", - "charlie@test.io", - "devtest@gitea.io", - "noreply@example.com", - } -} - func prepareMockDataIconGallery(ctx *context.Context) { allNames := svg.DiscoveredIconNames() grouped := map[string][]string{} @@ -219,8 +206,6 @@ func prepareMockData(ctx *context.Context) { prepareMockDataBadgeActionsSvg(ctx) case "/devtest/relative-time": prepareMockDataRelativeTime(ctx) - case "/devtest/avatar": - prepareMockDataAvatar(ctx) case "/devtest/icon-gallery": prepareMockDataIconGallery(ctx) } diff --git a/templates/devtest/avatar.tmpl b/templates/devtest/avatar.tmpl deleted file mode 100644 index 3c48675f63..0000000000 --- a/templates/devtest/avatar.tmpl +++ /dev/null @@ -1,83 +0,0 @@ -{{template "devtest/devtest-header"}} -
-

Avatar

- -

Sizes (via AvatarByEmail)

-

Using ctx.AvatarUtils.AvatarByEmail at various pixel sizes:

-
- {{range $size := .AvatarSizes}} -
- {{ctx.AvatarUtils.AvatarByEmail "devtest@example.com" "Dev Test" $size}} - {{$size}}px -
- {{end}} -
- -

User Avatars

-

Using ctx.AvatarUtils.Avatar with a real user and with nil (fallback):

-
-
- {{ctx.AvatarUtils.Avatar .MockUser 28}} - User (28px) -
-
- {{ctx.AvatarUtils.Avatar .MockUser 40}} - User (40px) -
-
- {{ctx.AvatarUtils.Avatar .MockUser 100}} - User (100px) -
-
- {{ctx.AvatarUtils.Avatar nil 28}} - nil fallback (28px) -
-
- {{ctx.AvatarUtils.Avatar nil 40}} - nil fallback (40px) -
-
- -

Custom CSS Classes

-
-
- {{ctx.AvatarUtils.Avatar .MockUser 28 "ui avatar tw-align-middle"}} - default class -
-
- {{ctx.AvatarUtils.Avatar .MockUser 28 "ui avatar tw-align-middle tw-rounded-full"}} - tw-rounded-full -
-
- -

Inline with Text

-
- {{ctx.AvatarUtils.Avatar .MockUser 20 "ui avatar tw-align-middle"}} {{.MockUser.Name}} opened this issue -
-
- {{ctx.AvatarUtils.AvatarByEmail "user1@example.com" "User One" 16}} User One and {{ctx.AvatarUtils.AvatarByEmail "user2@example.com" "User Two" 16}} User Two -
- -

Avatar with Link (typical pattern)

- - -

Avatar Upload Cropper

-

The cropper requires index.js (global init). The HTML structure is shown below for reference:

-
- {{template "shared/avatar_upload_crop" dict "LabelText" "Choose an avatar"}} -
- -

Multiple Email Avatars (gravatar hashing)

-

Different emails produce different fallback avatars:

-
- {{range $email := .SampleEmails}} -
- {{ctx.AvatarUtils.AvatarByEmail $email $email 28}} - {{$email}} -
- {{end}} -
-
-{{template "devtest/devtest-footer"}} From 0434d5f0818285bc655d12e13bc2a7e3139206d0 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 1 Apr 2026 20:09:32 +0200 Subject: [PATCH 4/5] revert sqlite --- Makefile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Makefile b/Makefile index 304cea6061..2bc3f65550 100644 --- a/Makefile +++ b/Makefile @@ -370,10 +370,6 @@ lint-json-fix: node_modules ## lint and fix json files watch: ## watch everything and continuously rebuild @bash tools/watch.sh -.PHONY: watch-sqlite -watch-sqlite: ## watch and use sqlite for testing - @TAGS="sqlite sqlite_unlock_notify" $(MAKE) watch - .PHONY: watch-frontend watch-frontend: node_modules ## start vite dev server for frontend NODE_ENV=development pnpm exec vite From 8f6f30c1cedff3aca172c2f9c6fcb4d605741847 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Sat, 18 Apr 2026 21:22:22 +0200 Subject: [PATCH 5/5] extraction --- templates/devtest/icon-gallery.tmpl | 28 ++++------------------------ web_src/css/devtest.css | 17 +++++++++++++++++ web_src/js/modules/devtest.ts | 19 +++++++++++++++++++ 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/templates/devtest/icon-gallery.tmpl b/templates/devtest/icon-gallery.tmpl index fd24bf7b34..77607f9370 100644 --- a/templates/devtest/icon-gallery.tmpl +++ b/templates/devtest/icon-gallery.tmpl @@ -3,39 +3,19 @@

Icon Gallery

All {{.IconCount}} SVG icons available in templates.

- +

- - {{range $prefix := .IconGroupOrder}} {{$icons := index $.IconGroups $prefix}} {{if $icons}}

{{$prefix}} {{len $icons}}

{{range $name := $icons}} -
-
{{svg $name 16}}
- {{$name}} + {{end}}
diff --git a/web_src/css/devtest.css b/web_src/css/devtest.css index c344d99058..18424681f9 100644 --- a/web_src/css/devtest.css +++ b/web_src/css/devtest.css @@ -18,3 +18,20 @@ h1, h2 { .fetch-action-demo-forms .form-fetch-action { border: 1px red dashed; /* show the border for demo purpose */ } + +.icon-gallery-search { + width: 300px; +} + +.icon-gallery-card { + width: 120px; +} + +.icon-gallery-preview { + height: 32px; +} + +.icon-gallery-name { + max-width: 108px; + font-size: 10px; +} diff --git a/web_src/js/modules/devtest.ts b/web_src/js/modules/devtest.ts index 7886ff9748..8aa7a9364f 100644 --- a/web_src/js/modules/devtest.ts +++ b/web_src/js/modules/devtest.ts @@ -8,6 +8,25 @@ import {html} from '../utils/html.ts'; type LevelMap = Record Toast | null>; function initDevtestPage() { + const iconSearch = document.querySelector('#icon-search'); + const iconSizeToggle = document.querySelector('#icon-size-toggle'); + if (iconSearch && iconSizeToggle) { + iconSearch.addEventListener('input', () => { + const query = iconSearch.value.toLowerCase(); + for (const card of document.querySelectorAll('.icon-card')) { + card.style.display = card.getAttribute('data-name')!.includes(query) ? '' : 'none'; + } + }); + + iconSizeToggle.addEventListener('change', () => { + const size = iconSizeToggle.checked ? '24' : '16'; + for (const icon of document.querySelectorAll('.icon-card svg')) { + icon.setAttribute('width', size); + icon.setAttribute('height', size); + } + }); + } + const toastButtons = document.querySelectorAll('.toast-test-button'); if (toastButtons.length) { const levelMap: LevelMap = {info: showInfoToast, warning: showWarningToast, error: showErrorToast};