mirror of
https://github.com/go-gitea/gitea
synced 2024-10-19 20:10:10 +02:00
Merge branch 'main' into add-repo-license-display
This commit is contained in:
commit
60bfdea091
@ -136,6 +136,12 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
# some markdown that will only be visible once the issue has been created
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
This issue was created by an issue **template** :)
|
||||
visible: [content]
|
||||
- type: input
|
||||
id: contact
|
||||
attributes:
|
||||
@ -187,11 +193,16 @@ body:
|
||||
options:
|
||||
- label: I agree to follow this project's Code of Conduct
|
||||
required: true
|
||||
- label: I have also read the CONTRIBUTION.MD
|
||||
required: true
|
||||
visible: [form]
|
||||
- label: This is a TODO only visible after issue creation
|
||||
visible: [content]
|
||||
```
|
||||
|
||||
### Markdown
|
||||
|
||||
You can use a `markdown` element to display Markdown in your form that provides extra context to the user, but is not submitted.
|
||||
You can use a `markdown` element to display Markdown in your form that provides extra context to the user, but is not submitted by default.
|
||||
|
||||
Attributes:
|
||||
|
||||
@ -199,6 +210,8 @@ Attributes:
|
||||
|-------|--------------------------------------------------------------|----------|--------|---------|--------------|
|
||||
| value | The text that is rendered. Markdown formatting is supported. | Required | String | - | - |
|
||||
|
||||
visible: Default is **[form]**
|
||||
|
||||
### Textarea
|
||||
|
||||
You can use a `textarea` element to add a multi-line text field to your form. Contributors can also attach files in `textarea` fields.
|
||||
@ -219,6 +232,8 @@ Validations:
|
||||
|----------|------------------------------------------------------|----------|---------|---------|--------------|
|
||||
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
|
||||
|
||||
visible: Default is **[form, content]**
|
||||
|
||||
### Input
|
||||
|
||||
You can use an `input` element to add a single-line text field to your form.
|
||||
@ -240,6 +255,8 @@ Validations:
|
||||
| is_number | Prevents form submission until element is filled with a number. | Optional | Boolean | false | - |
|
||||
| regex | Prevents form submission until element is filled with a value that match the regular expression. | Optional | String | - | a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) |
|
||||
|
||||
visible: Default is **[form, content]**
|
||||
|
||||
### Dropdown
|
||||
|
||||
You can use a `dropdown` element to add a dropdown menu in your form.
|
||||
@ -259,6 +276,8 @@ Validations:
|
||||
|----------|------------------------------------------------------|----------|---------|---------|--------------|
|
||||
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
|
||||
|
||||
visible: Default is **[form, content]**
|
||||
|
||||
### Checkboxes
|
||||
|
||||
You can use the `checkboxes` element to add a set of checkboxes to your form.
|
||||
@ -266,17 +285,20 @@ You can use the `checkboxes` element to add a set of checkboxes to your form.
|
||||
Attributes:
|
||||
|
||||
| Key | Description | Required | Type | Default | Valid values |
|
||||
|-------------|-------------------------------------------------------------------------------------------------------|----------|--------|--------------|--------------|
|
||||
| ----------- | ----------------------------------------------------------------------------------------------------- | -------- | ------ | ------------ | ------------ |
|
||||
| label | A brief description of the expected user input, which is displayed in the form. | Required | String | - | - |
|
||||
| description | A description of the set of checkboxes, which is displayed in the form. Supports Markdown formatting. | Optional | String | Empty String | - |
|
||||
| options | An array of checkboxes that the user can select. For syntax, see below. | Required | Array | - | - |
|
||||
|
||||
For each value in the options array, you can set the following keys.
|
||||
|
||||
| Key | Description | Required | Type | Default | Options |
|
||||
|----------|------------------------------------------------------------------------------------------------------------------------------------------|----------|---------|---------|---------|
|
||||
| label | The identifier for the option, which is displayed in the form. Markdown is supported for bold or italic text formatting, and hyperlinks. | Required | String | - | - |
|
||||
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
|
||||
| Key | Description | Required | Type | Default | Options |
|
||||
|--------------|------------------------------------------------------------------------------------------------------------------------------------------|----------|--------------|---------|---------|
|
||||
| label | The identifier for the option, which is displayed in the form. Markdown is supported for bold or italic text formatting, and hyperlinks. | Required | String | - | - |
|
||||
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
|
||||
| visible | Whether a specific checkbox appears in the form only, in the created issue only, or both. Valid options are "form" and "content". | Optional | String array | false | - |
|
||||
|
||||
visible: Default is **[form, content]**
|
||||
|
||||
## Syntax for issue config
|
||||
|
||||
@ -292,15 +314,15 @@ contact_links:
|
||||
|
||||
### Possible Options
|
||||
|
||||
| Key | Description | Type | Default |
|
||||
|----------------------|-------------------------------------------------------------------------------------------------------|--------------------|----------------|
|
||||
| blank_issues_enabled | If set to false, the User is forced to use a Template | Boolean | true |
|
||||
| contact_links | Custom Links to show in the Choose Box | Contact Link Array | Empty Array |
|
||||
| Key | Description | Type | Default |
|
||||
|----------------------|-------------------------------------------------------|--------------------|-------------|
|
||||
| blank_issues_enabled | If set to false, the User is forced to use a Template | Boolean | true |
|
||||
| contact_links | Custom Links to show in the Choose Box | Contact Link Array | Empty Array |
|
||||
|
||||
### Contact Link
|
||||
|
||||
| Key | Description | Type | Required |
|
||||
|----------------------|-------------------------------------------------------------------------------------------------------|---------|----------|
|
||||
| name | the name of your link | String | true |
|
||||
| url | The URL of your Link | String | true |
|
||||
| about | A short description of your Link | String | true |
|
||||
| Key | Description | Type | Required |
|
||||
|-------|----------------------------------|--------|----------|
|
||||
| name | the name of your link | String | true |
|
||||
| url | The URL of your Link | String | true |
|
||||
| about | A short description of your Link | String | true |
|
||||
|
@ -122,7 +122,13 @@ func validateRequired(field *api.IssueFormField, idx int) error {
|
||||
// The label is not required for a markdown or checkboxes field
|
||||
return nil
|
||||
}
|
||||
return validateBoolItem(newErrorPosition(idx, field.Type), field.Validations, "required")
|
||||
if err := validateBoolItem(newErrorPosition(idx, field.Type), field.Validations, "required"); err != nil {
|
||||
return err
|
||||
}
|
||||
if required, _ := field.Validations["required"].(bool); required && !field.VisibleOnForm() {
|
||||
return newErrorPosition(idx, field.Type).Errorf("can not require a hidden field")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateID(field *api.IssueFormField, idx int, ids container.Set[string]) error {
|
||||
@ -172,10 +178,38 @@ func validateOptions(field *api.IssueFormField, idx int) error {
|
||||
return position.Errorf("'label' is required and should be a string")
|
||||
}
|
||||
|
||||
if visibility, ok := opt["visible"]; ok {
|
||||
visibilityList, ok := visibility.([]any)
|
||||
if !ok {
|
||||
return position.Errorf("'visible' should be list")
|
||||
}
|
||||
for _, visibleType := range visibilityList {
|
||||
visibleType, ok := visibleType.(string)
|
||||
if !ok || !(visibleType == "form" || visibleType == "content") {
|
||||
return position.Errorf("'visible' list can only contain strings of 'form' and 'content'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if required, ok := opt["required"]; ok {
|
||||
if _, ok := required.(bool); !ok {
|
||||
return position.Errorf("'required' should be a bool")
|
||||
}
|
||||
|
||||
// validate if hidden field is required
|
||||
if visibility, ok := opt["visible"]; ok {
|
||||
visibilityList, _ := visibility.([]any)
|
||||
isVisible := false
|
||||
for _, v := range visibilityList {
|
||||
if vv, _ := v.(string); vv == "form" {
|
||||
isVisible = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isVisible {
|
||||
return position.Errorf("can not require a hidden checkbox")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -238,7 +272,7 @@ func RenderToMarkdown(template *api.IssueTemplate, values url.Values) string {
|
||||
IssueFormField: field,
|
||||
Values: values,
|
||||
}
|
||||
if f.ID == "" {
|
||||
if f.ID == "" || !f.VisibleInContent() {
|
||||
continue
|
||||
}
|
||||
f.WriteTo(builder)
|
||||
@ -253,11 +287,6 @@ type valuedField struct {
|
||||
}
|
||||
|
||||
func (f *valuedField) WriteTo(builder *strings.Builder) {
|
||||
if f.Type == api.IssueFormFieldTypeMarkdown {
|
||||
// markdown blocks do not appear in output
|
||||
return
|
||||
}
|
||||
|
||||
// write label
|
||||
if !f.HideLabel() {
|
||||
_, _ = fmt.Fprintf(builder, "### %s\n\n", f.Label())
|
||||
@ -269,6 +298,9 @@ func (f *valuedField) WriteTo(builder *strings.Builder) {
|
||||
switch f.Type {
|
||||
case api.IssueFormFieldTypeCheckboxes:
|
||||
for _, option := range f.Options() {
|
||||
if !option.VisibleInContent() {
|
||||
continue
|
||||
}
|
||||
checked := " "
|
||||
if option.IsChecked() {
|
||||
checked = "x"
|
||||
@ -302,6 +334,10 @@ func (f *valuedField) WriteTo(builder *strings.Builder) {
|
||||
} else {
|
||||
_, _ = fmt.Fprintf(builder, "%s\n", value)
|
||||
}
|
||||
case api.IssueFormFieldTypeMarkdown:
|
||||
if value, ok := f.Attributes["value"].(string); ok {
|
||||
_, _ = fmt.Fprintf(builder, "%s\n", value)
|
||||
}
|
||||
}
|
||||
_, _ = fmt.Fprintln(builder)
|
||||
}
|
||||
@ -314,6 +350,9 @@ func (f *valuedField) Label() string {
|
||||
}
|
||||
|
||||
func (f *valuedField) HideLabel() bool {
|
||||
if f.Type == api.IssueFormFieldTypeMarkdown {
|
||||
return true
|
||||
}
|
||||
if label, ok := f.Attributes["hide_label"].(bool); ok {
|
||||
return label
|
||||
}
|
||||
@ -385,6 +424,22 @@ func (o *valuedOption) IsChecked() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *valuedOption) VisibleInContent() bool {
|
||||
if o.field.Type == api.IssueFormFieldTypeCheckboxes {
|
||||
if vs, ok := o.data.(map[string]any); ok {
|
||||
if vl, ok := vs["visible"].([]any); ok {
|
||||
for _, v := range vl {
|
||||
if vv, _ := v.(string); vv == "content" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var minQuotesRegex = regexp.MustCompilePOSIX("^`{3,}")
|
||||
|
||||
// minQuotes return 3 or more back-quotes.
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -318,6 +319,42 @@ body:
|
||||
`,
|
||||
wantErr: "body[0](checkboxes), option[0]: 'required' should be a bool",
|
||||
},
|
||||
{
|
||||
name: "field is required but hidden",
|
||||
content: `
|
||||
name: "test"
|
||||
about: "this is about"
|
||||
body:
|
||||
- type: "input"
|
||||
id: "1"
|
||||
attributes:
|
||||
label: "a"
|
||||
validations:
|
||||
required: true
|
||||
visible: [content]
|
||||
`,
|
||||
wantErr: "body[0](input): can not require a hidden field",
|
||||
},
|
||||
{
|
||||
name: "checkboxes is required but hidden",
|
||||
content: `
|
||||
name: "test"
|
||||
about: "this is about"
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: "1"
|
||||
attributes:
|
||||
label: Label of checkboxes
|
||||
description: Description of checkboxes
|
||||
options:
|
||||
- label: Option 1
|
||||
required: false
|
||||
- label: Required and hidden
|
||||
required: true
|
||||
visible: [content]
|
||||
`,
|
||||
wantErr: "body[0](checkboxes), option[1]: can not require a hidden checkbox",
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
content: `
|
||||
@ -374,8 +411,11 @@ body:
|
||||
required: true
|
||||
- label: Option 2 of checkboxes
|
||||
required: false
|
||||
- label: Option 3 of checkboxes
|
||||
- label: Hidden Option 3 of checkboxes
|
||||
visible: [content]
|
||||
- label: Required but not submitted
|
||||
required: true
|
||||
visible: [form]
|
||||
`,
|
||||
want: &api.IssueTemplate{
|
||||
Name: "Name",
|
||||
@ -390,6 +430,7 @@ body:
|
||||
Attributes: map[string]any{
|
||||
"value": "Value of the markdown",
|
||||
},
|
||||
Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm},
|
||||
},
|
||||
{
|
||||
Type: "textarea",
|
||||
@ -404,6 +445,7 @@ body:
|
||||
Validations: map[string]any{
|
||||
"required": true,
|
||||
},
|
||||
Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm, api.IssueFormFieldVisibleContent},
|
||||
},
|
||||
{
|
||||
Type: "input",
|
||||
@ -419,6 +461,7 @@ body:
|
||||
"is_number": true,
|
||||
"regex": "[a-zA-Z0-9]+",
|
||||
},
|
||||
Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm, api.IssueFormFieldVisibleContent},
|
||||
},
|
||||
{
|
||||
Type: "dropdown",
|
||||
@ -436,6 +479,7 @@ body:
|
||||
Validations: map[string]any{
|
||||
"required": true,
|
||||
},
|
||||
Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm, api.IssueFormFieldVisibleContent},
|
||||
},
|
||||
{
|
||||
Type: "checkboxes",
|
||||
@ -446,9 +490,11 @@ body:
|
||||
"options": []any{
|
||||
map[string]any{"label": "Option 1 of checkboxes", "required": true},
|
||||
map[string]any{"label": "Option 2 of checkboxes", "required": false},
|
||||
map[string]any{"label": "Option 3 of checkboxes", "required": true},
|
||||
map[string]any{"label": "Hidden Option 3 of checkboxes", "visible": []string{"content"}},
|
||||
map[string]any{"label": "Required but not submitted", "required": true, "visible": []string{"form"}},
|
||||
},
|
||||
},
|
||||
Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm, api.IssueFormFieldVisibleContent},
|
||||
},
|
||||
},
|
||||
FileName: "test.yaml",
|
||||
@ -467,7 +513,12 @@ body:
|
||||
- type: markdown
|
||||
id: id1
|
||||
attributes:
|
||||
value: Value of the markdown
|
||||
value: Value of the markdown shown in form
|
||||
- type: markdown
|
||||
id: id2
|
||||
attributes:
|
||||
value: Value of the markdown shown in created issue
|
||||
visible: [content]
|
||||
`,
|
||||
want: &api.IssueTemplate{
|
||||
Name: "Name",
|
||||
@ -480,8 +531,17 @@ body:
|
||||
Type: "markdown",
|
||||
ID: "id1",
|
||||
Attributes: map[string]any{
|
||||
"value": "Value of the markdown",
|
||||
"value": "Value of the markdown shown in form",
|
||||
},
|
||||
Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm},
|
||||
},
|
||||
{
|
||||
Type: "markdown",
|
||||
ID: "id2",
|
||||
Attributes: map[string]any{
|
||||
"value": "Value of the markdown shown in created issue",
|
||||
},
|
||||
Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleContent},
|
||||
},
|
||||
},
|
||||
FileName: "test.yaml",
|
||||
@ -515,6 +575,7 @@ body:
|
||||
Attributes: map[string]any{
|
||||
"value": "Value of the markdown",
|
||||
},
|
||||
Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm},
|
||||
},
|
||||
},
|
||||
FileName: "test.yaml",
|
||||
@ -548,6 +609,7 @@ body:
|
||||
Attributes: map[string]any{
|
||||
"value": "Value of the markdown",
|
||||
},
|
||||
Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm},
|
||||
},
|
||||
},
|
||||
FileName: "test.yaml",
|
||||
@ -622,9 +684,14 @@ body:
|
||||
- type: markdown
|
||||
id: id1
|
||||
attributes:
|
||||
value: Value of the markdown
|
||||
- type: textarea
|
||||
value: Value of the markdown shown in form
|
||||
- type: markdown
|
||||
id: id2
|
||||
attributes:
|
||||
value: Value of the markdown shown in created issue
|
||||
visible: [content]
|
||||
- type: textarea
|
||||
id: id3
|
||||
attributes:
|
||||
label: Label of textarea
|
||||
description: Description of textarea
|
||||
@ -634,7 +701,7 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: id3
|
||||
id: id4
|
||||
attributes:
|
||||
label: Label of input
|
||||
description: Description of input
|
||||
@ -646,7 +713,7 @@ body:
|
||||
is_number: true
|
||||
regex: "[a-zA-Z0-9]+"
|
||||
- type: dropdown
|
||||
id: id4
|
||||
id: id5
|
||||
attributes:
|
||||
label: Label of dropdown
|
||||
description: Description of dropdown
|
||||
@ -658,7 +725,7 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: id5
|
||||
id: id6
|
||||
attributes:
|
||||
label: Label of checkboxes
|
||||
description: Description of checkboxes
|
||||
@ -669,20 +736,26 @@ body:
|
||||
required: false
|
||||
- label: Option 3 of checkboxes
|
||||
required: true
|
||||
visible: [form]
|
||||
- label: Hidden Option of checkboxes
|
||||
visible: [content]
|
||||
`,
|
||||
values: map[string][]string{
|
||||
"form-field-id2": {"Value of id2"},
|
||||
"form-field-id3": {"Value of id3"},
|
||||
"form-field-id4": {"0,1"},
|
||||
"form-field-id5-0": {"on"},
|
||||
"form-field-id5-2": {"on"},
|
||||
"form-field-id4": {"Value of id4"},
|
||||
"form-field-id5": {"0,1"},
|
||||
"form-field-id6-0": {"on"},
|
||||
"form-field-id6-2": {"on"},
|
||||
},
|
||||
},
|
||||
want: `### Label of textarea
|
||||
|
||||
` + "```bash\nValue of id2\n```" + `
|
||||
want: `Value of the markdown shown in created issue
|
||||
|
||||
Value of id3
|
||||
### Label of textarea
|
||||
|
||||
` + "```bash\nValue of id3\n```" + `
|
||||
|
||||
Value of id4
|
||||
|
||||
### Label of dropdown
|
||||
|
||||
@ -692,7 +765,7 @@ Option 1 of dropdown, Option 2 of dropdown
|
||||
|
||||
- [x] Option 1 of checkboxes
|
||||
- [ ] Option 2 of checkboxes
|
||||
- [x] Option 3 of checkboxes
|
||||
- [ ] Hidden Option of checkboxes
|
||||
|
||||
`,
|
||||
},
|
||||
@ -704,7 +777,7 @@ Option 1 of dropdown, Option 2 of dropdown
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := RenderToMarkdown(template, tt.args.values); got != tt.want {
|
||||
t.Errorf("RenderToMarkdown() = %v, want %v", got, tt.want)
|
||||
assert.EqualValues(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -128,9 +128,18 @@ func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
|
||||
}
|
||||
}
|
||||
for i, v := range it.Fields {
|
||||
// set default id value
|
||||
if v.ID == "" {
|
||||
v.ID = strconv.Itoa(i)
|
||||
}
|
||||
// set default visibility
|
||||
if v.Visible == nil {
|
||||
v.Visible = []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm}
|
||||
// markdown is not submitted by default
|
||||
if v.Type != api.IssueFormFieldTypeMarkdown {
|
||||
v.Visible = append(v.Visible, api.IssueFormFieldVisibleContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ package structs
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -141,12 +142,37 @@ const (
|
||||
// IssueFormField represents a form field
|
||||
// swagger:model
|
||||
type IssueFormField struct {
|
||||
Type IssueFormFieldType `json:"type" yaml:"type"`
|
||||
ID string `json:"id" yaml:"id"`
|
||||
Attributes map[string]any `json:"attributes" yaml:"attributes"`
|
||||
Validations map[string]any `json:"validations" yaml:"validations"`
|
||||
Type IssueFormFieldType `json:"type" yaml:"type"`
|
||||
ID string `json:"id" yaml:"id"`
|
||||
Attributes map[string]any `json:"attributes" yaml:"attributes"`
|
||||
Validations map[string]any `json:"validations" yaml:"validations"`
|
||||
Visible []IssueFormFieldVisible `json:"visible,omitempty"`
|
||||
}
|
||||
|
||||
func (iff IssueFormField) VisibleOnForm() bool {
|
||||
if len(iff.Visible) == 0 {
|
||||
return true
|
||||
}
|
||||
return slices.Contains(iff.Visible, IssueFormFieldVisibleForm)
|
||||
}
|
||||
|
||||
func (iff IssueFormField) VisibleInContent() bool {
|
||||
if len(iff.Visible) == 0 {
|
||||
// we have our markdown exception
|
||||
return iff.Type != IssueFormFieldTypeMarkdown
|
||||
}
|
||||
return slices.Contains(iff.Visible, IssueFormFieldVisibleContent)
|
||||
}
|
||||
|
||||
// IssueFormFieldVisible defines issue form field visible
|
||||
// swagger:model
|
||||
type IssueFormFieldVisible string
|
||||
|
||||
const (
|
||||
IssueFormFieldVisibleForm IssueFormFieldVisible = "form"
|
||||
IssueFormFieldVisibleContent IssueFormFieldVisible = "content"
|
||||
)
|
||||
|
||||
// IssueTemplate represents an issue template for a repository
|
||||
// swagger:model
|
||||
type IssueTemplate struct {
|
||||
|
@ -9,7 +9,9 @@ import (
|
||||
)
|
||||
|
||||
// MockLocale provides a mocked locale without any translations
|
||||
type MockLocale struct{}
|
||||
type MockLocale struct {
|
||||
Lang, LangName string // these fields are used directly in templates: ctx.Locale.Lang
|
||||
}
|
||||
|
||||
var _ Locale = (*MockLocale)(nil)
|
||||
|
||||
|
@ -144,7 +144,7 @@ func Match(tags ...language.Tag) language.Tag {
|
||||
// locale represents the information of localization.
|
||||
type locale struct {
|
||||
i18n.Locale
|
||||
Lang, LangName string // these fields are used directly in templates: .i18n.Lang
|
||||
Lang, LangName string // these fields are used directly in templates: ctx.Locale.Lang
|
||||
msgPrinter *message.Printer
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,8 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/contexttest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -113,3 +115,18 @@ func TestMilestonesForSpecificRepo(t *testing.T) {
|
||||
assert.Len(t, ctx.Data["Milestones"], 1)
|
||||
assert.Len(t, ctx.Data["Repos"], 2) // both repo 42 and 1 have milestones and both are owned by user 2
|
||||
}
|
||||
|
||||
func TestDashboardPagination(t *testing.T) {
|
||||
ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
|
||||
page := context.NewPagination(10, 3, 1, 3)
|
||||
|
||||
setting.AppSubURL = "/SubPath"
|
||||
out, err := ctx.RenderToHTML("base/paginate", map[string]any{"Link": setting.AppSubURL, "Page": page})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, out, `<a class=" item navigation" href="/SubPath/?page=2">`)
|
||||
|
||||
setting.AppSubURL = ""
|
||||
out, err = ctx.RenderToHTML("base/paginate", map[string]any{"Link": setting.AppSubURL, "Page": page})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, out, `<a class=" item navigation" href="/?page=2">`)
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
@ -61,7 +62,8 @@ func MockContext(t *testing.T, reqPath string, opts ...MockContextOption) (*cont
|
||||
base.Locale = &translation.MockLocale{}
|
||||
|
||||
ctx := context.NewWebContext(base, opt.Render, nil)
|
||||
|
||||
ctx.PageData = map[string]any{}
|
||||
ctx.Data["PageStartTime"] = time.Now()
|
||||
chiCtx := chi.NewRouteContext()
|
||||
ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx)
|
||||
return ctx, resp
|
||||
|
@ -1,13 +1,15 @@
|
||||
{{$paginationLink := .Page.GetParams}}
|
||||
{{$paginationParams := .Page.GetParams}}
|
||||
{{$paginationLink := $.Link}}
|
||||
{{if eq $paginationLink AppSubUrl}}{{$paginationLink = print $paginationLink "/"}}{{end}}
|
||||
{{with .Page.Paginater}}
|
||||
{{if gt .TotalPages 1}}
|
||||
<div class="center page buttons">
|
||||
<div class="ui borderless pagination menu">
|
||||
<a class="{{if .IsFirst}}disabled{{end}} item navigation" {{if not .IsFirst}}href="{{$.Link}}{{if $paginationLink}}?{{$paginationLink}}{{end}}"{{end}}>
|
||||
<a class="{{if .IsFirst}}disabled{{end}} item navigation" {{if not .IsFirst}}href="{{$paginationLink}}{{if $paginationParams}}?{{$paginationParams}}{{end}}"{{end}}>
|
||||
{{svg "gitea-double-chevron-left" 16 "gt-mr-2"}}
|
||||
<span class="navigation_label">{{ctx.Locale.Tr "admin.first_page"}}</span>
|
||||
</a>
|
||||
<a class="{{if not .HasPrevious}}disabled{{end}} item navigation" {{if .HasPrevious}}href="{{$.Link}}?page={{.Previous}}{{if $paginationLink}}&{{$paginationLink}}{{end}}"{{end}}>
|
||||
<a class="{{if not .HasPrevious}}disabled{{end}} item navigation" {{if .HasPrevious}}href="{{$paginationLink}}?page={{.Previous}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
|
||||
{{svg "octicon-chevron-left" 16 "gt-mr-2"}}
|
||||
<span class="navigation_label">{{ctx.Locale.Tr "repo.issues.previous"}}</span>
|
||||
</a>
|
||||
@ -15,14 +17,14 @@
|
||||
{{if eq .Num -1}}
|
||||
<a class="disabled item">...</a>
|
||||
{{else}}
|
||||
<a class="{{if .IsCurrent}}active {{end}}item gt-content-center" {{if not .IsCurrent}}href="{{$.Link}}?page={{.Num}}{{if $paginationLink}}&{{$paginationLink}}{{end}}"{{end}}>{{.Num}}</a>
|
||||
<a class="{{if .IsCurrent}}active {{end}}item gt-content-center" {{if not .IsCurrent}}href="{{$paginationLink}}?page={{.Num}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>{{.Num}}</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$.Link}}?page={{.Next}}{{if $paginationLink}}&{{$paginationLink}}{{end}}"{{end}}>
|
||||
<a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$paginationLink}}?page={{.Next}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
|
||||
<span class="navigation_label">{{ctx.Locale.Tr "repo.issues.next"}}</span>
|
||||
{{svg "octicon-chevron-right" 16 "gt-ml-2"}}
|
||||
</a>
|
||||
<a class="{{if .IsLast}}disabled{{end}} item navigation" {{if not .IsLast}}href="{{$.Link}}?page={{.TotalPages}}{{if $paginationLink}}&{{$paginationLink}}{{end}}"{{end}}>
|
||||
<a class="{{if .IsLast}}disabled{{end}} item navigation" {{if not .IsLast}}href="{{$paginationLink}}?page={{.TotalPages}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
|
||||
<span class="navigation_label">{{ctx.Locale.Tr "admin.last_page"}}</span>
|
||||
{{svg "gitea-double-chevron-right" 16 "gt-ml-2"}}
|
||||
</a>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<div class="field">
|
||||
<div class="field {{if not .item.VisibleOnForm}}gt-hidden{{end}}">
|
||||
{{template "repo/issue/fields/header" .}}
|
||||
{{range $i, $opt := .item.Attributes.options}}
|
||||
<div class="field inline">
|
||||
<div class="ui checkbox gt-mr-0">
|
||||
<div class="ui checkbox gt-mr-0 {{if and ($opt.visible) (not (SliceUtils.Contains $opt.visible "form"))}}gt-hidden{{end}}">
|
||||
<input type="checkbox" name="form-field-{{$.item.ID}}-{{$i}}" {{if $opt.required}}required{{end}}>
|
||||
<label>{{RenderMarkdownToHtml $.context $opt.label}}</label>
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="field">
|
||||
<div class="field {{if not .item.VisibleOnForm}}gt-hidden{{end}}">
|
||||
{{template "repo/issue/fields/header" .}}
|
||||
{{/* FIXME: required validation */}}
|
||||
<div class="ui fluid selection dropdown {{if .item.Attributes.multiple}}multiple clearable{{end}}">
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="field">
|
||||
<div class="field {{if not .item.VisibleOnForm}}gt-hidden{{end}}">
|
||||
{{template "repo/issue/fields/header" .}}
|
||||
<input type="{{if .item.Validations.is_number}}number{{else}}text{{end}}" name="form-field-{{.item.ID}}" placeholder="{{.item.Attributes.placeholder}}" value="{{.item.Attributes.value}}" {{if .item.Validations.required}}required{{end}} {{if .item.Validations.regex}}pattern="{{.item.Validations.regex}}" title="{{.item.Validations.regex}}"{{end}}>
|
||||
</div>
|
||||
|
@ -1,3 +1,3 @@
|
||||
<div class="field">
|
||||
<div class="field {{if not .item.VisibleOnForm}}gt-hidden{{end}}">
|
||||
<div>{{RenderMarkdownToHtml .Context .item.Attributes.value}}</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{$useMarkdownEditor := not .item.Attributes.render}}
|
||||
<div class="field {{if $useMarkdownEditor}}combo-editor-dropzone{{end}}">
|
||||
<div class="field {{if not .item.VisibleOnForm}}gt-hidden{{end}} {{if $useMarkdownEditor}}combo-editor-dropzone{{end}}">
|
||||
{{template "repo/issue/fields/header" .}}
|
||||
|
||||
{{/* the real form element to provide the value */}}
|
||||
|
12
templates/swagger/v1_json.tmpl
generated
12
templates/swagger/v1_json.tmpl
generated
@ -20623,6 +20623,13 @@
|
||||
"type": "object",
|
||||
"additionalProperties": {},
|
||||
"x-go-name": "Validations"
|
||||
},
|
||||
"visible": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/IssueFormFieldVisible"
|
||||
},
|
||||
"x-go-name": "Visible"
|
||||
}
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
@ -20632,6 +20639,11 @@
|
||||
"title": "IssueFormFieldType defines issue form field type, can be \"markdown\", \"textarea\", \"input\", \"dropdown\" or \"checkboxes\"",
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"IssueFormFieldVisible": {
|
||||
"description": "IssueFormFieldVisible defines issue form field visible",
|
||||
"type": "string",
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"IssueLabelsOption": {
|
||||
"description": "IssueLabelsOption a collection of labels",
|
||||
"type": "object",
|
||||
|
Loading…
Reference in New Issue
Block a user