diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index f00d738026..5a89662cb2 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1053,6 +1053,10 @@ admin.failed_to_replace_flags = Failed to replace repository flags
admin.flags_replaced = Repository flags replaced
new_repo_helper = A repository contains all project files, including revision history. Already hosting one elsewhere? Migrate repository .
+new_from_template = Use a template
+new_from_template_description = You can select an existing repository template on this instance and apply its settings.
+new_advanced = Advanced settings
+new_advanced_expand = Click to expand
owner = Owner
owner_helper = Some organizations may not show up in the dropdown due to a maximum repository count limit.
repo_name = Repository name
@@ -1099,7 +1103,8 @@ object_format_helper = Object format of the repository. Cannot be changed later.
readme = README
readme_helper = Select a README file template
readme_helper_desc = This is the place where you can write a complete description for your project.
-auto_init = Initialize repository (Adds .gitignore, License and README)
+auto_init = Initialize repository
+auto_init_description = Start the Git history with a README and optionally add License and .gitignore files.
create_repo = Create repository
default_branch = Default branch
default_branch_label = default
diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl
index df4288a2f2..7ee8587435 100644
--- a/templates/repo/create.tmpl
+++ b/templates/repo/create.tmpl
@@ -16,206 +16,34 @@
{{ctx.Locale.TrN .MaxCreationLimit "repo.form.reach_limit_of_creation_1" "repo.form.reach_limit_of_creation_n" .MaxCreationLimit}}
{{end}}
-
-
{{ctx.Locale.Tr "repo.owner"}}
-
-
-
- {{ctx.AvatarUtils.Avatar .ContextUser 28 "mini"}}
- {{.ContextUser.ShortName 40}}
-
- {{svg "octicon-triangle-down" 14 "dropdown icon"}}
-
-
-
{{ctx.Locale.Tr "repo.owner_helper"}}
-
+
+ {{template "repo/create_basic" .}}
+
-
- {{ctx.Locale.Tr "repo.repo_name"}}
-
- {{ctx.Locale.Tr "repo.repo_name_helper"}}
-
-
-
{{ctx.Locale.Tr "repo.visibility"}}
-
-
- {{ctx.Locale.Tr "repo.visibility_helper"}}
-
- {{if .IsForcedPrivate}}
-
{{ctx.Locale.Tr "repo.visibility_helper_forced"}}
- {{end}}
-
{{ctx.Locale.Tr "repo.visibility_description"}}
-
-
- {{ctx.Locale.Tr "repo.repo_desc"}}
-
-
-
-
{{ctx.Locale.Tr "repo.template"}}
-
-
-
{{.repo_template_name}}
-
-
-
-
-
-
-
{{ctx.Locale.Tr "repo.template.items"}}
-
-
- {{ctx.Locale.Tr "repo.template.git_content"}}
-
-
-
- {{ctx.Locale.Tr "repo.template.git_hooks"}}
-
-
-
-
-
-
- {{ctx.Locale.Tr "repo.template.webhooks"}}
-
-
-
- {{ctx.Locale.Tr "repo.template.topics"}}
-
-
-
-
-
-
- {{ctx.Locale.Tr "repo.template.avatar"}}
-
-
-
- {{ctx.Locale.Tr "repo.template.issue_labels"}}
-
-
-
-
-
-
- {{ctx.Locale.Tr "repo.settings.protected_branch"}}
-
-
-
+
+
+ {{ctx.Locale.Tr "repo.new_from_template"}}
+ {{ctx.Locale.Tr "repo.new_from_template_description"}}
+
+ {{template "repo/create_from_template" .}}
+
-
-
{{ctx.Locale.Tr "repo.issue_labels"}}
-
-
-
{{ctx.Locale.Tr "repo.issue_labels_helper"}}
-
-
-
+
+ {{ctx.Locale.Tr "repo.auto_init"}}
+ {{template "repo/create_init" .}}
+
-
-
-
-
.gitignore
-
-
-
{{ctx.Locale.Tr "repo.repo_gitignore_helper"}}
-
-
-
{{ctx.Locale.Tr "repo.repo_gitignore_helper_desc"}}
-
-
-
{{ctx.Locale.Tr "repo.license"}}
-
-
-
{{ctx.Locale.Tr "repo.license_helper"}}
-
-
-
{{ctx.Locale.Tr "repo.license_helper_desc" "https://choosealicense.com/"}}
-
-
-
-
{{ctx.Locale.Tr "repo.readme"}}
-
-
-
{{ctx.Locale.Tr "repo.readme_helper"}}
-
-
-
{{ctx.Locale.Tr "repo.readme_helper_desc"}}
-
-
-
-
- {{ctx.Locale.Tr "repo.auto_init"}}
-
-
-
- {{ctx.Locale.Tr "repo.default_branch"}}
-
- {{ctx.Locale.Tr "repo.default_branch_helper"}}
-
-
-
{{ctx.Locale.Tr "repo.object_format"}}
-
-
-
{{.DefaultObjectFormat.Name}}
-
-
-
{{ctx.Locale.Tr "repo.object_format_helper"}}
-
-
-
{{ctx.Locale.Tr "repo.template"}}
-
-
- {{ctx.Locale.Tr "repo.template_helper"}}
-
-
-
-
-
-
-
- {{ctx.Locale.Tr "repo.create_repo"}}
-
+
+ {{ctx.Locale.Tr "repo.new_advanced"}}
+ {{ctx.Locale.Tr "repo.new_advanced_expand"}}
+ {{template "repo/create_advanced" .}}
+
+
+
+ {{ctx.Locale.Tr "repo.create_repo"}}
+
diff --git a/templates/repo/create_advanced.tmpl b/templates/repo/create_advanced.tmpl
new file mode 100644
index 0000000000..c0274701f8
--- /dev/null
+++ b/templates/repo/create_advanced.tmpl
@@ -0,0 +1,45 @@
+
+ {{ctx.Locale.Tr "repo.issue_labels"}}
+
+
+
{{ctx.Locale.Tr "repo.issue_labels_helper"}}
+
+
+
+
+{{$supportedFormatsLength := len .SupportedObjectFormats}}
+{{/* Only offer object format selection if there is an actual choice */}}
+{{if ge $supportedFormatsLength 2}}
+
+ {{ctx.Locale.Tr "repo.object_format"}}
+
+
+
{{.DefaultObjectFormat.Name}}
+
+
+ {{ctx.Locale.Tr "repo.object_format_helper"}}
+
+{{else}}
+
+{{end}}
+
+
+ {{ctx.Locale.Tr "repo.default_branch"}}
+
+ {{ctx.Locale.Tr "repo.default_branch_helper"}}
+
+
+
+
+ {{ctx.Locale.Tr "repo.template_helper"}}
+ {{ctx.Locale.Tr "repo.template_description"}}
+
diff --git a/templates/repo/create_basic.tmpl b/templates/repo/create_basic.tmpl
new file mode 100644
index 0000000000..0396629fef
--- /dev/null
+++ b/templates/repo/create_basic.tmpl
@@ -0,0 +1,47 @@
+
+ {{ctx.Locale.Tr "repo.owner"}}
+
+ {{/* uid id is used by the repo-template code */}}
+
+
+ {{ctx.AvatarUtils.Avatar .ContextUser 28 "mini"}}
+ {{.ContextUser.ShortName 40}}
+
+ {{svg "octicon-triangle-down" 14 "dropdown icon"}}
+
+
+ {{ctx.Locale.Tr "repo.owner_helper"}}
+
+
+ {{ctx.Locale.Tr "repo.repo_name"}}
+
+ {{ctx.Locale.Tr "repo.repo_name_helper"}}
+
+
+
+ {{ctx.Locale.Tr "repo.visibility_helper"}}
+ {{if .IsForcedPrivate}}
+ {{ctx.Locale.Tr "repo.visibility_helper_forced"}}
+ {{end}}
+ {{ctx.Locale.Tr "repo.visibility_description"}}
+
+
+ {{ctx.Locale.Tr "repo.repo_desc"}}
+
+
diff --git a/templates/repo/create_from_template.tmpl b/templates/repo/create_from_template.tmpl
new file mode 100644
index 0000000000..47cda3df02
--- /dev/null
+++ b/templates/repo/create_from_template.tmpl
@@ -0,0 +1,49 @@
+
+ {{ctx.Locale.Tr "repo.template"}}
+
+{{/* If the dropdown is inside the label, the focus works correctly and it is more accessible.
+ However, the Javascript takes the focus and opens the dropdown again immediately after closing.
+ When the user interacts (via mouse or keyboard), the dropdown closes again.
+ Due to the fieldset legend, this solutions is probably acceptable until the dropdown can be fixed properly. */}}
+
+
+
{{.repo_template_name}}
+
+
+
+
+ {{ctx.Locale.Tr "repo.template.items"}}
+
+
+
+ {{ctx.Locale.Tr "repo.template.git_content"}}
+
+
+
+ {{ctx.Locale.Tr "repo.template.webhooks"}}
+
+
+
+ {{ctx.Locale.Tr "repo.template.topics"}}
+
+
+
+ {{ctx.Locale.Tr "repo.template.avatar"}}
+
+
+
+ {{ctx.Locale.Tr "repo.template.issue_labels"}}
+
+
+
+ {{ctx.Locale.Tr "repo.settings.protected_branch"}}
+
+
+
+ {{ctx.Locale.Tr "repo.template.git_hooks"}}
+ {{if not .SignedUser.CanEditGitHook}}
+ {{ctx.Locale.Tr "repo.template.git_hooks_tooltip"}}
+ {{end}}
+
+
diff --git a/templates/repo/create_init.tmpl b/templates/repo/create_init.tmpl
new file mode 100644
index 0000000000..729b44c8e6
--- /dev/null
+++ b/templates/repo/create_init.tmpl
@@ -0,0 +1,56 @@
+
+
+ {{ctx.Locale.Tr "repo.auto_init"}}
+ {{ctx.Locale.Tr "repo.auto_init_description"}}
+
+
+
+
+ .gitignore
+
+
+
{{ctx.Locale.Tr "repo.repo_gitignore_helper"}}
+
+
+ {{ctx.Locale.Tr "repo.repo_gitignore_helper_desc"}}
+
+
+
+ {{ctx.Locale.Tr "repo.license"}}
+
+
+
{{ctx.Locale.Tr "repo.license_helper"}}
+
+
+ {{ctx.Locale.Tr "repo.license_helper_desc" "https://choosealicense.com/"}}
+
+
+ {{$supportedReadmesLength := len .Readmes}}
+ {{/* Only offer README selection if there is an actual choice */}}
+ {{if ge $supportedReadmesLength 2}}
+
+ {{ctx.Locale.Tr "repo.readme"}}
+
+
+
{{ctx.Locale.Tr "repo.readme_helper"}}
+
+
+ {{ctx.Locale.Tr "repo.readme_helper_desc"}}
+
+ {{else}}
+
+ {{end}}
+
diff --git a/tests/e2e/repo-new.test.e2e.ts b/tests/e2e/repo-new.test.e2e.ts
new file mode 100644
index 0000000000..c9cc29ad56
--- /dev/null
+++ b/tests/e2e/repo-new.test.e2e.ts
@@ -0,0 +1,134 @@
+// @watch start
+// templates/repo/create**.tmpl
+// web_src/css/{form,repo}.css
+// @watch end
+
+import {expect} from '@playwright/test';
+import {test, dynamic_id, save_visual, login_user, login} from './utils_e2e.ts';
+import {validate_form} from './shared/forms.ts';
+
+test.beforeAll(async ({browser}, workerInfo) => {
+ await login_user(browser, workerInfo, 'user2');
+});
+
+test('New repo: invalid', async ({browser}, workerInfo) => {
+ const page = await login({browser}, workerInfo);
+ const response = await page.goto('/repo/create');
+ expect(response?.status()).toBe(200);
+ // check that relevant form content is hidden or available
+ await expect(page.getByRole('group', {name: 'Use a template You can select'}).getByRole('combobox')).toBeVisible();
+ await expect(page.getByText('.gitignore Select .gitignore')).toBeHidden();
+ await expect(page.getByText('Labels Select a label set')).toBeHidden();
+ await validate_form({page}, 'fieldset');
+ await save_visual(page);
+
+ await page.getByLabel('Repository name').fill('*invalid');
+ await page.getByRole('button', {name: 'Create repository'}).click();
+ await expect(page.getByText('Repository name should contain only alphanumeric')).toBeVisible();
+ await save_visual(page);
+});
+
+test('New repo: initialize', async ({browser}, workerInfo) => {
+ const page = await login({browser}, workerInfo);
+ const response = await page.goto('/repo/create');
+ expect(response?.status()).toBe(200);
+ // check that relevant form content is hidden or available
+ await expect(page.getByRole('group', {name: 'Use a template You can select'}).getByRole('combobox')).toBeVisible();
+ await expect(page.getByText('.gitignore Select .gitignore')).toBeHidden();
+ // fill initialization section
+ await page.getByText('Start the Git history with').click();
+ await page.getByText('Select .gitignore templates').click();
+ await page.getByLabel('.gitignore Select .gitignore').fill('Go');
+ await page.getByRole('option', {name: 'Go', exact: true}).click();
+ await page.keyboard.press('Escape');
+ await page.getByLabel('License Select a license file').click();
+ await page.getByRole('option', {name: 'MIT', exact: true}).click();
+ await page.keyboard.press('Escape');
+ // add advanced settings
+ await page.getByText('Click to expand').click();
+ await page.getByPlaceholder('master').fill('main');
+ await page.getByLabel('Make repository a template').check();
+
+ await validate_form({page}, 'fieldset');
+ await save_visual(page);
+ const reponame = dynamic_id();
+ await page.getByLabel('Repository name').fill(reponame);
+ await page.getByRole('button', {name: 'Create repository'}).click();
+ await expect(page.getByRole('link', {name: '.gitignore'})).toBeVisible();
+ await expect(page.getByRole('link', {name: 'LICENSE', exact: true})).toBeVisible();
+ if (!workerInfo.project.name.includes('Mobile')) {
+ await expect(page.getByText('Template', {exact: true})).toBeVisible();
+ }
+ await save_visual(page);
+});
+
+test('New repo: initialize later', async ({browser}, workerInfo) => {
+ const page = await login({browser}, workerInfo);
+ const response = await page.goto('/repo/create');
+ expect(response?.status()).toBe(200);
+
+ const reponame = dynamic_id();
+ await page.getByLabel('Repository name').fill(reponame);
+ await page.getByPlaceholder('Enter short description').fill(`Description for repo ${reponame}`);
+ await page.getByText('Click to expand').click();
+ await page.getByPlaceholder('master').fill('devbranch');
+ await validate_form({page}, 'fieldset');
+ await page.getByRole('button', {name: 'Create repository'}).click();
+ expect(page.url()).toBe(`http://localhost:3003/user2/${reponame}`);
+ await expect(page.getByRole('link', {name: 'New file'})).toBeVisible();
+ await expect(page.getByRole('heading', {name: 'Creating a new repository on'})).toBeVisible();
+ await save_visual(page);
+
+ // add a README
+ await page.getByRole('link', {name: 'New file'}).click();
+ // wait for loading spinner to disappear
+ // Otherwise, filling the filename might not populate the tree_path form field or preview tab
+ // The editor has race conditions, likely related to https://codeberg.org/forgejo/forgejo/issues/3371
+ await expect(page.locator('.is-loading')).toBeHidden();
+ await page.locator('.view-lines').click();
+ await page.keyboard.type('# Heading\n\nHello Forgejo!');
+ await page.getByPlaceholder('Name your fileā¦').fill('README.md');
+ await expect(page.getByText('Preview')).toBeVisible();
+ await page.getByPlaceholder('Add ""').fill('My first commit message');
+ await page.getByRole('button', {name: 'Commit changes'}).click();
+ expect(page.url()).toBe(`http://localhost:3003/user2/${reponame}/src/branch/devbranch/README.md`);
+ await expect(page.getByRole('link', {name: 'My first commit message'})).toBeVisible();
+ await expect(page.getByText('Hello Forgejo!')).toBeVisible();
+ await save_visual(page);
+});
+
+test('New repo: from template', async ({browser}, workerInfo) => {
+ test.skip(['Mobile Safari', 'webkit'].includes(workerInfo.project.name), 'WebKit browsers seem to have CORS issues with localhost here.');
+ const page = await login({browser}, workerInfo);
+ const response = await page.goto('/repo/create');
+ expect(response?.status()).toBe(200);
+
+ const reponame = dynamic_id();
+ await page.getByRole('group', {name: 'Use a template You can select'}).getByRole('combobox').click();
+ await page.getByRole('option', {name: 'user27/template1'}).click();
+ await page.getByText('Git content (Default branch)').click();
+ await save_visual(page);
+ await page.getByLabel('Repository name').fill(reponame);
+ await page.getByRole('button', {name: 'Create repository'}).click();
+ await expect(page.getByRole('link', {name: `${reponame}.log`})).toBeVisible();
+ await save_visual(page);
+});
+
+test('New repo: label set', async ({browser}, workerInfo) => {
+ const page = await login({browser}, workerInfo);
+ await page.goto('/repo/create');
+
+ const reponame = dynamic_id();
+ await page.getByText('Click to expand').click();
+ await page.getByLabel('Labels Select a label set').click();
+ await page.getByRole('option', {name: 'Advanced (Kind/Bug, Kind/'}).click();
+ // close dropdown via unrelated click
+ await page.getByText('You can select an existing').click();
+ await save_visual(page);
+ await page.getByLabel('Repository name').fill(reponame);
+ await page.getByRole('button', {name: 'Create repository'}).click();
+ await page.goto(`/user2/${reponame}/issues`);
+ await page.getByRole('link', {name: 'Labels'}).click();
+ await expect(page.getByText('Kind/Bug Something is not')).toBeVisible();
+ await save_visual(page);
+});
diff --git a/tests/e2e/shared/forms.ts b/tests/e2e/shared/forms.ts
index 99ad5a0a6d..fc608489b0 100644
--- a/tests/e2e/shared/forms.ts
+++ b/tests/e2e/shared/forms.ts
@@ -7,6 +7,9 @@ export async function validate_form({page}: {page: Page}, scope: 'form' | 'field
'span[data-tooltip-content',
// exclude weird non-semantic HTML disabled content
'.disabled',
+ // legacy dropdowns don't use semantic HTML yet,
+ // avoid using these where possible
+ '.ui.dropdown',
];
await accessibilityCheck({page}, [scope], excludedElements, []);
diff --git a/tests/e2e/utils_e2e.ts b/tests/e2e/utils_e2e.ts
index 09189e6826..31fc999fb0 100644
--- a/tests/e2e/utils_e2e.ts
+++ b/tests/e2e/utils_e2e.ts
@@ -81,6 +81,14 @@ export async function save_visual(page: Page) {
await page.locator('.flex-item-body > relative-time').filter({hasText: /now|minute/}).evaluateAll((nodes) => {
for (const node of nodes) node.outerHTML = 'relative time in repo';
});
+ // dynamically generated UUIDs
+ await page.getByText('dyn-id-').evaluateAll((nodes) => {
+ for (const node of nodes) node.innerHTML = node.innerHTML.replaceAll(/dyn-id-[a-f0-9-]+/g, 'dynamic-id');
+ });
+ // repeat above, work around https://github.com/microsoft/playwright/issues/34152
+ await page.getByText('dyn-id-').evaluateAll((nodes) => {
+ for (const node of nodes) node.innerHTML = node.innerHTML.replaceAll(/dyn-id-[a-f0-9-]+/g, 'dynamic-id');
+ });
await page.locator('relative-time').evaluateAll((nodes) => {
for (const node of nodes) node.outerHTML = 'time element';
});
@@ -97,6 +105,8 @@ export async function save_visual(page: Page) {
page.locator('#repo_migrating'),
// update order of recently created repos is not fully deterministic
page.locator('.flex-item-main').filter({hasText: 'relative time in repo'}),
+ // dynamic IDs in fixed-size inputs
+ page.locator('input[value*="dyn-id-"]'),
],
});
}
@@ -122,3 +132,8 @@ export async function create_temp_user(browser: Browser, workerInfo: TestInfo, r
return {context: await login_user(browser, workerInfo, username), username};
}
+
+// returns a random string with a pattern that can be filtered for screenshots automatically
+export function dynamic_id() {
+ return `dyn-id-${globalThis.crypto.randomUUID()}`;
+}
diff --git a/tests/integration/repo_generate_test.go b/tests/integration/repo_generate_test.go
index 4bd9f32119..bcee0df417 100644
--- a/tests/integration/repo_generate_test.go
+++ b/tests/integration/repo_generate_test.go
@@ -43,7 +43,7 @@ func assertRepoCreateForm(t *testing.T, htmlDoc *HTMLDoc, owner *user_model.User
// the template menu is loaded client-side, so don't assert the option exists
assert.Equal(t, templateID, htmlDoc.GetInputValueByName("repo_template"), "Unexpected repo_template selection")
- for _, name := range []string{"issue_labels", "gitignores", "license", "readme", "object_format_name"} {
+ for _, name := range []string{"issue_labels", "gitignores", "license", "object_format_name"} {
htmlDoc.AssertDropdownHasOptions(t, name)
}
}
diff --git a/web_src/css/base.css b/web_src/css/base.css
index fc3200d7da..dd761a1b75 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -652,97 +652,6 @@ img.ui.avatar,
background: var(--color-active);
}
-.ui.form .fields.error .field textarea,
-.ui.form .fields.error .field select,
-.ui.form .fields.error .field input:not([type]),
-.ui.form .fields.error .field input[type="date"],
-.ui.form .fields.error .field input[type="datetime-local"],
-.ui.form .fields.error .field input[type="email"],
-.ui.form .fields.error .field input[type="number"],
-.ui.form .fields.error .field input[type="password"],
-.ui.form .fields.error .field input[type="search"],
-.ui.form .fields.error .field input[type="tel"],
-.ui.form .fields.error .field input[type="time"],
-.ui.form .fields.error .field input[type="text"],
-.ui.form .fields.error .field input[type="file"],
-.ui.form .fields.error .field input[type="url"],
-.ui.form .fields.error .field .ui.dropdown,
-.ui.form .fields.error .field .ui.dropdown .item,
-.ui.form .field.error .ui.dropdown,
-.ui.form .field.error .ui.dropdown .text,
-.ui.form .field.error .ui.dropdown .item,
-.ui.form .field.error textarea,
-.ui.form .field.error select,
-.ui.form .field.error input:not([type]),
-.ui.form .field.error input[type="date"],
-.ui.form .field.error input[type="datetime-local"],
-.ui.form .field.error input[type="email"],
-.ui.form .field.error input[type="number"],
-.ui.form .field.error input[type="password"],
-.ui.form .field.error input[type="search"],
-.ui.form .field.error input[type="tel"],
-.ui.form .field.error input[type="time"],
-.ui.form .field.error input[type="text"],
-.ui.form .field.error input[type="file"],
-.ui.form .field.error input[type="url"],
-.ui.form .field.error select:focus,
-.ui.form .field.error input:not([type]):focus,
-.ui.form .field.error input[type="date"]:focus,
-.ui.form .field.error input[type="datetime-local"]:focus,
-.ui.form .field.error input[type="email"]:focus,
-.ui.form .field.error input[type="number"]:focus,
-.ui.form .field.error input[type="password"]:focus,
-.ui.form .field.error input[type="search"]:focus,
-.ui.form .field.error input[type="tel"]:focus,
-.ui.form .field.error input[type="time"]:focus,
-.ui.form .field.error input[type="text"]:focus,
-.ui.form .field.error input[type="file"]:focus,
-.ui.form .field.error input[type="url"]:focus {
- background-color: var(--color-error-bg);
- border-color: var(--color-error-border);
- color: var(--color-error-text);
-}
-
-.ui.form .fields.error .field .ui.dropdown,
-.ui.form .field.error .ui.dropdown,
-.ui.form .fields.error .field .ui.dropdown:hover,
-.ui.form .field.error .ui.dropdown:hover {
- border-color: var(--color-error-border) !important;
-}
-
-.ui.form .fields.error .field .ui.dropdown .menu .item:hover,
-.ui.form .field.error .ui.dropdown .menu .item:hover {
- background-color: var(--color-error-bg-hover);
-}
-
-.ui.form .fields.error .field .ui.dropdown .menu .active.item,
-.ui.form .field.error .ui.dropdown .menu .active.item {
- background-color: var(--color-error-bg-active) !important;
-}
-
-.ui.form .fields.error .dropdown .menu,
-.ui.form .field.error .dropdown .menu {
- border-color: var(--color-error-border) !important;
-}
-
-input:-webkit-autofill,
-input:-webkit-autofill:focus,
-input:-webkit-autofill:hover,
-input:-webkit-autofill:active,
-.ui.form .field.field input:-webkit-autofill,
-.ui.form .field.field input:-webkit-autofill:focus,
-.ui.form .field.field input:-webkit-autofill:hover,
-.ui.form .field.field input:-webkit-autofill:active {
- -webkit-background-clip: text;
- -webkit-text-fill-color: var(--color-text);
- box-shadow: 0 0 0 100px var(--color-primary-light-6) inset !important;
- border-color: var(--color-primary-light-4) !important;
-}
-
-.ui.form .field.muted {
- opacity: var(--opacity-disabled);
-}
-
.text.primary {
color: var(--color-primary) !important;
}
diff --git a/web_src/css/form.css b/web_src/css/form.css
index fb9364db45..bf50114344 100644
--- a/web_src/css/form.css
+++ b/web_src/css/form.css
@@ -18,6 +18,11 @@ fieldset label:has(input[type="number"]) {
font-weight: var(--font-weight-medium);
}
+/* override inline style on custom input elements */
+fieldset label .ui.dropdown {
+ width: 100% !important;
+}
+
fieldset .help {
font-weight: var(--font-weight-normal);
}
@@ -27,9 +32,17 @@ fieldset .help {
padding-bottom: 0;
}
-fieldset input[type="checkbox"],
-fieldset input[type="radio"] {
+fieldset label > input,
+fieldset label > textarea,
+fieldset label > .ui.dropdown,
+fieldset label + .ui.dropdown {
+ margin-top: 0.28rem !important;
+}
+
+fieldset label > input[type="checkbox"],
+fieldset label > input[type="radio"] {
margin-right: 0.75em;
+ margin-top: 0 !important;
vertical-align: initial !important; /* overrides a semantic.css rule, remove when obsolete */
}
@@ -142,6 +155,101 @@ textarea:focus,
color: var(--color-input-text);
}
+/* error messages */
+fieldset label.error textarea,
+fieldset label.error select,
+fieldset label.error input,
+.ui.form .fields.error .field textarea,
+.ui.form .fields.error .field select,
+.ui.form .fields.error .field input:not([type]),
+.ui.form .fields.error .field input[type="date"],
+.ui.form .fields.error .field input[type="datetime-local"],
+.ui.form .fields.error .field input[type="email"],
+.ui.form .fields.error .field input[type="number"],
+.ui.form .fields.error .field input[type="password"],
+.ui.form .fields.error .field input[type="search"],
+.ui.form .fields.error .field input[type="tel"],
+.ui.form .fields.error .field input[type="time"],
+.ui.form .fields.error .field input[type="text"],
+.ui.form .fields.error .field input[type="file"],
+.ui.form .fields.error .field input[type="url"],
+.ui.form .fields.error .field .ui.dropdown,
+.ui.form .fields.error .field .ui.dropdown .item,
+.ui.form .field.error .ui.dropdown,
+.ui.form .field.error .ui.dropdown .text,
+.ui.form .field.error .ui.dropdown .item,
+.ui.form .field.error textarea,
+.ui.form .field.error select,
+.ui.form .field.error input:not([type]),
+.ui.form .field.error input[type="date"],
+.ui.form .field.error input[type="datetime-local"],
+.ui.form .field.error input[type="email"],
+.ui.form .field.error input[type="number"],
+.ui.form .field.error input[type="password"],
+.ui.form .field.error input[type="search"],
+.ui.form .field.error input[type="tel"],
+.ui.form .field.error input[type="time"],
+.ui.form .field.error input[type="text"],
+.ui.form .field.error input[type="file"],
+.ui.form .field.error input[type="url"],
+.ui.form .field.error select:focus,
+.ui.form .field.error input:not([type]):focus,
+.ui.form .field.error input[type="date"]:focus,
+.ui.form .field.error input[type="datetime-local"]:focus,
+.ui.form .field.error input[type="email"]:focus,
+.ui.form .field.error input[type="number"]:focus,
+.ui.form .field.error input[type="password"]:focus,
+.ui.form .field.error input[type="search"]:focus,
+.ui.form .field.error input[type="tel"]:focus,
+.ui.form .field.error input[type="time"]:focus,
+.ui.form .field.error input[type="text"]:focus,
+.ui.form .field.error input[type="file"]:focus,
+.ui.form .field.error input[type="url"]:focus {
+ background-color: var(--color-error-bg);
+ border-color: var(--color-error-border);
+ color: var(--color-error-text);
+}
+
+.ui.form .fields.error .field .ui.dropdown,
+.ui.form .field.error .ui.dropdown,
+.ui.form .fields.error .field .ui.dropdown:hover,
+.ui.form .field.error .ui.dropdown:hover {
+ border-color: var(--color-error-border) !important;
+}
+
+.ui.form .fields.error .field .ui.dropdown .menu .item:hover,
+.ui.form .field.error .ui.dropdown .menu .item:hover {
+ background-color: var(--color-error-bg-hover);
+}
+
+.ui.form .fields.error .field .ui.dropdown .menu .active.item,
+.ui.form .field.error .ui.dropdown .menu .active.item {
+ background-color: var(--color-error-bg-active) !important;
+}
+
+.ui.form .fields.error .dropdown .menu,
+.ui.form .field.error .dropdown .menu {
+ border-color: var(--color-error-border) !important;
+}
+
+input:-webkit-autofill,
+input:-webkit-autofill:focus,
+input:-webkit-autofill:hover,
+input:-webkit-autofill:active,
+.ui.form .field.field input:-webkit-autofill,
+.ui.form .field.field input:-webkit-autofill:focus,
+.ui.form .field.field input:-webkit-autofill:hover,
+.ui.form .field.field input:-webkit-autofill:active {
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: var(--color-text);
+ box-shadow: 0 0 0 100px var(--color-primary-light-6) inset !important;
+ border-color: var(--color-primary-light-4) !important;
+}
+
+.ui.form .field.muted {
+ opacity: var(--opacity-disabled);
+}
+
.ui.form .field > label,
.ui.form .inline.fields > label,
.ui.form .inline.fields .field > label,
@@ -400,14 +508,12 @@ textarea:focus,
.repository.new.fork form .header {
padding-left: 280px !important;
}
- .repository.new.repo form .inline.field > label,
.repository.new.migrate form .inline.field > label,
.repository.new.fork form .inline.field > label {
text-align: right;
width: 250px !important;
word-wrap: break-word;
}
- .repository.new.repo form .help,
.repository.new.migrate form .help,
.repository.new.fork form .help {
margin-left: 265px !important;
@@ -417,10 +523,8 @@ textarea:focus,
.repository.new.fork form .optional .title {
margin-left: 250px !important;
}
- .repository.new.repo form .inline.field > input,
.repository.new.migrate form .inline.field > input,
.repository.new.fork form .inline.field > input,
- .repository.new.repo form .inline.field > textarea,
.repository.new.migrate form .inline.field > textarea,
.repository.new.fork form .inline.field > textarea {
width: 50%;
@@ -440,7 +544,6 @@ textarea:focus,
}
}
-.repository.new.repo form .dropdown .text,
.repository.new.migrate form .dropdown .text,
.repository.new.fork form .dropdown .text {
margin-right: 0 !important;
@@ -453,7 +556,6 @@ textarea:focus,
text-align: center;
}
-.repository.new.repo form .selection.dropdown,
.repository.new.migrate form .selection.dropdown,
.repository.new.fork form .selection.dropdown,
.repository.new.fork form .field a {
@@ -490,10 +592,6 @@ textarea:focus,
}
}
-.repository.new.repo .ui.form .selection.dropdown:not(.owner) {
- width: 50% !important;
-}
-
@media (max-width: 767.98px) {
.repository.new.repo .ui.form .selection.dropdown:not(.owner) {
width: 100% !important;
diff --git a/web_src/css/repo.css b/web_src/css/repo.css
index 484cdf1c85..675e5565bd 100644
--- a/web_src/css/repo.css
+++ b/web_src/css/repo.css
@@ -4,10 +4,6 @@
user-select: none;
}
-.repository .owner.dropdown {
- min-width: 40% !important;
-}
-
.repository .unicode-escaped .escaped-code-point[data-escaped]::before {
visibility: visible;
content: attr(data-escaped);