Documentation
¶
Overview ¶
Package submissions lets visitors propose a news item: a title, a text and an optional image, with their name and e-mail address. Editors review the proposals in the admin and accept one as a draft post in a Publishing instance, where it is edited and published as usual, or reject it. Add an instance with the key of the Publishing instance it feeds:
app.Use(publishing.Module("news", "News"), submissions.Module("tips", "News tips", "news"))
Form ¶
The form posts to the module's action, multipart because of the image; submitInput gives its model (ProposalInput) for go-form's functions:
<div id="tips">
{{with submitInput "tips"}}
{{form_open .}}
{{form_field . "name" nil}} {{form_field . "email" nil}}
{{form_field . "title" nil}} {{form_field . "text" nil}}
{{form_field . "image" nil}}
<button>Send</button>
{{form_close}}
{{end}}
</div>
Fields: name (200 characters), email, title (200 characters), text (plain text or Markdown, 20000 characters) and the optional image (JPEG, PNG or GIF up to 5 MB, stored in the media library). form_open adds the multipart encoding, the spam trap and the page to return to: the element with the instance key as its id. The visitor returns to the form's page with ?<key>=sent, ?<key>=invalid when a field is missing or wrong, or ?<key>=throttled after too many proposals (five per hour); spam is answered with sent and dropped. The proposal keeps the language of that page.
Templates ¶
Placed on a page, the module renders submissions/form.gohtml with a FormView (App.WriteTemplates writes a working starting point). The form can also be placed in any other template with the functions:
{{submitURL "tips"}} the address the form posts to
{{submitInput "tips"}} the form's model, as above
{{submitResult "tips"}} "sent", "invalid", "throttled" or "" after the visitor's post
for example {{if eq (submitResult "tips") "sent"}}<p>Thank you!</p>{{end}}.
Mail ¶
In the admin settings of the instance editors set the address that receives every new proposal, and whether submitters get a confirmation after sending and a message when their proposal is accepted or rejected. The messages are mail templates (see webthings.Module.Mail) with MailView as data: received, accepted and rejected go to the submitter in the proposal's language, notify to the editors in the admin's language. The defaults are English text in tl, so the site's translations translate them; a theme replaces them with mail/<key>/<name>.gohtml or mail/submissions/<name>.gohtml.
Index ¶
- Constants
- Variables
- func Module(key, title, target string) webthings.Module
- type DetailView
- type FormView
- type ListView
- type MailView
- type Proposal
- type ProposalInput
- type Row
- type Service
- func (s *Service) Accept(ctx context.Context, id, user string) (Submission, error)
- func (s *Service) Count(ctx context.Context, status string) (int, error)
- func (s *Service) Create(ctx context.Context, proposal Proposal) (Submission, error)
- func (s *Service) Delete(ctx context.Context, id string) error
- func (s *Service) Get(ctx context.Context, id string) (Submission, error)
- func (s *Service) List(ctx context.Context, status string, offset, limit int) ([]Submission, bool, error)
- func (s *Service) Purge(ctx context.Context, id string) error
- func (s *Service) Reject(ctx context.Context, id, user string) (Submission, error)
- func (s *Service) SaveSettings(ctx context.Context, settings Settings) error
- func (s *Service) Settings(ctx context.Context) (Settings, error)
- func (s *Service) Undelete(ctx context.Context, id string) error
- type Settings
- type Submission
Constants ¶
const ( // ResultSent is a proposal that was received and waits for an editor. ResultSent = "sent" // ResultInvalid is a proposal with a missing or wrong field, or an // image of another type; nothing was saved. ResultInvalid = "invalid" // ResultThrottled is the answer to a visitor who sent too many proposals. ResultThrottled = frontend.ResultThrottled )
Results are the values of ?<key>= after a visitor's post.
const ( // StatusNew is a proposal no editor has decided on yet. StatusNew = "new" // StatusAccepted is a proposal that became a draft post. StatusAccepted = "accepted" // StatusRejected is a proposal an editor turned down. StatusRejected = "rejected" )
Statuses of a submission. New ones wait for an editor's decision.
Variables ¶
var ( // ErrInvalid is returned for a proposal with a missing or wrong field. ErrInvalid = errors.New("submissions_invalid") // ErrDecided is returned when accepting an accepted submission or // rejecting one that is no longer new. ErrDecided = errors.New("submissions_decided") // ErrNoTarget is returned when accepting a submission while the target // key names no Publishing instance. ErrNoTarget = errors.New("submissions_no_target") )
Functions ¶
func Module ¶
Module adds a Submissions instance. key is used in URLs of the admin and the form action and must be unique on the site; title is shown in the admin and the page editor; target is the key of the Publishing instance accepted proposals become draft posts in, for example "news". Add that instance to the app as well.
Types ¶
type DetailView ¶
type DetailView struct {
Title string
BackURL string
Name string
Email string
Subject string
Body string
// ImageURL is empty without an image.
ImageURL string
Locale string
Date string
Status string
// Decided is when and by whom the submission was accepted or rejected.
Decided string
DecidedBy string
// PostURL opens the draft post of an accepted submission.
PostURL string
Accept *form.RenderModel
Reject *form.RenderModel
Delete *form.RenderModel
}
DetailView is the data of templates/admin/detail.gohtml: one proposal with the buttons to accept or reject it.
type FormView ¶
type FormView struct {
// Key is the instance key; results arrive as ?<key>=<result>, and the
// form returns the visitor to the element with the id Key.
Key string
// Title is the title of the page the module is placed on.
Title string
// Action is the address the form posts to.
Action string
// Result is "sent" after a proposal was received, "invalid" when it was
// refused, "throttled" after too many proposals, empty otherwise.
Result string
// MaxImageMB is the largest image visitors can add, in megabytes.
MaxImageMB int
// Form is the form's model (ProposalInput) for go-form's template
// functions, such as {{form_open .Form}} and {{form_field .Form "title"
// nil}}.
Form form.RenderModel
}
FormView is the data of submissions/form.gohtml.
type ListView ¶
type ListView struct {
Title string
// SettingsURL is set for editors who may change the settings.
SettingsURL string
// TrashURL shows the instance's deleted proposals.
TrashURL string
// Filters switch between the statuses; new ones are counted.
Filters admin.Tabs
Rows []Row
Pager admin.Pager
}
ListView is the data of templates/admin/list.gohtml: the proposals, filtered by status.
type MailView ¶
type MailView struct {
Submission
// ImageURL is the absolute address of the image; empty without one, and
// in mail to the submitter.
ImageURL string
// AdminURL opens the submission in the admin; empty in mail to the
// submitter.
AdminURL string
}
MailView is the data of the mail templates: the submission, and for the editors' notification the links to its image and to the admin.
type Proposal ¶
type Proposal struct {
Name string
Email string
Title string
// Body is plain text or Markdown.
Body string
// MediaID is the uploaded image in the media library, or empty.
MediaID string
Locale string
}
Proposal is what a visitor sends.
type ProposalInput ¶
type ProposalInput struct {
Name string `form:"input,text" name:"name" label:"Name" required:"true" maxLength:"200" line:"true" autocomplete:"name"`
Email string `` /* 137-byte string literal not displayed */
Title string `form:"input,text" name:"title" label:"Title" required:"true" maxLength:"200" line:"true"`
Text string `form:"textarea" name:"text" label:"Text" required:"true" maxLength:"20000"`
Image *multipart.FileHeader `` /* 212-byte string literal not displayed */
}
ProposalInput is the form a visitor posts to /_/<key>/submit: a name, an e-mail address, a title, the text and optionally an image. Themes render its fields with form_field, with their limits (FormView.Form), or write inputs with these names; a form with the image is sent as multipart/form-data.
type Row ¶
type Row struct {
URL string
Date string
Title string
Name string
Locale string
// Status is the translated status label.
Status string
}
Row is one proposal in the list.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is one Submissions instance, for the website's own code.
func (*Service) Accept ¶
Accept turns a submission into a draft post in the target Publishing instance, in the submission's language, and returns it with the post's ID. user is the deciding editor's ID. An accepted submission returns ErrDecided, a target that is no Publishing instance ErrNoTarget.
func (*Service) Create ¶
Create stores a proposal as a new submission. It returns ErrInvalid when a field is missing or too long; the image must already be in the media library.
func (*Service) Delete ¶
Delete moves a submission to the trash: it leaves the admin's lists and counts, and Undelete brings it back. Its image stays in the media library meanwhile, and the post of an accepted submission is not touched.
func (*Service) List ¶
func (s *Service) List(ctx context.Context, status string, offset, limit int) ([]Submission, bool, error)
List returns submissions with status (all when empty) newest first, skipping offset, and whether older ones exist.
func (*Service) Purge ¶
Purge removes a submission in the trash for good; db.ErrNotFound means it is not in the trash. The image of a submission that was not accepted goes to the media library's trash too, unless other content uses it; the post of an accepted one keeps its image and is not touched.
func (*Service) Reject ¶
Reject marks a new submission as rejected; user is the deciding editor's ID. A submission that is no longer new returns ErrDecided.
func (*Service) SaveSettings ¶
SaveSettings stores the notification address and which mails submitters get. An invalid address returns ErrInvalid.
type Settings ¶
type Settings struct {
// Notify receives every new proposal; empty sends nothing.
Notify string `db:"notify"`
// ConfirmMail sends submitters a confirmation of their proposal.
ConfirmMail bool `db:"confirm_mail"`
// DecisionMail tells submitters when their proposal is accepted or rejected.
DecisionMail bool `db:"decision_mail"`
}
Settings are the instance's mail settings.
type Submission ¶
type Submission struct {
ID string
Name string
Email string
Title string
Body string
MediaID string
Locale string
Status string
// PostID is the draft post of an accepted submission.
PostID string
CreatedAt time.Time
// DecidedAt and DecidedBy (a user ID) are set once accepted or rejected.
DecidedAt time.Time
DecidedBy string
}
Submission is a stored proposal with the editors' decision.