loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request 'Rework new repo dialog' (#6386) from fnetx/new-repo-form into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6386
Reviewed-by: 0ko <0ko@noreply.codeberg.org>

Otto 6d61ae5c 6723087a

+489 -304
+6 -1
options/locale/locale_en-US.ini
··· 1053 1053 admin.flags_replaced = Repository flags replaced 1054 1054 1055 1055 new_repo_helper = A repository contains all project files, including revision history. Already hosting one elsewhere? <a href="%s">Migrate repository</a>. 1056 + new_from_template = Use a template 1057 + new_from_template_description = You can select an existing repository template on this instance and apply its settings. 1058 + new_advanced = Advanced settings 1059 + new_advanced_expand = Click to expand 1056 1060 owner = Owner 1057 1061 owner_helper = Some organizations may not show up in the dropdown due to a maximum repository count limit. 1058 1062 repo_name = Repository name ··· 1099 1103 readme = README 1100 1104 readme_helper = Select a README file template 1101 1105 readme_helper_desc = This is the place where you can write a complete description for your project. 1102 - auto_init = Initialize repository (Adds .gitignore, License and README) 1106 + auto_init = Initialize repository 1107 + auto_init_description = Start the Git history with a README and optionally add License and .gitignore files. 1103 1108 create_repo = Create repository 1104 1109 default_branch = Default branch 1105 1110 default_branch_label = default
+23 -195
templates/repo/create.tmpl
··· 16 16 <p>{{ctx.Locale.TrN .MaxCreationLimit "repo.form.reach_limit_of_creation_1" "repo.form.reach_limit_of_creation_n" .MaxCreationLimit}}</p> 17 17 </div> 18 18 {{end}} 19 - <div class="inline required field {{if .Err_Owner}}error{{end}}"> 20 - <label>{{ctx.Locale.Tr "repo.owner"}}</label> 21 - <div class="ui selection owner dropdown"> 22 - <input type="hidden" id="uid" name="uid" value="{{.ContextUser.ID}}" required> 23 - <span class="text truncated-item-container" title="{{.ContextUser.Name}}"> 24 - {{ctx.AvatarUtils.Avatar .ContextUser 28 "mini"}} 25 - <span class="truncated-item-name">{{.ContextUser.ShortName 40}}</span> 26 - </span> 27 - {{svg "octicon-triangle-down" 14 "dropdown icon"}} 28 - <div class="menu"> 29 - <div class="item truncated-item-container" data-value="{{.SignedUser.ID}}" title="{{.SignedUser.Name}}"> 30 - {{ctx.AvatarUtils.Avatar .SignedUser 28 "mini"}} 31 - <span class="truncated-item-name">{{.SignedUser.ShortName 40}}</span> 32 - </div> 33 - {{range .Orgs}} 34 - <div class="item truncated-item-container" data-value="{{.ID}}" title="{{.Name}}"> 35 - {{ctx.AvatarUtils.Avatar . 28 "mini"}} 36 - <span class="truncated-item-name">{{.ShortName 40}}</span> 37 - </div> 38 - {{end}} 39 - </div> 40 - </div> 41 - <span class="help">{{ctx.Locale.Tr "repo.owner_helper"}}</span> 42 - </div> 19 + <fieldset> 20 + {{template "repo/create_basic" .}} 21 + </fieldset> 43 22 44 - <div class="inline required field {{if .Err_RepoName}}error{{end}}"> 45 - <label for="repo_name">{{ctx.Locale.Tr "repo.repo_name"}}</label> 46 - <input id="repo_name" name="repo_name" value="{{.repo_name}}" autofocus required maxlength="100"> 47 - <span class="help">{{ctx.Locale.Tr "repo.repo_name_helper"}}</span> 48 - </div> 49 - <div class="inline field"> 50 - <label>{{ctx.Locale.Tr "repo.visibility"}}</label> 51 - <div class="ui checkbox"> 52 - <input name="private" type="checkbox" 53 - {{if .IsForcedPrivate}} 54 - checked disabled 55 - {{else}} 56 - {{if .private}}checked{{end}} 57 - {{end}}> 58 - <label>{{ctx.Locale.Tr "repo.visibility_helper"}}</label> 59 - </div> 60 - {{if .IsForcedPrivate}} 61 - <span class="help">{{ctx.Locale.Tr "repo.visibility_helper_forced"}}</span> 62 - {{end}} 63 - <span class="help">{{ctx.Locale.Tr "repo.visibility_description"}}</span> 64 - </div> 65 - <div class="inline field {{if .Err_Description}}error{{end}}"> 66 - <label for="description">{{ctx.Locale.Tr "repo.repo_desc"}}</label> 67 - <textarea id="description" rows="2" name="description" placeholder="{{ctx.Locale.Tr "repo.repo_desc_helper"}}" maxlength="2048">{{.description}}</textarea> 68 - </div> 69 - <div class="inline field"> 70 - <label>{{ctx.Locale.Tr "repo.template"}}</label> 71 - <div id="repo_template_search" class="ui search selection dropdown"> 72 - <input type="hidden" id="repo_template" name="repo_template" value="{{if ne .repo_template 0}}{{.repo_template}}{{end}}"> 73 - <div class="default text">{{.repo_template_name}}</div> 74 - <div class="menu"> 75 - </div> 76 - </div> 77 - </div> 78 - 79 - <div id="template_units" class="tw-hidden"> 80 - <div class="inline field"> 81 - <label>{{ctx.Locale.Tr "repo.template.items"}}</label> 82 - <div class="ui checkbox"> 83 - <input name="git_content" type="checkbox" {{if .git_content}}checked{{end}}> 84 - <label>{{ctx.Locale.Tr "repo.template.git_content"}}</label> 85 - </div> 86 - <div class="ui checkbox" {{if not .SignedUser.CanEditGitHook}}data-tooltip-content="{{ctx.Locale.Tr "repo.template.git_hooks_tooltip"}}"{{end}}> 87 - <input name="git_hooks" type="checkbox" {{if .git_hooks}}checked{{end}}> 88 - <label>{{ctx.Locale.Tr "repo.template.git_hooks"}}</label> 89 - </div> 90 - </div> 91 - <div class="inline field"> 92 - <label></label> 93 - <div class="ui checkbox"> 94 - <input name="webhooks" type="checkbox" {{if .webhooks}}checked{{end}}> 95 - <label>{{ctx.Locale.Tr "repo.template.webhooks"}}</label> 96 - </div> 97 - <div class="ui checkbox"> 98 - <input name="topics" type="checkbox" {{if .topics}}checked{{end}}> 99 - <label>{{ctx.Locale.Tr "repo.template.topics"}}</label> 100 - </div> 101 - </div> 102 - <div class="inline field"> 103 - <label></label> 104 - <div class="ui checkbox"> 105 - <input name="avatar" type="checkbox" {{if .avatar}}checked{{end}}> 106 - <label>{{ctx.Locale.Tr "repo.template.avatar"}}</label> 107 - </div> 108 - <div class="ui checkbox"> 109 - <input name="labels" type="checkbox" {{if .labels}}checked{{end}}> 110 - <label>{{ctx.Locale.Tr "repo.template.issue_labels"}}</label> 111 - </div> 112 - </div> 113 - <div class="inline field"> 114 - <label></label> 115 - <div class="ui checkbox"> 116 - <input name="protected_branch" type="checkbox" {{if .protected_branch}}checked{{end}}> 117 - <label>{{ctx.Locale.Tr "repo.settings.protected_branch"}}</label> 118 - </div> 119 - </div> 120 - </div> 23 + <fieldset> 24 + <legend> 25 + {{ctx.Locale.Tr "repo.new_from_template"}} 26 + <span class="help">{{ctx.Locale.Tr "repo.new_from_template_description"}}</span> 27 + </legend> 28 + {{template "repo/create_from_template" .}} 29 + </fieldset> 121 30 122 31 <div id="non_template"> 123 - <div class="inline field"> 124 - <label>{{ctx.Locale.Tr "repo.issue_labels"}}</label> 125 - <div class="ui search selection dropdown"> 126 - <input type="hidden" name="issue_labels" value="{{.issueLabels}}"> 127 - <div class="default text">{{ctx.Locale.Tr "repo.issue_labels_helper"}}</div> 128 - <div class="menu"> 129 - <div class="item" data-value="">{{ctx.Locale.Tr "repo.issue_labels_helper"}}</div> 130 - {{range .LabelTemplateFiles}} 131 - <div class="item" data-value="{{.DisplayName}}">{{.DisplayName}}<br><p>({{.Description}})</p></div> 132 - {{end}} 133 - </div> 134 - </div> 135 - </div> 136 - 137 - <div class="divider"></div> 32 + <fieldset> 33 + <legend>{{ctx.Locale.Tr "repo.auto_init"}}</legend> 34 + {{template "repo/create_init" .}} 35 + </fieldset> 138 36 139 - <div class="inline field"> 140 - <label>.gitignore</label> 141 - <div class="ui multiple search selection dropdown"> 142 - <input type="hidden" name="gitignores" value="{{.gitignores}}"> 143 - <div class="default text">{{ctx.Locale.Tr "repo.repo_gitignore_helper"}}</div> 144 - <div class="menu"> 145 - {{range .Gitignores}} 146 - <div class="item" data-value="{{.}}">{{.}}</div> 147 - {{end}} 148 - </div> 149 - </div> 150 - <span class="help">{{ctx.Locale.Tr "repo.repo_gitignore_helper_desc"}}</span> 151 - </div> 152 - <div class="inline field"> 153 - <label>{{ctx.Locale.Tr "repo.license"}}</label> 154 - <div class="ui search selection dropdown"> 155 - <input type="hidden" name="license" value="{{.license}}"> 156 - <div class="default text">{{ctx.Locale.Tr "repo.license_helper"}}</div> 157 - <div class="menu"> 158 - <div class="item" data-value="">{{ctx.Locale.Tr "repo.license_helper"}}</div> 159 - {{range .Licenses}} 160 - <div class="item" data-value="{{.}}">{{.}}</div> 161 - {{end}} 162 - </div> 163 - </div> 164 - <span class="help">{{ctx.Locale.Tr "repo.license_helper_desc" "https://choosealicense.com/"}}</span> 165 - </div> 166 - 167 - <div class="inline field"> 168 - <label>{{ctx.Locale.Tr "repo.readme"}}</label> 169 - <div class="ui selection dropdown"> 170 - <input type="hidden" name="readme" value="{{.readme}}"> 171 - <div class="default text">{{ctx.Locale.Tr "repo.readme_helper"}}</div> 172 - <div class="menu"> 173 - {{range .Readmes}} 174 - <div class="item" data-value="{{.}}">{{.}}</div> 175 - {{end}} 176 - </div> 177 - </div> 178 - <span class="help">{{ctx.Locale.Tr "repo.readme_helper_desc"}}</span> 179 - </div> 180 - <div class="inline field"> 181 - <div class="ui checkbox" id="auto-init"> 182 - <input name="auto_init" type="checkbox" {{if .auto_init}}checked{{end}}> 183 - <label>{{ctx.Locale.Tr "repo.auto_init"}}</label> 184 - </div> 185 - </div> 186 - <div class="inline field"> 187 - <label for="default_branch">{{ctx.Locale.Tr "repo.default_branch"}}</label> 188 - <input id="default_branch" name="default_branch" value="{{.default_branch}}" placeholder="{{.default_branch}}"> 189 - <span class="help">{{ctx.Locale.Tr "repo.default_branch_helper"}}</span> 190 - </div> 191 - <div class="inline field"> 192 - <label>{{ctx.Locale.Tr "repo.object_format"}}</label> 193 - <div class="ui selection owner dropdown"> 194 - <input type="hidden" id="object_format_name" name="object_format_name" value="{{.DefaultObjectFormat.Name}}" required> 195 - <div class="default text">{{.DefaultObjectFormat.Name}}</div> 196 - <div class="menu"> 197 - {{range .SupportedObjectFormats}} 198 - <div class="item" data-value="{{.Name}}">{{.Name}}</div> 199 - {{end}} 200 - </div> 201 - </div> 202 - <span class="help">{{ctx.Locale.Tr "repo.object_format_helper"}}</span> 203 - </div> 204 - <div class="inline field"> 205 - <label>{{ctx.Locale.Tr "repo.template"}}</label> 206 - <div class="ui checkbox"> 207 - <input name="template" type="checkbox"> 208 - <label>{{ctx.Locale.Tr "repo.template_helper"}}</label> 209 - </div> 210 - </div> 37 + <fieldset> 38 + <legend>{{ctx.Locale.Tr "repo.new_advanced"}}</legend> 39 + <details><summary>{{ctx.Locale.Tr "repo.new_advanced_expand"}}</summary> 40 + {{template "repo/create_advanced" .}} 41 + </details> 42 + </fieldset> 211 43 </div> 212 - <br> 213 - <div class="inline field"> 214 - <label></label> 215 - <button class="ui primary button{{if not .CanCreateRepo}} disabled{{end}}"> 216 - {{ctx.Locale.Tr "repo.create_repo"}} 217 - </button> 218 - </div> 44 + <button class="ui primary button{{if not .CanCreateRepo}} disabled{{end}}"> 45 + {{ctx.Locale.Tr "repo.create_repo"}} 46 + </button> 219 47 </div> 220 48 </form> 221 49 </div>
+45
templates/repo/create_advanced.tmpl
··· 1 + <label> 2 + {{ctx.Locale.Tr "repo.issue_labels"}} 3 + <div class="ui search selection dropdown"> 4 + <input type="hidden" name="issue_labels" value="{{.issueLabels}}"> 5 + <div class="default text">{{ctx.Locale.Tr "repo.issue_labels_helper"}}</div> 6 + <div class="menu"> 7 + <div class="item" data-value="">{{ctx.Locale.Tr "repo.issue_labels_helper"}}</div> 8 + {{range .LabelTemplateFiles}} 9 + <div class="item" data-value="{{.DisplayName}}">{{.DisplayName}}<br><p>({{.Description}})</p></div> 10 + {{end}} 11 + </div> 12 + </div> 13 + </label> 14 + 15 + {{$supportedFormatsLength := len .SupportedObjectFormats}} 16 + {{/* Only offer object format selection if there is an actual choice */}} 17 + {{if ge $supportedFormatsLength 2}} 18 + <label> 19 + {{ctx.Locale.Tr "repo.object_format"}} 20 + <div class="ui selection dropdown"> 21 + <input type="hidden" id="object_format_name" name="object_format_name" value="{{.DefaultObjectFormat.Name}}" required> 22 + <div class="default text">{{.DefaultObjectFormat.Name}}</div> 23 + <div class="menu"> 24 + {{range .SupportedObjectFormats}} 25 + <div class="item" data-value="{{.Name}}">{{.Name}}</div> 26 + {{end}} 27 + </div> 28 + </div> 29 + <span class="help">{{ctx.Locale.Tr "repo.object_format_helper"}}</span> 30 + </label> 31 + {{else}} 32 + <input type="hidden" name="object_format_name" value="{{.DefaultObjectFormat.Name}}" required> 33 + {{end}} 34 + 35 + <label> 36 + {{ctx.Locale.Tr "repo.default_branch"}} 37 + <input name="default_branch" value="{{.default_branch}}" placeholder="{{.default_branch}}"> 38 + <span class="help">{{ctx.Locale.Tr "repo.default_branch_helper"}}</span> 39 + </label> 40 + 41 + <label> 42 + <input name="template" type="checkbox"> 43 + {{ctx.Locale.Tr "repo.template_helper"}} 44 + <span class="help">{{ctx.Locale.Tr "repo.template_description"}}</span> 45 + </label>
+47
templates/repo/create_basic.tmpl
··· 1 + <label id="repo_owner_label" {{if .Err_Owner}}class="field error"{{end}}> 2 + {{ctx.Locale.Tr "repo.owner"}} 3 + <div class="ui selection required dropdown" aria-labelledby="repo_owner_label"> 4 + {{/* uid id is used by the repo-template code */}} 5 + <input type="hidden" id="uid" name="uid" value="{{.ContextUser.ID}}" required> 6 + <span class="text truncated-item-container" title="{{.ContextUser.Name}}"> 7 + {{ctx.AvatarUtils.Avatar .ContextUser 28 "mini"}} 8 + <span class="truncated-item-name">{{.ContextUser.ShortName 40}}</span> 9 + </span> 10 + {{svg "octicon-triangle-down" 14 "dropdown icon"}} 11 + <div class="menu"> 12 + <div class="item truncated-item-container" data-value="{{.SignedUser.ID}}" title="{{.SignedUser.Name}}"> 13 + {{ctx.AvatarUtils.Avatar .SignedUser 28 "mini"}} 14 + <span class="truncated-item-name">{{.SignedUser.ShortName 40}}</span> 15 + </div> 16 + {{range .Orgs}} 17 + <div class="item truncated-item-container" data-value="{{.ID}}" title="{{.Name}}"> 18 + {{ctx.AvatarUtils.Avatar . 28 "mini"}} 19 + <span class="truncated-item-name">{{.ShortName 40}}</span> 20 + </div> 21 + {{end}} 22 + </div> 23 + </div> 24 + <span class="help">{{ctx.Locale.Tr "repo.owner_helper"}}</span> 25 + </label> 26 + <label {{if .Err_RepoName}}class="field error"{{end}}> 27 + {{ctx.Locale.Tr "repo.repo_name"}} 28 + <input name="repo_name" value="{{.repo_name}}" required maxlength="100"> 29 + <span class="help">{{ctx.Locale.Tr "repo.repo_name_helper"}}</span> 30 + </label> 31 + <label> 32 + <input name="private" type="checkbox" 33 + {{if .IsForcedPrivate}} 34 + checked disabled 35 + {{else}} 36 + {{if .private}}checked{{end}} 37 + {{end}}> 38 + {{ctx.Locale.Tr "repo.visibility_helper"}} 39 + {{if .IsForcedPrivate}} 40 + <span class="help">{{ctx.Locale.Tr "repo.visibility_helper_forced"}}</span> 41 + {{end}} 42 + <span class="help">{{ctx.Locale.Tr "repo.visibility_description"}}</span> 43 + </label> 44 + <label {{if .Err_Description}}class="field error"{{end}}> 45 + {{ctx.Locale.Tr "repo.repo_desc"}} 46 + <textarea rows="2" name="description" placeholder="{{ctx.Locale.Tr "repo.repo_desc_helper"}}" maxlength="2048">{{.description}}</textarea> 47 + </label>
+49
templates/repo/create_from_template.tmpl
··· 1 + <label class="tw-mb-0"> 2 + {{ctx.Locale.Tr "repo.template"}} 3 + </label> 4 + {{/* If the dropdown is inside the label, the focus works correctly and it is more accessible. 5 + However, the Javascript takes the focus and opens the dropdown again immediately after closing. 6 + When the user interacts (via mouse or keyboard), the dropdown closes again. 7 + Due to the fieldset legend, this solutions is probably acceptable until the dropdown can be fixed properly. */}} 8 + <div id="repo_template_search" class="ui search selection dropdown tw-w-full"> 9 + <input type="hidden" id="repo_template" name="repo_template" value="{{if ne .repo_template 0}}{{.repo_template}}{{end}}"> 10 + <div class="default text">{{.repo_template_name}}</div> 11 + <div class="menu"> 12 + </div> 13 + </div> 14 + 15 + <fieldset id="template_units" class="tw-hidden simple-grid grid-2"> 16 + <legend>{{ctx.Locale.Tr "repo.template.items"}}</legend> 17 + 18 + <label> 19 + <input name="git_content" type="checkbox" {{if .git_content}}checked{{end}}> 20 + {{ctx.Locale.Tr "repo.template.git_content"}} 21 + </label> 22 + <label> 23 + <input name="webhooks" type="checkbox" {{if .webhooks}}checked{{end}}> 24 + {{ctx.Locale.Tr "repo.template.webhooks"}} 25 + </label> 26 + <label> 27 + <input name="topics" type="checkbox" {{if .topics}}checked{{end}}> 28 + {{ctx.Locale.Tr "repo.template.topics"}} 29 + </label> 30 + <label> 31 + <input name="avatar" type="checkbox" {{if .avatar}}checked{{end}}> 32 + {{ctx.Locale.Tr "repo.template.avatar"}} 33 + </label> 34 + <label> 35 + <input name="labels" type="checkbox" {{if .labels}}checked{{end}}> 36 + {{ctx.Locale.Tr "repo.template.issue_labels"}} 37 + </label> 38 + <label> 39 + <input name="protected_branch" type="checkbox" {{if .protected_branch}}checked{{end}}> 40 + {{ctx.Locale.Tr "repo.settings.protected_branch"}} 41 + </label> 42 + <label> 43 + <input name="git_hooks" type="checkbox" {{if .git_hooks}}checked{{end}}> 44 + {{ctx.Locale.Tr "repo.template.git_hooks"}} 45 + {{if not .SignedUser.CanEditGitHook}} 46 + <span class="help">{{ctx.Locale.Tr "repo.template.git_hooks_tooltip"}}</span> 47 + {{end}} 48 + </label> 49 + </fieldset>
+56
templates/repo/create_init.tmpl
··· 1 + <label> 2 + <input name="auto_init" type="checkbox" {{if .auto_init}}checked{{end}}> 3 + {{ctx.Locale.Tr "repo.auto_init"}} 4 + <span class="help">{{ctx.Locale.Tr "repo.auto_init_description"}}</span> 5 + </label> 6 + 7 + <div class="hide-unless-checked"> 8 + <label> 9 + .gitignore 10 + <div class="ui multiple search selection dropdown"> 11 + <input type="hidden" name="gitignores" value="{{.gitignores}}"> 12 + <div class="default text">{{ctx.Locale.Tr "repo.repo_gitignore_helper"}}</div> 13 + <div class="menu"> 14 + {{range .Gitignores}} 15 + <div class="item" data-value="{{.}}">{{.}}</div> 16 + {{end}} 17 + </div> 18 + </div> 19 + <span class="help">{{ctx.Locale.Tr "repo.repo_gitignore_helper_desc"}}</span> 20 + </label> 21 + 22 + <label> 23 + {{ctx.Locale.Tr "repo.license"}} 24 + <div class="ui search selection dropdown"> 25 + <input type="hidden" name="license" value="{{.license}}"> 26 + <div class="default text">{{ctx.Locale.Tr "repo.license_helper"}}</div> 27 + <div class="menu"> 28 + <div class="item" data-value="">{{ctx.Locale.Tr "repo.license_helper"}}</div> 29 + {{range .Licenses}} 30 + <div class="item" data-value="{{.}}">{{.}}</div> 31 + {{end}} 32 + </div> 33 + </div> 34 + <span class="help">{{ctx.Locale.Tr "repo.license_helper_desc" "https://choosealicense.com/"}}</span> 35 + </label> 36 + 37 + {{$supportedReadmesLength := len .Readmes}} 38 + {{/* Only offer README selection if there is an actual choice */}} 39 + {{if ge $supportedReadmesLength 2}} 40 + <label> 41 + {{ctx.Locale.Tr "repo.readme"}} 42 + <div class="ui selection dropdown"> 43 + <input type="hidden" name="readme" value="{{.readme}}"> 44 + <div class="default text">{{ctx.Locale.Tr "repo.readme_helper"}}</div> 45 + <div class="menu"> 46 + {{range .Readmes}} 47 + <div class="item" data-value="{{.}}">{{.}}</div> 48 + {{end}} 49 + </div> 50 + </div> 51 + <span class="help">{{ctx.Locale.Tr "repo.readme_helper_desc"}}</span> 52 + </label> 53 + {{else}} 54 + <input type="hidden" name="readme" value="Default"> 55 + {{end}} 56 + </div>
+134
tests/e2e/repo-new.test.e2e.ts
··· 1 + // @watch start 2 + // templates/repo/create**.tmpl 3 + // web_src/css/{form,repo}.css 4 + // @watch end 5 + 6 + import {expect} from '@playwright/test'; 7 + import {test, dynamic_id, save_visual, login_user, login} from './utils_e2e.ts'; 8 + import {validate_form} from './shared/forms.ts'; 9 + 10 + test.beforeAll(async ({browser}, workerInfo) => { 11 + await login_user(browser, workerInfo, 'user2'); 12 + }); 13 + 14 + test('New repo: invalid', async ({browser}, workerInfo) => { 15 + const page = await login({browser}, workerInfo); 16 + const response = await page.goto('/repo/create'); 17 + expect(response?.status()).toBe(200); 18 + // check that relevant form content is hidden or available 19 + await expect(page.getByRole('group', {name: 'Use a template You can select'}).getByRole('combobox')).toBeVisible(); 20 + await expect(page.getByText('.gitignore Select .gitignore')).toBeHidden(); 21 + await expect(page.getByText('Labels Select a label set')).toBeHidden(); 22 + await validate_form({page}, 'fieldset'); 23 + await save_visual(page); 24 + 25 + await page.getByLabel('Repository name').fill('*invalid'); 26 + await page.getByRole('button', {name: 'Create repository'}).click(); 27 + await expect(page.getByText('Repository name should contain only alphanumeric')).toBeVisible(); 28 + await save_visual(page); 29 + }); 30 + 31 + test('New repo: initialize', async ({browser}, workerInfo) => { 32 + const page = await login({browser}, workerInfo); 33 + const response = await page.goto('/repo/create'); 34 + expect(response?.status()).toBe(200); 35 + // check that relevant form content is hidden or available 36 + await expect(page.getByRole('group', {name: 'Use a template You can select'}).getByRole('combobox')).toBeVisible(); 37 + await expect(page.getByText('.gitignore Select .gitignore')).toBeHidden(); 38 + // fill initialization section 39 + await page.getByText('Start the Git history with').click(); 40 + await page.getByText('Select .gitignore templates').click(); 41 + await page.getByLabel('.gitignore Select .gitignore').fill('Go'); 42 + await page.getByRole('option', {name: 'Go', exact: true}).click(); 43 + await page.keyboard.press('Escape'); 44 + await page.getByLabel('License Select a license file').click(); 45 + await page.getByRole('option', {name: 'MIT', exact: true}).click(); 46 + await page.keyboard.press('Escape'); 47 + // add advanced settings 48 + await page.getByText('Click to expand').click(); 49 + await page.getByPlaceholder('master').fill('main'); 50 + await page.getByLabel('Make repository a template').check(); 51 + 52 + await validate_form({page}, 'fieldset'); 53 + await save_visual(page); 54 + const reponame = dynamic_id(); 55 + await page.getByLabel('Repository name').fill(reponame); 56 + await page.getByRole('button', {name: 'Create repository'}).click(); 57 + await expect(page.getByRole('link', {name: '.gitignore'})).toBeVisible(); 58 + await expect(page.getByRole('link', {name: 'LICENSE', exact: true})).toBeVisible(); 59 + if (!workerInfo.project.name.includes('Mobile')) { 60 + await expect(page.getByText('Template', {exact: true})).toBeVisible(); 61 + } 62 + await save_visual(page); 63 + }); 64 + 65 + test('New repo: initialize later', async ({browser}, workerInfo) => { 66 + const page = await login({browser}, workerInfo); 67 + const response = await page.goto('/repo/create'); 68 + expect(response?.status()).toBe(200); 69 + 70 + const reponame = dynamic_id(); 71 + await page.getByLabel('Repository name').fill(reponame); 72 + await page.getByPlaceholder('Enter short description').fill(`Description for repo ${reponame}`); 73 + await page.getByText('Click to expand').click(); 74 + await page.getByPlaceholder('master').fill('devbranch'); 75 + await validate_form({page}, 'fieldset'); 76 + await page.getByRole('button', {name: 'Create repository'}).click(); 77 + expect(page.url()).toBe(`http://localhost:3003/user2/${reponame}`); 78 + await expect(page.getByRole('link', {name: 'New file'})).toBeVisible(); 79 + await expect(page.getByRole('heading', {name: 'Creating a new repository on'})).toBeVisible(); 80 + await save_visual(page); 81 + 82 + // add a README 83 + await page.getByRole('link', {name: 'New file'}).click(); 84 + // wait for loading spinner to disappear 85 + // Otherwise, filling the filename might not populate the tree_path form field or preview tab 86 + // The editor has race conditions, likely related to https://codeberg.org/forgejo/forgejo/issues/3371 87 + await expect(page.locator('.is-loading')).toBeHidden(); 88 + await page.locator('.view-lines').click(); 89 + await page.keyboard.type('# Heading\n\nHello Forgejo!'); 90 + await page.getByPlaceholder('Name your file…').fill('README.md'); 91 + await expect(page.getByText('Preview')).toBeVisible(); 92 + await page.getByPlaceholder('Add "<filename>"').fill('My first commit message'); 93 + await page.getByRole('button', {name: 'Commit changes'}).click(); 94 + expect(page.url()).toBe(`http://localhost:3003/user2/${reponame}/src/branch/devbranch/README.md`); 95 + await expect(page.getByRole('link', {name: 'My first commit message'})).toBeVisible(); 96 + await expect(page.getByText('Hello Forgejo!')).toBeVisible(); 97 + await save_visual(page); 98 + }); 99 + 100 + test('New repo: from template', async ({browser}, workerInfo) => { 101 + test.skip(['Mobile Safari', 'webkit'].includes(workerInfo.project.name), 'WebKit browsers seem to have CORS issues with localhost here.'); 102 + const page = await login({browser}, workerInfo); 103 + const response = await page.goto('/repo/create'); 104 + expect(response?.status()).toBe(200); 105 + 106 + const reponame = dynamic_id(); 107 + await page.getByRole('group', {name: 'Use a template You can select'}).getByRole('combobox').click(); 108 + await page.getByRole('option', {name: 'user27/template1'}).click(); 109 + await page.getByText('Git content (Default branch)').click(); 110 + await save_visual(page); 111 + await page.getByLabel('Repository name').fill(reponame); 112 + await page.getByRole('button', {name: 'Create repository'}).click(); 113 + await expect(page.getByRole('link', {name: `${reponame}.log`})).toBeVisible(); 114 + await save_visual(page); 115 + }); 116 + 117 + test('New repo: label set', async ({browser}, workerInfo) => { 118 + const page = await login({browser}, workerInfo); 119 + await page.goto('/repo/create'); 120 + 121 + const reponame = dynamic_id(); 122 + await page.getByText('Click to expand').click(); 123 + await page.getByLabel('Labels Select a label set').click(); 124 + await page.getByRole('option', {name: 'Advanced (Kind/Bug, Kind/'}).click(); 125 + // close dropdown via unrelated click 126 + await page.getByText('You can select an existing').click(); 127 + await save_visual(page); 128 + await page.getByLabel('Repository name').fill(reponame); 129 + await page.getByRole('button', {name: 'Create repository'}).click(); 130 + await page.goto(`/user2/${reponame}/issues`); 131 + await page.getByRole('link', {name: 'Labels'}).click(); 132 + await expect(page.getByText('Kind/Bug Something is not')).toBeVisible(); 133 + await save_visual(page); 134 + });
+3
tests/e2e/shared/forms.ts
··· 7 7 'span[data-tooltip-content', 8 8 // exclude weird non-semantic HTML disabled content 9 9 '.disabled', 10 + // legacy dropdowns don't use semantic HTML yet, 11 + // avoid using these where possible 12 + '.ui.dropdown', 10 13 ]; 11 14 await accessibilityCheck({page}, [scope], excludedElements, []); 12 15
+15
tests/e2e/utils_e2e.ts
··· 81 81 await page.locator('.flex-item-body > relative-time').filter({hasText: /now|minute/}).evaluateAll((nodes) => { 82 82 for (const node of nodes) node.outerHTML = 'relative time in repo'; 83 83 }); 84 + // dynamically generated UUIDs 85 + await page.getByText('dyn-id-').evaluateAll((nodes) => { 86 + for (const node of nodes) node.innerHTML = node.innerHTML.replaceAll(/dyn-id-[a-f0-9-]+/g, 'dynamic-id'); 87 + }); 88 + // repeat above, work around https://github.com/microsoft/playwright/issues/34152 89 + await page.getByText('dyn-id-').evaluateAll((nodes) => { 90 + for (const node of nodes) node.innerHTML = node.innerHTML.replaceAll(/dyn-id-[a-f0-9-]+/g, 'dynamic-id'); 91 + }); 84 92 await page.locator('relative-time').evaluateAll((nodes) => { 85 93 for (const node of nodes) node.outerHTML = 'time element'; 86 94 }); ··· 97 105 page.locator('#repo_migrating'), 98 106 // update order of recently created repos is not fully deterministic 99 107 page.locator('.flex-item-main').filter({hasText: 'relative time in repo'}), 108 + // dynamic IDs in fixed-size inputs 109 + page.locator('input[value*="dyn-id-"]'), 100 110 ], 101 111 }); 102 112 } ··· 122 132 123 133 return {context: await login_user(browser, workerInfo, username), username}; 124 134 } 135 + 136 + // returns a random string with a pattern that can be filtered for screenshots automatically 137 + export function dynamic_id() { 138 + return `dyn-id-${globalThis.crypto.randomUUID()}`; 139 + }
+1 -1
tests/integration/repo_generate_test.go
··· 43 43 // the template menu is loaded client-side, so don't assert the option exists 44 44 assert.Equal(t, templateID, htmlDoc.GetInputValueByName("repo_template"), "Unexpected repo_template selection") 45 45 46 - for _, name := range []string{"issue_labels", "gitignores", "license", "readme", "object_format_name"} { 46 + for _, name := range []string{"issue_labels", "gitignores", "license", "object_format_name"} { 47 47 htmlDoc.AssertDropdownHasOptions(t, name) 48 48 } 49 49 }
-91
web_src/css/base.css
··· 652 652 background: var(--color-active); 653 653 } 654 654 655 - .ui.form .fields.error .field textarea, 656 - .ui.form .fields.error .field select, 657 - .ui.form .fields.error .field input:not([type]), 658 - .ui.form .fields.error .field input[type="date"], 659 - .ui.form .fields.error .field input[type="datetime-local"], 660 - .ui.form .fields.error .field input[type="email"], 661 - .ui.form .fields.error .field input[type="number"], 662 - .ui.form .fields.error .field input[type="password"], 663 - .ui.form .fields.error .field input[type="search"], 664 - .ui.form .fields.error .field input[type="tel"], 665 - .ui.form .fields.error .field input[type="time"], 666 - .ui.form .fields.error .field input[type="text"], 667 - .ui.form .fields.error .field input[type="file"], 668 - .ui.form .fields.error .field input[type="url"], 669 - .ui.form .fields.error .field .ui.dropdown, 670 - .ui.form .fields.error .field .ui.dropdown .item, 671 - .ui.form .field.error .ui.dropdown, 672 - .ui.form .field.error .ui.dropdown .text, 673 - .ui.form .field.error .ui.dropdown .item, 674 - .ui.form .field.error textarea, 675 - .ui.form .field.error select, 676 - .ui.form .field.error input:not([type]), 677 - .ui.form .field.error input[type="date"], 678 - .ui.form .field.error input[type="datetime-local"], 679 - .ui.form .field.error input[type="email"], 680 - .ui.form .field.error input[type="number"], 681 - .ui.form .field.error input[type="password"], 682 - .ui.form .field.error input[type="search"], 683 - .ui.form .field.error input[type="tel"], 684 - .ui.form .field.error input[type="time"], 685 - .ui.form .field.error input[type="text"], 686 - .ui.form .field.error input[type="file"], 687 - .ui.form .field.error input[type="url"], 688 - .ui.form .field.error select:focus, 689 - .ui.form .field.error input:not([type]):focus, 690 - .ui.form .field.error input[type="date"]:focus, 691 - .ui.form .field.error input[type="datetime-local"]:focus, 692 - .ui.form .field.error input[type="email"]:focus, 693 - .ui.form .field.error input[type="number"]:focus, 694 - .ui.form .field.error input[type="password"]:focus, 695 - .ui.form .field.error input[type="search"]:focus, 696 - .ui.form .field.error input[type="tel"]:focus, 697 - .ui.form .field.error input[type="time"]:focus, 698 - .ui.form .field.error input[type="text"]:focus, 699 - .ui.form .field.error input[type="file"]:focus, 700 - .ui.form .field.error input[type="url"]:focus { 701 - background-color: var(--color-error-bg); 702 - border-color: var(--color-error-border); 703 - color: var(--color-error-text); 704 - } 705 - 706 - .ui.form .fields.error .field .ui.dropdown, 707 - .ui.form .field.error .ui.dropdown, 708 - .ui.form .fields.error .field .ui.dropdown:hover, 709 - .ui.form .field.error .ui.dropdown:hover { 710 - border-color: var(--color-error-border) !important; 711 - } 712 - 713 - .ui.form .fields.error .field .ui.dropdown .menu .item:hover, 714 - .ui.form .field.error .ui.dropdown .menu .item:hover { 715 - background-color: var(--color-error-bg-hover); 716 - } 717 - 718 - .ui.form .fields.error .field .ui.dropdown .menu .active.item, 719 - .ui.form .field.error .ui.dropdown .menu .active.item { 720 - background-color: var(--color-error-bg-active) !important; 721 - } 722 - 723 - .ui.form .fields.error .dropdown .menu, 724 - .ui.form .field.error .dropdown .menu { 725 - border-color: var(--color-error-border) !important; 726 - } 727 - 728 - input:-webkit-autofill, 729 - input:-webkit-autofill:focus, 730 - input:-webkit-autofill:hover, 731 - input:-webkit-autofill:active, 732 - .ui.form .field.field input:-webkit-autofill, 733 - .ui.form .field.field input:-webkit-autofill:focus, 734 - .ui.form .field.field input:-webkit-autofill:hover, 735 - .ui.form .field.field input:-webkit-autofill:active { 736 - -webkit-background-clip: text; 737 - -webkit-text-fill-color: var(--color-text); 738 - box-shadow: 0 0 0 100px var(--color-primary-light-6) inset !important; 739 - border-color: var(--color-primary-light-4) !important; 740 - } 741 - 742 - .ui.form .field.muted { 743 - opacity: var(--opacity-disabled); 744 - } 745 - 746 655 .text.primary { 747 656 color: var(--color-primary) !important; 748 657 }
+110 -12
web_src/css/form.css
··· 18 18 font-weight: var(--font-weight-medium); 19 19 } 20 20 21 + /* override inline style on custom input elements */ 22 + fieldset label .ui.dropdown { 23 + width: 100% !important; 24 + } 25 + 21 26 fieldset .help { 22 27 font-weight: var(--font-weight-normal); 23 28 } ··· 27 32 padding-bottom: 0; 28 33 } 29 34 30 - fieldset input[type="checkbox"], 31 - fieldset input[type="radio"] { 35 + fieldset label > input, 36 + fieldset label > textarea, 37 + fieldset label > .ui.dropdown, 38 + fieldset label + .ui.dropdown { 39 + margin-top: 0.28rem !important; 40 + } 41 + 42 + fieldset label > input[type="checkbox"], 43 + fieldset label > input[type="radio"] { 32 44 margin-right: 0.75em; 45 + margin-top: 0 !important; 33 46 vertical-align: initial !important; /* overrides a semantic.css rule, remove when obsolete */ 34 47 } 35 48 ··· 142 155 color: var(--color-input-text); 143 156 } 144 157 158 + /* error messages */ 159 + fieldset label.error textarea, 160 + fieldset label.error select, 161 + fieldset label.error input, 162 + .ui.form .fields.error .field textarea, 163 + .ui.form .fields.error .field select, 164 + .ui.form .fields.error .field input:not([type]), 165 + .ui.form .fields.error .field input[type="date"], 166 + .ui.form .fields.error .field input[type="datetime-local"], 167 + .ui.form .fields.error .field input[type="email"], 168 + .ui.form .fields.error .field input[type="number"], 169 + .ui.form .fields.error .field input[type="password"], 170 + .ui.form .fields.error .field input[type="search"], 171 + .ui.form .fields.error .field input[type="tel"], 172 + .ui.form .fields.error .field input[type="time"], 173 + .ui.form .fields.error .field input[type="text"], 174 + .ui.form .fields.error .field input[type="file"], 175 + .ui.form .fields.error .field input[type="url"], 176 + .ui.form .fields.error .field .ui.dropdown, 177 + .ui.form .fields.error .field .ui.dropdown .item, 178 + .ui.form .field.error .ui.dropdown, 179 + .ui.form .field.error .ui.dropdown .text, 180 + .ui.form .field.error .ui.dropdown .item, 181 + .ui.form .field.error textarea, 182 + .ui.form .field.error select, 183 + .ui.form .field.error input:not([type]), 184 + .ui.form .field.error input[type="date"], 185 + .ui.form .field.error input[type="datetime-local"], 186 + .ui.form .field.error input[type="email"], 187 + .ui.form .field.error input[type="number"], 188 + .ui.form .field.error input[type="password"], 189 + .ui.form .field.error input[type="search"], 190 + .ui.form .field.error input[type="tel"], 191 + .ui.form .field.error input[type="time"], 192 + .ui.form .field.error input[type="text"], 193 + .ui.form .field.error input[type="file"], 194 + .ui.form .field.error input[type="url"], 195 + .ui.form .field.error select:focus, 196 + .ui.form .field.error input:not([type]):focus, 197 + .ui.form .field.error input[type="date"]:focus, 198 + .ui.form .field.error input[type="datetime-local"]:focus, 199 + .ui.form .field.error input[type="email"]:focus, 200 + .ui.form .field.error input[type="number"]:focus, 201 + .ui.form .field.error input[type="password"]:focus, 202 + .ui.form .field.error input[type="search"]:focus, 203 + .ui.form .field.error input[type="tel"]:focus, 204 + .ui.form .field.error input[type="time"]:focus, 205 + .ui.form .field.error input[type="text"]:focus, 206 + .ui.form .field.error input[type="file"]:focus, 207 + .ui.form .field.error input[type="url"]:focus { 208 + background-color: var(--color-error-bg); 209 + border-color: var(--color-error-border); 210 + color: var(--color-error-text); 211 + } 212 + 213 + .ui.form .fields.error .field .ui.dropdown, 214 + .ui.form .field.error .ui.dropdown, 215 + .ui.form .fields.error .field .ui.dropdown:hover, 216 + .ui.form .field.error .ui.dropdown:hover { 217 + border-color: var(--color-error-border) !important; 218 + } 219 + 220 + .ui.form .fields.error .field .ui.dropdown .menu .item:hover, 221 + .ui.form .field.error .ui.dropdown .menu .item:hover { 222 + background-color: var(--color-error-bg-hover); 223 + } 224 + 225 + .ui.form .fields.error .field .ui.dropdown .menu .active.item, 226 + .ui.form .field.error .ui.dropdown .menu .active.item { 227 + background-color: var(--color-error-bg-active) !important; 228 + } 229 + 230 + .ui.form .fields.error .dropdown .menu, 231 + .ui.form .field.error .dropdown .menu { 232 + border-color: var(--color-error-border) !important; 233 + } 234 + 235 + input:-webkit-autofill, 236 + input:-webkit-autofill:focus, 237 + input:-webkit-autofill:hover, 238 + input:-webkit-autofill:active, 239 + .ui.form .field.field input:-webkit-autofill, 240 + .ui.form .field.field input:-webkit-autofill:focus, 241 + .ui.form .field.field input:-webkit-autofill:hover, 242 + .ui.form .field.field input:-webkit-autofill:active { 243 + -webkit-background-clip: text; 244 + -webkit-text-fill-color: var(--color-text); 245 + box-shadow: 0 0 0 100px var(--color-primary-light-6) inset !important; 246 + border-color: var(--color-primary-light-4) !important; 247 + } 248 + 249 + .ui.form .field.muted { 250 + opacity: var(--opacity-disabled); 251 + } 252 + 145 253 .ui.form .field > label, 146 254 .ui.form .inline.fields > label, 147 255 .ui.form .inline.fields .field > label, ··· 400 508 .repository.new.fork form .header { 401 509 padding-left: 280px !important; 402 510 } 403 - .repository.new.repo form .inline.field > label, 404 511 .repository.new.migrate form .inline.field > label, 405 512 .repository.new.fork form .inline.field > label { 406 513 text-align: right; 407 514 width: 250px !important; 408 515 word-wrap: break-word; 409 516 } 410 - .repository.new.repo form .help, 411 517 .repository.new.migrate form .help, 412 518 .repository.new.fork form .help { 413 519 margin-left: 265px !important; ··· 417 523 .repository.new.fork form .optional .title { 418 524 margin-left: 250px !important; 419 525 } 420 - .repository.new.repo form .inline.field > input, 421 526 .repository.new.migrate form .inline.field > input, 422 527 .repository.new.fork form .inline.field > input, 423 - .repository.new.repo form .inline.field > textarea, 424 528 .repository.new.migrate form .inline.field > textarea, 425 529 .repository.new.fork form .inline.field > textarea { 426 530 width: 50%; ··· 440 544 } 441 545 } 442 546 443 - .repository.new.repo form .dropdown .text, 444 547 .repository.new.migrate form .dropdown .text, 445 548 .repository.new.fork form .dropdown .text { 446 549 margin-right: 0 !important; ··· 453 556 text-align: center; 454 557 } 455 558 456 - .repository.new.repo form .selection.dropdown, 457 559 .repository.new.migrate form .selection.dropdown, 458 560 .repository.new.fork form .selection.dropdown, 459 561 .repository.new.fork form .field a { ··· 488 590 .repository.new.repo .ui.form #auto-init { 489 591 margin-left: 265px !important; 490 592 } 491 - } 492 - 493 - .repository.new.repo .ui.form .selection.dropdown:not(.owner) { 494 - width: 50% !important; 495 593 } 496 594 497 595 @media (max-width: 767.98px) {
-4
web_src/css/repo.css
··· 4 4 user-select: none; 5 5 } 6 6 7 - .repository .owner.dropdown { 8 - min-width: 40% !important; 9 - } 10 - 11 7 .repository .unicode-escaped .escaped-code-point[data-escaped]::before { 12 8 visibility: visible; 13 9 content: attr(data-escaped);