Implement motion editing
parent
335ce16547
commit
2b98712aa8
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 CAcert Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package forms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.cacert.org/cacert-boardvoting/internal/models"
|
||||||
|
"git.cacert.org/cacert-boardvoting/internal/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NewMotionForm struct {
|
||||||
|
Title string `form:"title"`
|
||||||
|
Content string `form:"content"`
|
||||||
|
Type *models.VoteType `form:"type"`
|
||||||
|
Due int `form:"due"`
|
||||||
|
validator.Validator `form:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
minimumTitleLength = 3
|
||||||
|
maximumTitleLength = 200
|
||||||
|
minimumContentLength = 3
|
||||||
|
maximumContentLength = 8000
|
||||||
|
|
||||||
|
threeDays = 3
|
||||||
|
oneWeek = 7
|
||||||
|
twoWeeks = 14
|
||||||
|
threeWeeks = 28
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *NewMotionForm) Validate() {
|
||||||
|
f.CheckField(
|
||||||
|
validator.NotBlank(f.Title),
|
||||||
|
"title",
|
||||||
|
"This field cannot be blank",
|
||||||
|
)
|
||||||
|
f.CheckField(
|
||||||
|
validator.MinChars(f.Title, minimumTitleLength),
|
||||||
|
"title",
|
||||||
|
fmt.Sprintf("This field must be at least %d characters long", minimumTitleLength),
|
||||||
|
)
|
||||||
|
f.CheckField(
|
||||||
|
validator.MaxChars(f.Title, maximumTitleLength),
|
||||||
|
"title",
|
||||||
|
fmt.Sprintf("This field must be at most %d characters long", maximumTitleLength),
|
||||||
|
)
|
||||||
|
f.CheckField(
|
||||||
|
validator.NotBlank(f.Content),
|
||||||
|
"content",
|
||||||
|
"This field cannot be blank",
|
||||||
|
)
|
||||||
|
f.CheckField(
|
||||||
|
validator.MinChars(f.Content, minimumContentLength),
|
||||||
|
"content",
|
||||||
|
fmt.Sprintf("This field must be at least %d characters long", minimumContentLength),
|
||||||
|
)
|
||||||
|
f.CheckField(
|
||||||
|
validator.MaxChars(f.Content, maximumContentLength),
|
||||||
|
"content",
|
||||||
|
fmt.Sprintf("This field must be at most %d characters long", maximumContentLength),
|
||||||
|
)
|
||||||
|
|
||||||
|
f.CheckField(validator.PermittedInt(
|
||||||
|
f.Due, threeDays, oneWeek, twoWeeks, threeWeeks), "due", "invalid duration choice",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EditMotionForm struct {
|
||||||
|
Title string `form:"title"`
|
||||||
|
Content string `form:"content"`
|
||||||
|
Type *models.VoteType `form:"type"`
|
||||||
|
Due int `form:"due"`
|
||||||
|
validator.Validator `form:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f EditMotionForm) Validate() {
|
||||||
|
f.CheckField(
|
||||||
|
validator.NotBlank(f.Title),
|
||||||
|
"title",
|
||||||
|
"This field cannot be blank",
|
||||||
|
)
|
||||||
|
f.CheckField(
|
||||||
|
validator.MinChars(f.Title, minimumTitleLength),
|
||||||
|
"title",
|
||||||
|
fmt.Sprintf("This field must be at least %d characters long", minimumTitleLength),
|
||||||
|
)
|
||||||
|
f.CheckField(
|
||||||
|
validator.MaxChars(f.Title, maximumTitleLength),
|
||||||
|
"title",
|
||||||
|
fmt.Sprintf("This field must be at most %d characters long", maximumTitleLength),
|
||||||
|
)
|
||||||
|
f.CheckField(
|
||||||
|
validator.NotBlank(f.Content),
|
||||||
|
"content",
|
||||||
|
"This field cannot be blank",
|
||||||
|
)
|
||||||
|
f.CheckField(
|
||||||
|
validator.MinChars(f.Content, minimumContentLength),
|
||||||
|
"content",
|
||||||
|
fmt.Sprintf("This field must be at least %d characters long", minimumContentLength),
|
||||||
|
)
|
||||||
|
f.CheckField(
|
||||||
|
validator.MaxChars(f.Content, maximumContentLength),
|
||||||
|
"content",
|
||||||
|
fmt.Sprintf("This field must be at most %d characters long", maximumContentLength),
|
||||||
|
)
|
||||||
|
|
||||||
|
f.CheckField(validator.PermittedInt(
|
||||||
|
f.Due, threeDays, oneWeek, twoWeeks, threeWeeks), "due", "invalid duration choice",
|
||||||
|
)
|
||||||
|
}
|
@ -1,26 +1,26 @@
|
|||||||
Dear Board,
|
Dear Board,
|
||||||
|
|
||||||
{{ .Name }} has modified motion {{ .Tag }} to the following:
|
{{ .Data.Name }} has modified motion {{ .Data.Tag }} to the following:
|
||||||
|
|
||||||
{{ .Title }}
|
{{ .Data.Title }}
|
||||||
|
|
||||||
{{ wrap 76 .Content }}
|
{{ wrap 76 .Data.Content }}
|
||||||
|
|
||||||
Vote type: {{ .VoteType }}
|
Vote type: {{ .Data.Type }}
|
||||||
|
|
||||||
Voting will close {{ .Due }}
|
Voting will close {{ .Data.Due }}
|
||||||
|
|
||||||
To vote please choose:
|
To vote please choose:
|
||||||
|
|
||||||
Aye: {{ .VoteURL }}/aye
|
Aye: {{ .BaseURL }}{{ .Data.VoteURL }}/aye
|
||||||
Naye: {{ .VoteURL }}/naye
|
Naye: {{ .BaseURL }}{{ .Data.VoteURL }}/naye
|
||||||
Abstain: {{ .VoteURL }}/abstain
|
Abstain: {{ .BaseURL }}{{ .Data.VoteURL }}/abstain
|
||||||
|
|
||||||
Please be aware, that if you have voted already your vote is still
|
Please be aware, that if you have voted already your vote is still
|
||||||
registered and valid. If this modification has an impact on how you wish to
|
registered and valid. If this modification has an impact on how you wish to
|
||||||
vote, you are responsible for voting again.
|
vote, you are responsible for voting again.
|
||||||
|
|
||||||
To see all your pending votes: {{ .UnvotedURL }}
|
To see all your pending votes: {{ .BaseURL }}{{ .Data.UnvotedURL }}
|
||||||
|
|
||||||
Kind regards,
|
Kind regards,
|
||||||
the voting system
|
the voting system
|
@ -1,72 +1,70 @@
|
|||||||
{{ define "title" }}New motion{{ end }}
|
{{ define "title" }}New motion{{ end }}
|
||||||
|
|
||||||
{{ define "main" }}
|
{{ define "main" }}
|
||||||
<div class="ui raised segment">
|
<form action="/newmotion/" method="post">
|
||||||
<form action="/newmotion/" method="post">
|
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
<div class="ui form segment{{ if .Form.FieldErrors }} error{{ end }}">
|
||||||
<div class="ui form{{ if .Form.FieldErrors }} error{{ end }}">
|
<div class="three fields">
|
||||||
<div class="three fields">
|
<div class="field">
|
||||||
<div class="field">
|
<label>ID:</label>
|
||||||
<label>ID:</label>
|
(generated on submit)
|
||||||
(generated on submit)
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>Proponent:</label>
|
|
||||||
{{ .Form.User.Name }}
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>Proposed date/time:</label>
|
|
||||||
(auto filled to current date/time)
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="required field{{ if .Form.FieldErrors.title }} error{{ end }}">
|
<div class="field">
|
||||||
<label for="title">Title:</label>
|
<label>Proponent:</label>
|
||||||
<input id="title" name="title" type="text" value="{{ .Form.Title }}">
|
{{ .User.Name }}
|
||||||
{{ if .Form.FieldErrors.title }}
|
|
||||||
<span class="ui small error text">{{ .Form.FieldErrors.title }}</span>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="required field{{ if .Form.FieldErrors.content }} error{{ end }}">
|
<div class="field">
|
||||||
<label for="content">Text:</label>
|
<label>Proposed date/time:</label>
|
||||||
<textarea id="content" name="content">{{ .Form.Content }}</textarea>
|
(auto filled to current date/time)
|
||||||
{{ if .Form.FieldErrors.content }}
|
|
||||||
<span class="ui small error text">{{ .Form.FieldErrors.content }}</span>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="two fields">
|
</div>
|
||||||
<div class="required field{{ if .Form.FieldErrors.type }} error{{ end }}">
|
<div class="required field{{ if .Form.FieldErrors.title }} error{{ end }}">
|
||||||
<label for="type">Vote type:</label>
|
<label for="title">Title:</label>
|
||||||
{{ $voteType := toString .Form.Type }}
|
<input id="title" name="title" type="text" value="{{ .Form.Title }}">
|
||||||
<select id="type" name="type">
|
{{ if .Form.FieldErrors.title }}
|
||||||
<option value="motion"
|
<span class="ui small error text">{{ .Form.FieldErrors.title }}</span>
|
||||||
{{ if eq "motion" $voteType }}selected{{ end }}>
|
{{ end }}
|
||||||
Motion
|
</div>
|
||||||
</option>
|
<div class="required field{{ if .Form.FieldErrors.content }} error{{ end }}">
|
||||||
<option value="veto"
|
<label for="content">Text:</label>
|
||||||
{{ if eq "veto" $voteType }}selected{{ end }}>
|
<textarea id="content" name="content">{{ .Form.Content }}</textarea>
|
||||||
Veto
|
{{ if .Form.FieldErrors.content }}
|
||||||
</option>
|
<span class="ui small error text">{{ .Form.FieldErrors.content }}</span>
|
||||||
</select>
|
{{ end }}
|
||||||
{{ if .Form.FieldErrors.type }}
|
</div>
|
||||||
<span class="ui small error text">{{ .Form.FieldErrors.type}}</span>
|
<div class="two fields">
|
||||||
{{ end}}
|
<div class="required field{{ if .Form.FieldErrors.type }} error{{ end }}">
|
||||||
</div>
|
<label for="type">Vote type:</label>
|
||||||
<div class="required field{{ if .Form.FieldErrors.due }} error{{ end }}">
|
{{ $voteType := toString .Form.Type }}
|
||||||
<label for="due">Due: (autofilled from chosen
|
<select id="type" name="type">
|
||||||
option)</label>
|
<option value="motion"
|
||||||
<select id="due" name="due">
|
{{ if eq "motion" $voteType }}selected{{ end }}>
|
||||||
<option value="3"{{ if eq 3 .Form.Due }} selected{{ end }}>In 3 Days</option>
|
Motion
|
||||||
<option value="7"{{ if eq 7 .Form.Due }} selected{{ end }}>In 1 Week</option>
|
</option>
|
||||||
<option value="14"{{ if eq 14 .Form.Due }} selected{{ end }}>In 2 Weeks</option>
|
<option value="veto"
|
||||||
<option value="28"{{ if eq 28 .Form.Due }} selected{{ end }}>In 4 Weeks</option>
|
{{ if eq "veto" $voteType }}selected{{ end }}>
|
||||||
</select>
|
Veto
|
||||||
{{ if .Form.FieldErrors.due }}
|
</option>
|
||||||
<span class="ui small error text">{{ .Form.FieldErrors.due }}</span>
|
</select>
|
||||||
{{ end }}
|
{{ if .Form.FieldErrors.type }}
|
||||||
</div>
|
<span class="ui small error text">{{ .Form.FieldErrors.type}}</span>
|
||||||
|
{{ end}}
|
||||||
|
</div>
|
||||||
|
<div class="required field{{ if .Form.FieldErrors.due }} error{{ end }}">
|
||||||
|
<label for="due">Due: (autofilled from chosen
|
||||||
|
option)</label>
|
||||||
|
<select id="due" name="due">
|
||||||
|
<option value="3"{{ if eq 3 .Form.Due }} selected{{ end }}>In 3 Days</option>
|
||||||
|
<option value="7"{{ if eq 7 .Form.Due }} selected{{ end }}>In 1 Week</option>
|
||||||
|
<option value="14"{{ if eq 14 .Form.Due }} selected{{ end }}>In 2 Weeks</option>
|
||||||
|
<option value="28"{{ if eq 28 .Form.Due }} selected{{ end }}>In 4 Weeks</option>
|
||||||
|
</select>
|
||||||
|
{{ if .Form.FieldErrors.due }}
|
||||||
|
<span class="ui small error text">{{ .Form.FieldErrors.due }}</span>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<button class="ui button" type="submit">Propose</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<button class="ui button" type="submit">Propose</button>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
{{ end }}
|
{{ end }}
|
@ -0,0 +1,70 @@
|
|||||||
|
{{ define "title" }}Edit motion {{ .Motion.Tag }}{{ end }}
|
||||||
|
|
||||||
|
{{ define "main" }}
|
||||||
|
<form action="/motions/{{ .Motion.Tag }}/edit" method="post">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||||
|
<div class="ui form segment{{ if .Form.FieldErrors }} error{{ end }}">
|
||||||
|
<div class="three fields">
|
||||||
|
<div class="field">
|
||||||
|
<label>ID:</label>
|
||||||
|
{{ .Motion.Tag }}
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Proponent:</label>
|
||||||
|
{{ .User.Name }}
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Proposed date/time:</label>
|
||||||
|
{{ .Motion.Proposed }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="required field{{ if .Form.FieldErrors.title }} error{{ end }}">
|
||||||
|
<label for="title">Title:</label>
|
||||||
|
<input id="title" name="title" type="text" value="{{ .Form.Title }}">
|
||||||
|
{{ if .Form.FieldErrors.title }}
|
||||||
|
<span class="ui small error text">{{ .Form.FieldErrors.title }}</span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<div class="required field{{ if .Form.FieldErrors.content }} error{{ end }}">
|
||||||
|
<label for="content">Text:</label>
|
||||||
|
<textarea id="content" name="content">{{ .Form.Content }}</textarea>
|
||||||
|
{{ if .Form.FieldErrors.content }}
|
||||||
|
<span class="ui small error text">{{ .Form.FieldErrors.content }}</span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<div class="two fields">
|
||||||
|
<div class="required field{{ if .Form.FieldErrors.type }} error{{ end }}">
|
||||||
|
<label for="type">Vote type:</label>
|
||||||
|
{{ $voteType := toString .Form.Type }}
|
||||||
|
<select id="type" name="type">
|
||||||
|
<option value="motion"
|
||||||
|
{{ if eq "motion" $voteType }}selected{{ end }}>
|
||||||
|
Motion
|
||||||
|
</option>
|
||||||
|
<option value="veto"
|
||||||
|
{{ if eq "veto" $voteType }}selected{{ end }}>
|
||||||
|
Veto
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
{{ if .Form.FieldErrors.type }}
|
||||||
|
<span class="ui small error text">{{ .Form.FieldErrors.type}}</span>
|
||||||
|
{{ end}}
|
||||||
|
</div>
|
||||||
|
<div class="required field{{ if .Form.FieldErrors.due }} error{{ end }}">
|
||||||
|
<label for="due">Due: (autofilled from chosen
|
||||||
|
option)</label>
|
||||||
|
<select id="due" name="due">
|
||||||
|
<option value="3"{{ if eq 3 .Form.Due }} selected{{ end }}>In 3 Days</option>
|
||||||
|
<option value="7"{{ if eq 7 .Form.Due }} selected{{ end }}>In 1 Week</option>
|
||||||
|
<option value="14"{{ if eq 14 .Form.Due }} selected{{ end }}>In 2 Weeks</option>
|
||||||
|
<option value="28"{{ if eq 28 .Form.Due }} selected{{ end }}>In 4 Weeks</option>
|
||||||
|
</select>
|
||||||
|
{{ if .Form.FieldErrors.due }}
|
||||||
|
<span class="ui small error text">{{ .Form.FieldErrors.due }}</span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="ui button" type="submit">Modify</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{{ end }}
|
Loading…
Reference in New Issue