Issue due date api (#3890)
* Implemented basic api endpoint to manage deadlines * Fixed checking for permissions * Updating a deadline from the ui is now entirely done via the api * cleanup * Cosmetics * fixed lint + fmt * Added swagger model definition for deadline response * Updated gitea-sdk * Updated gitea-sdk * More cleanup * Generate swagger json * Merge branch 'master' of https://github.com/go-gitea/gitea into issue-due-date-api # Conflicts: # public/swagger.v1.json * Fixed permission to update a deadline via api * Re-added form to change a deadline * Added client-side validation + not ignore error messages from the api * Added locale for error message * Merge branch 'master' of https://github.com/go-gitea/gitea # Conflicts: # models/issue_comment.go * Proper date validation * Fixed indention * moved css to css file * added documentation for error codes * after merge cleanup * Added swagger description * DO NOTHING BUT TRIGGER THAT F*CKIN CI SO IT PICKS UP THE LATEST COMMIT AS IT SHOULD * DO NOTHING BUT TRIGGER THAT F*CKIN CI SO IT PICKS UP THE LATEST COMMIT AS IT SHOULD * regenerated stylesheetsrelease/v1.15
parent
55d9ddf24a
commit
ef6813abc9
|
@ -781,6 +781,7 @@ issues.due_date_added = "added the due date %s %s"
|
||||||
issues.due_date_modified = "modified the due date to %s from %s %s"
|
issues.due_date_modified = "modified the due date to %s from %s %s"
|
||||||
issues.due_date_remove = "removed the due date %s %s"
|
issues.due_date_remove = "removed the due date %s %s"
|
||||||
issues.due_date_overdue = "Overdue"
|
issues.due_date_overdue = "Overdue"
|
||||||
|
issues.due_date_invalid = "The due date is invalid or out of range. Please use the format yyyy-mm-dd."
|
||||||
|
|
||||||
pulls.desc = Enable merge requests and code reviews.
|
pulls.desc = Enable merge requests and code reviews.
|
||||||
pulls.new = New Pull Request
|
pulls.new = New Pull Request
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2447,14 +2447,44 @@ function initTopicbar() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function toggleDuedateForm() {
|
function toggleDeadlineForm() {
|
||||||
$('#add_deadline_form').fadeToggle(150);
|
$('#deadlineForm').fadeToggle(150);
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteDueDate(url) {
|
function setDeadline() {
|
||||||
$.post(url, {
|
var deadline = $('#deadlineDate').val();
|
||||||
'_csrf': csrf,
|
updateDeadline(deadline);
|
||||||
},function( data ) {
|
}
|
||||||
|
|
||||||
|
function updateDeadline(deadlineString) {
|
||||||
|
$('#deadline-err-invalid-date').hide();
|
||||||
|
$('#deadline-loader').addClass('loading');
|
||||||
|
|
||||||
|
var realDeadline = null;
|
||||||
|
if (deadlineString !== '') {
|
||||||
|
|
||||||
|
var newDate = Date.parse(deadlineString)
|
||||||
|
|
||||||
|
if (isNaN(newDate)) {
|
||||||
|
$('#deadline-loader').removeClass('loading');
|
||||||
|
$('#deadline-err-invalid-date').show();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
realDeadline = new Date(newDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax($('#update-issue-deadline-form').attr('action') + '/deadline', {
|
||||||
|
data: JSON.stringify({
|
||||||
|
'due_date': realDeadline,
|
||||||
|
}),
|
||||||
|
contentType: 'application/json',
|
||||||
|
type: 'POST',
|
||||||
|
success: function () {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
$('#deadline-loader').removeClass('loading');
|
||||||
|
$('#deadline-err-invalid-date').show();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,6 +98,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#deadlineForm input{
|
||||||
|
width: 12.8rem;
|
||||||
|
border-radius: 4px 0 0 4px;
|
||||||
|
border-right: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.header-wrapper {
|
.header-wrapper {
|
||||||
background-color: #FAFAFA;
|
background-color: #FAFAFA;
|
||||||
|
|
|
@ -2320,6 +2320,68 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/issues/{index}/deadline": {
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"issue"
|
||||||
|
],
|
||||||
|
"summary": "Set an issue deadline. If set to null, the deadline is deleted.",
|
||||||
|
"operationId": "issueEditIssueDeadline",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "index of the issue to create or update a deadline on",
|
||||||
|
"name": "index",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/EditDeadlineOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"$ref": "#/responses/IssueDeadline"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Not repo writer",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Issue not found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/responses/empty"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/issues/{index}/labels": {
|
"/repos/{owner}/{repo}/issues/{index}/labels": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -6145,6 +6207,21 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
|
"x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
|
||||||
},
|
},
|
||||||
|
"EditDeadlineOption": {
|
||||||
|
"description": "EditDeadlineOption options for creating a deadline",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"due_date"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"due_date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "Deadline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
|
||||||
|
},
|
||||||
"EditHookOption": {
|
"EditHookOption": {
|
||||||
"description": "EditHookOption options when modify one hook",
|
"description": "EditHookOption options when modify one hook",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -6635,6 +6712,18 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
|
"x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
|
||||||
},
|
},
|
||||||
|
"IssueDeadline": {
|
||||||
|
"description": "IssueDeadline represents an issue deadline",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"due_date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "Deadline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
|
||||||
|
},
|
||||||
"IssueLabelsOption": {
|
"IssueLabelsOption": {
|
||||||
"description": "IssueLabelsOption a collection of labels",
|
"description": "IssueLabelsOption a collection of labels",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -7635,6 +7724,12 @@
|
||||||
"$ref": "#/definitions/Issue"
|
"$ref": "#/definitions/Issue"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"IssueDeadline": {
|
||||||
|
"description": "IssueDeadline",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/IssueDeadline"
|
||||||
|
}
|
||||||
|
},
|
||||||
"IssueList": {
|
"IssueList": {
|
||||||
"description": "IssueList",
|
"description": "IssueList",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
|
|
@ -447,6 +447,8 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
m.Combo("").Get(repo.ListTrackedTimes).
|
m.Combo("").Get(repo.ListTrackedTimes).
|
||||||
Post(reqToken(), bind(api.AddTimeOption{}), repo.AddTime)
|
Post(reqToken(), bind(api.AddTimeOption{}), repo.AddTime)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
|
||||||
})
|
})
|
||||||
}, mustEnableIssues)
|
}, mustEnableIssues)
|
||||||
m.Group("/labels", func() {
|
m.Group("/labels", func() {
|
||||||
|
|
|
@ -278,7 +278,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
|
||||||
|
|
||||||
// Update the deadline
|
// Update the deadline
|
||||||
var deadlineUnix util.TimeStamp
|
var deadlineUnix util.TimeStamp
|
||||||
if form.Deadline != nil && !form.Deadline.IsZero() {
|
if form.Deadline != nil && !form.Deadline.IsZero() && ctx.Repo.IsWriter() {
|
||||||
deadlineUnix = util.TimeStamp(form.Deadline.Unix())
|
deadlineUnix = util.TimeStamp(form.Deadline.Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,3 +338,72 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
|
||||||
}
|
}
|
||||||
ctx.JSON(201, issue.APIFormat())
|
ctx.JSON(201, issue.APIFormat())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateIssueDeadline updates an issue deadline
|
||||||
|
func UpdateIssueDeadline(ctx *context.APIContext, form api.EditDeadlineOption) {
|
||||||
|
// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/deadline issue issueEditIssueDeadline
|
||||||
|
// ---
|
||||||
|
// summary: Set an issue deadline. If set to null, the deadline is deleted.
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: index
|
||||||
|
// in: path
|
||||||
|
// description: index of the issue to create or update a deadline on
|
||||||
|
// type: integer
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/EditDeadlineOption"
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// "$ref": "#/responses/IssueDeadline"
|
||||||
|
// "403":
|
||||||
|
// description: Not repo writer
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// description: Issue not found
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
|
||||||
|
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
|
||||||
|
if err != nil {
|
||||||
|
if models.IsErrIssueNotExist(err) {
|
||||||
|
ctx.Status(404)
|
||||||
|
} else {
|
||||||
|
ctx.Error(500, "GetIssueByIndex", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ctx.Repo.IsWriter() {
|
||||||
|
ctx.Status(403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var deadlineUnix util.TimeStamp
|
||||||
|
if form.Deadline != nil && !form.Deadline.IsZero() {
|
||||||
|
deadlineUnix = util.TimeStamp(form.Deadline.Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
|
||||||
|
ctx.Error(500, "UpdateIssueDeadline", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(201, api.IssueDeadline{Deadline: form.Deadline})
|
||||||
|
}
|
||||||
|
|
|
@ -77,3 +77,10 @@ type swaggerResponseTrackedTimeList struct {
|
||||||
// in:body
|
// in:body
|
||||||
Body []api.TrackedTime `json:"body"`
|
Body []api.TrackedTime `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IssueDeadline
|
||||||
|
// swagger:response IssueDeadline
|
||||||
|
type swaggerIssueDeadline struct {
|
||||||
|
// in:body
|
||||||
|
Body api.IssueDeadline `json:"body"`
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ type swaggerParameterBodies struct {
|
||||||
CreateIssueOption api.CreateIssueOption
|
CreateIssueOption api.CreateIssueOption
|
||||||
// in:body
|
// in:body
|
||||||
EditIssueOption api.EditIssueOption
|
EditIssueOption api.EditIssueOption
|
||||||
|
// in:body
|
||||||
|
EditDeadlineOption api.EditDeadlineOption
|
||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
CreateIssueCommentOption api.CreateIssueCommentOption
|
CreateIssueCommentOption api.CreateIssueCommentOption
|
||||||
|
|
|
@ -1490,51 +1490,3 @@ func ChangeCommentReaction(ctx *context.Context, form auth.ReactionForm) {
|
||||||
"html": html,
|
"html": html,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateDeadline adds or updates a deadline
|
|
||||||
func UpdateDeadline(ctx *context.Context, form auth.DeadlineForm) {
|
|
||||||
issue := GetActionIssue(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.HasError() {
|
|
||||||
ctx.ServerError("ChangeIssueDeadline", errors.New(ctx.GetErrMsg()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make unix of deadline string
|
|
||||||
deadline, err := time.ParseInLocation("2006-01-02", form.DateString, time.Local)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Flash.Error(ctx.Tr("repo.issues.invalid_due_date_format"))
|
|
||||||
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = models.UpdateIssueDeadline(issue, util.TimeStamp(deadline.Unix()), ctx.User); err != nil {
|
|
||||||
ctx.Flash.Error(ctx.Tr("repo.issues.error_modifying_due_date"))
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveDeadline removes a deadline
|
|
||||||
func RemoveDeadline(ctx *context.Context) {
|
|
||||||
issue := GetActionIssue(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.HasError() {
|
|
||||||
ctx.ServerError("RemoveIssueDeadline", errors.New(ctx.GetErrMsg()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := models.UpdateIssueDeadline(issue, 0, ctx.User); err != nil {
|
|
||||||
ctx.Flash.Error(ctx.Tr("repo.issues.error_removing_due_date"))
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
|
@ -532,8 +532,6 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction)
|
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction)
|
||||||
m.Post("/deadline/update", reqRepoWriter, bindIgnErr(auth.DeadlineForm{}), repo.UpdateDeadline)
|
|
||||||
m.Post("/deadline/delete", reqRepoWriter, repo.RemoveDeadline)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
m.Post("/labels", reqRepoWriter, repo.UpdateIssueLabel)
|
m.Post("/labels", reqRepoWriter, repo.UpdateIssueLabel)
|
||||||
|
|
|
@ -211,6 +211,11 @@
|
||||||
|
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<span class="text"><strong>{{.i18n.Tr "repo.issues.due_date"}}</strong></span>
|
<span class="text"><strong>{{.i18n.Tr "repo.issues.due_date"}}</strong></span>
|
||||||
|
<div class="ui form" id="deadline-loader">
|
||||||
|
<div class="ui negative message" id="deadline-err-invalid-date" style="display: none;">
|
||||||
|
<i class="close icon"></i>
|
||||||
|
{{.i18n.Tr "repo.issues.due_date_invalid"}}
|
||||||
|
</div>
|
||||||
{{if ne .Issue.DeadlineUnix 0}}
|
{{if ne .Issue.DeadlineUnix 0}}
|
||||||
<p>
|
<p>
|
||||||
<span class="octicon octicon-calendar"></span>
|
<span class="octicon octicon-calendar"></span>
|
||||||
|
@ -220,8 +225,8 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if and .IsSigned .IsRepositoryWriter}}
|
{{if and .IsSigned .IsRepositoryWriter}}
|
||||||
<br/>
|
<br/>
|
||||||
<a style="cursor:pointer;" onclick="toggleDuedateForm();"><i class="edit icon"></i>Edit</a> -
|
<a style="cursor:pointer;" onclick="toggleDeadlineForm();"><i class="edit icon"></i>Edit</a> -
|
||||||
<a style="cursor:pointer;" onclick="deleteDueDate('{{$.RepoLink}}/issues/{{.Issue.Index}}/deadline/delete');"><i class="remove icon"></i>Remove</a>
|
<a style="cursor:pointer;" onclick="updateDeadline('');"><i class="remove icon"></i>Remove</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
</p>
|
</p>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
@ -229,10 +234,10 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if and .IsSigned .IsRepositoryWriter}}
|
{{if and .IsSigned .IsRepositoryWriter}}
|
||||||
<form method="POST" {{if ne .Issue.DeadlineUnix 0}}style="display: none;"{{end}} id="add_deadline_form" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/deadline/update" class="ui action input fluid">
|
<div {{if ne .Issue.DeadlineUnix 0}} style="display: none;"{{end}} id="deadlineForm">
|
||||||
|
<form class="ui fluid action input" action="{{AppSubUrl}}/api/v1/repos/{{.Repository.Owner.Name}}/{{.Repository.Name}}/issues/{{.Issue.Index}}" method="post" id="update-issue-deadline-form" onsubmit="setDeadline();return false;">
|
||||||
{{$.CsrfTokenHtml}}
|
{{$.CsrfTokenHtml}}
|
||||||
<div class="ui fluid action input">
|
<input required placeholder="{{.i18n.Tr "repo.issues.due_date_form"}}" {{if gt .Issue.DeadlineUnix 0}}value="{{.Issue.DeadlineUnix.Format "2006-01-02"}}"{{end}} type="date" name="deadlineDate" id="deadlineDate">
|
||||||
<input required placeholder="{{.i18n.Tr "repo.issues.due_date_form"}}" {{if ne .Issue.DeadlineUnix 0 }}value="{{.Issue.DeadlineUnix.Format "2006-01-02"}}"{{end}} type="date" name="date" style="min-width: 13.9rem;border-radius: 4px 0 0 4px;border-right: 0;white-space: nowrap;">
|
|
||||||
<button class="ui green icon button">
|
<button class="ui green icon button">
|
||||||
{{if ne .Issue.DeadlineUnix 0}}
|
{{if ne .Issue.DeadlineUnix 0}}
|
||||||
<i class="edit icon"></i>
|
<i class="edit icon"></i>
|
||||||
|
@ -240,9 +245,9 @@
|
||||||
<i class="plus icon"></i>
|
<i class="plus icon"></i>
|
||||||
{{end}}
|
{{end}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue