Documentation
¶
Overview ¶
Package directory is a categorised directory: organisations, shops, venues or a neighbourhood guide. Categories have a translated name and summary and are ordered by hand. Entries belong to one category and have a name, a slug, a translated summary and Markdown description, search keywords, a logo or image, contact details, an address with an optional map position and weekly opening hours. Add an instance with:
app.Use(directory.Module("guide", "Neighbourhood guide"))
Names and slugs are not translated: an organisation has the same name and URL in every language. Categories and entries show in a language once they are saved in it, and an entry only shows while its category is published. A category with entries cannot be deleted.
Pages ¶
Below the page the module is mounted on it serves:
/ directory/list.gohtml ListView /?q=bakery the same, searched /?category=food the same, one category /<category> directory/category.gohtml CategoryView /<category>/<entry> directory/entry.gohtml Entry
The search matches entries whose name, summary or keywords contain every word of q, ignoring case and accents like the site search. A search form is a plain GET form:
<form action="{{.URL}}"><input type="search" name="q" value="{{.Query}}"></form>
sitemap.xml lists the page the module is mounted on (like every page) and the address of every published category and entry; searches and filtered lists are left out. The site search finds entries by name, summary, keywords and description, and signed-in editors get a link to the entry or category in the admin.
Template functions ¶
Every template can show the directory without mounting it:
{{range directoryCategories "guide"}}<a href="{{.URL}}">{{.Name}} ({{.Count}})</a>{{end}}
{{range directoryEntries "guide" "food" 5}}<a href="{{.URL}}">{{.Name}}</a>{{end}}
{{with directoryEntry "guide" "bakery-jansen"}}{{.Name}}{{if .OpenNow}} · open{{end}}{{end}}
directoryEntries takes a category slug ("" for all categories) and a limit (0 or less for no limit). URLs point to the page the module is mounted on; while the module is on no page in the current language they are empty.
Opening hours ¶
Entry.Hours is the weekly schedule and Entry.OpenNow tells whether the entry is open while the page renders, in the site's timezone:
{{if .Hours.Known}}{{range .Hours.Week}}{{.Weekday}}: {{if .Closed}}closed{{else}}{{.Open}}–{{.Close}}{{end}}{{end}}{{end}}
Import ¶
The admin imports entries from a CSV file whose first row names the columns: name, category, summary, website, email, phone, street, postcode, city and keywords; name and category are required, others may be left out. Commas, semicolons and tabs separate the values. Missing categories are created. Text columns are stored in the site's default language.
"Publish new entries and categories" publishes what the import creates. It never publishes an existing category: an editor may keep a category hidden on purpose, so entries imported into an unpublished category stay off the site until the category is published.
Index ¶
- func Module(key, title string) webthings.Module
- type APIAddress
- type APICategory
- type APIDay
- type APIEntry
- type APIPoint
- type Category
- type CategoryFields
- type CategoryItem
- type CategoryText
- type CategoryView
- type Day
- type Entry
- type EntryFields
- type EntryItem
- type EntryText
- type Group
- type ImportOptions
- type ImportProblem
- type ImportResult
- type ImportView
- type ListView
- type OpeningHours
- type Query
- type Service
- func (s *Service) Categories() content.Store[CategoryFields, CategoryText]
- func (s *Service) Entries(ctx context.Context, locale string, query Query) ([]Entry, error)
- func (s *Service) Entry(ctx context.Context, locale, slug string) (Entry, error)
- func (s *Service) Import(ctx context.Context, source io.Reader, options ImportOptions) (ImportResult, error)
- func (s *Service) ListCategories(ctx context.Context, locale string) ([]Category, error)
- func (s *Service) Store() content.Store[EntryFields, EntryText]
- type WeekDay
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type APIAddress ¶
type APIAddress struct {
Street string `json:"street"`
Postcode string `json:"postcode"`
City string `json:"city"`
}
APIAddress is an entry's postal address.
type APICategory ¶
type APICategory struct {
ID string `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
Summary string `json:"summary"`
// EntryCount is the number of published entries in the category.
EntryCount int `json:"entry_count"`
// URL is the category's page, null while the directory is on no page;
// EntriesURL its entries in the API.
URL *string `json:"url"`
EntriesURL string `json:"entries_url"`
}
APICategory is a published category in the content API, at /api/v1/<key>/categories and with each of its entries.
type APIDay ¶
type APIDay struct {
Day string `json:"day"`
Open *string `json:"open"`
Close *string `json:"close"`
}
APIDay is one day of an entry's opening hours: the weekday in English lower case, such as "monday", and its opening and closing times as "15:04", both null on a closed day (see Day).
type APIEntry ¶
type APIEntry struct {
ID string `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
Summary string `json:"summary"`
Keywords []string `json:"keywords"`
// DescriptionHTML is the entry's description as HTML (api.HTML); only
// the entry's own endpoint has it.
DescriptionHTML string `json:"description_html,omitempty"`
Category APICategory `json:"category"`
Image *api.Image `json:"image"`
Website *string `json:"website"`
Email *string `json:"email"`
Phone *string `json:"phone"`
Address *APIAddress `json:"address"`
Location *APIPoint `json:"location"`
// OpeningHours are the days from Monday to Sunday, null for an entry
// without opening hours; HoursNote is the editors' note about them.
OpeningHours []APIDay `json:"opening_hours"`
HoursNote string `json:"hours_note"`
// URL is the entry's page, null while the directory is on no page;
// APIURL the entry in the API.
URL *string `json:"url"`
APIURL string `json:"api_url"`
UpdatedAt time.Time `json:"updated_at"`
}
APIEntry is a published entry in the content API (see package api), at /api/v1/<key>/entries/{id} and in the list of /api/v1/<key>/entries, which leaves out DescriptionHTML. The contact details, address, position and opening hours are those the entry's page shows, also in its structured data; each is null when not given.
type Category ¶
type Category struct {
ID string
Name string
Slug string
Summary string
URL string
// Count is the number of published entries in the category.
Count int
// Active is set on the category the list is filtered by.
Active bool
}
Category is a published category as templates see it.
type CategoryFields ¶
type CategoryFields struct {
Slug string `db:"slug"`
}
CategoryFields are the language-independent parts of a category.
type CategoryItem ¶
type CategoryItem = content.Item[CategoryFields, CategoryText]
CategoryItem is a category with its translations, as the store keeps it.
type CategoryText ¶
CategoryText is a category in one language.
type CategoryView ¶
type CategoryView struct {
Category Category
Entries []Entry
// ListURL links back to the whole directory.
ListURL string
}
CategoryView is the data of directory/category.gohtml.
type Day ¶
Day is the opening time of one weekday as "15:04" wall-clock times. A day without times is closed. A Close at or before Open means the entry closes after midnight; "24:00" closes at midnight and "00:00"–"00:00" is open all day.
type Entry ¶
type Entry struct {
ID string
Name string
Slug string
URL string
Category Category
Summary string
Description template.HTML
Keywords []string
// ImageURL is the entry's logo or image as pages show it, at most media.ImageWidth
// wide (see frontend.Frontend.Image); ImageSrcset lists smaller
// copies for a srcset attribute, ImageWidth and ImageHeight are the
// size of ImageURL and ImageOriginalURL is the uploaded file, for
// downloads. All are empty when the entry has none.
ImageURL string
ImageSrcset string
ImageOriginalURL string
ImageWidth int
ImageHeight int
Website string
Email string
Phone string
Street string
Postcode string
City string
// HasLocation is set when Latitude and Longitude are known.
HasLocation bool
Latitude float64
Longitude float64
Hours OpeningHours
HoursNote string
// OpenNow is Hours.OpenNow at the time the page renders, in the site's
// timezone.
OpenNow bool
}
Entry is a published entry as templates see it.
type EntryFields ¶
type EntryFields struct {
Category string `db:"category_id,null"`
Slug string `db:"slug"`
Name string `db:"name"`
Image string `db:"image_id,null"`
Website string `db:"website"`
Email string `db:"email"`
Phone string `db:"phone"`
Street string `db:"street"`
Postcode string `db:"postcode"`
City string `db:"city"`
Latitude sql.NullFloat64 `db:"latitude"`
Longitude sql.NullFloat64 `db:"longitude"`
Hours OpeningHours `db:"opening_hours"`
}
EntryFields are the language-independent parts of an entry. Website is a full http(s) URL. Latitude and Longitude are both set or both empty.
type EntryItem ¶
type EntryItem = content.Item[EntryFields, EntryText]
EntryItem is an entry with its translations, as the store keeps it.
type EntryText ¶
type EntryText struct {
Summary string `db:"summary"`
Description string `db:"description"`
Keywords string `db:"keywords"`
HoursNote string `db:"hours_note"`
}
EntryText is an entry in one language. Description is Markdown; Keywords are comma-separated words the search also matches.
type ImportOptions ¶
type ImportOptions struct {
// Locale is the language of the summary, keywords and new category names.
Locale string
// Update changes entries whose slug already exists; otherwise such rows
// are skipped.
Update bool
// Publish publishes the entries and categories the import creates.
// Existing categories keep their state: an editor may hide a category on
// purpose, so entries imported into an unpublished category stay off the
// site until the category is published.
Publish bool
}
ImportOptions change what Import does with the rows.
type ImportProblem ¶
ImportProblem is a row that was skipped. Reason is an admin message key.
type ImportResult ¶
type ImportResult struct {
Created int
Updated int
Skipped int
Categories int
Problems []ImportProblem
}
ImportResult tells what an import did.
type ImportView ¶
type ImportView struct {
Title string
BackURL string
Columns string
CanUpdate bool
CanPublish bool
Form form.RenderModel
Errors form.FieldErrors
Result *ImportResult
}
ImportView is the data of the import screen. The template lays out Form field by field: Update and Publish only for editors who may use them.
type ListView ¶
type ListView struct {
// Title is the title of the page the module is mounted on.
Title string
// URL is the list's address, the action of a search form.
URL string
// Query is the search (?q=); empty shows every entry.
Query string
// Category is set when the list is filtered (?category=).
Category *Category
// Categories are all categories with entries, for a filter menu.
Categories []Category
// Groups hold the matching entries per category, in category order.
Groups []Group
// Count is the number of matching entries.
Count int
}
ListView is the data of directory/list.gohtml.
type OpeningHours ¶
type OpeningHours [7]Day
OpeningHours is a weekly schedule indexed by time.Weekday, so hours[time.Monday] is Monday. It is stored as JSON in one column.
func (OpeningHours) Known ¶
func (h OpeningHours) Known() bool
Known reports whether the schedule has any opening time. Entries without one show no opening hours at all rather than "closed" every day.
func (OpeningHours) OpenNow ¶
func (h OpeningHours) OpenNow(t time.Time) bool
OpenNow reports whether the entry is open at t. t must be in the timezone the hours are written in; the module's own views use the site's timezone.
func (*OpeningHours) Scan ¶
func (h *OpeningHours) Scan(value any) error
Scan implements sql.Scanner, reading the JSON column back; an empty or NULL column is no opening hours.
func (OpeningHours) Value ¶
func (h OpeningHours) Value() (driver.Value, error)
Value stores the schedule as JSON, or "" when no day has times.
func (OpeningHours) Week ¶
func (h OpeningHours) Week() []WeekDay
Week returns the days from Monday to Sunday, for example {{range .Hours.Week}}{{.Weekday}} {{.Open}}–{{.Close}}{{end}}.
type Query ¶
type Query struct {
// Search holds words that must all appear in the name, summary or
// keywords. Case and accents are ignored, as in the site search.
Search string
// Category is a category slug.
Category string
}
Query selects entries; the zero Query selects every published entry.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is one Directory instance, for the website's own code.
func (*Service) Categories ¶
func (s *Service) Categories() content.Store[CategoryFields, CategoryText]
Categories gives write access to this instance's categories.
func (*Service) Entries ¶
Entries returns the published entries in locale that match query, sorted by name, for use outside the module's own pages. URLs are empty while the module is on no page in locale.
func (*Service) Entry ¶
Entry returns the published entry with slug in locale, or content.ErrNotFound.
func (*Service) Import ¶
func (s *Service) Import(ctx context.Context, source io.Reader, options ImportOptions) (ImportResult, error)
Import reads entries from CSV (see the package documentation for the columns) and creates or updates them. A row that cannot be saved is skipped and reported; other errors stop the import.
func (*Service) ListCategories ¶
ListCategories returns the published categories in locale in their order, with the number of entries in each. URLs are empty while the module is on no page in locale.