feat: pagination forms and suffixes in resolve and site
- Pagination tab: query or path form, per-locale segment, title and
description suffixes with %page% and %pages%
- ?page=1, /<segment>/1 and the other form answer 301 to the configured
address in one hop with normalisation and strict URLs; a page value
that is not a number answers not_found with pagination.invalid
- page 2 and later: self canonical with the page, index,follow, the
suffixes (%pages% left for the layer), paged hreflang alternates
- GET /site answers pagination {form, param, segments} and home_crumb
This commit is contained in:
@@ -51,8 +51,44 @@ const (
|
||||
errHoursOrder = "golem15.seo::lang.settings.hours_order"
|
||||
errCountryInvalid = "golem15.seo::lang.settings.country_invalid"
|
||||
errCoordinateInvalid = "golem15.seo::lang.settings.coordinate_invalid"
|
||||
errPageSegmentInvalid = "golem15.seo::lang.settings.page_segment_invalid"
|
||||
)
|
||||
|
||||
// PagePlaceholders are the placeholders a page suffix may use (D-41).
|
||||
var PagePlaceholders = []string{"%page%", "%pages%"}
|
||||
|
||||
// SuffixProblems refuses a page suffix with a placeholder other than
|
||||
// %page% and %pages%.
|
||||
func SuffixProblems(suffix string) []Problem {
|
||||
for _, found := range placeholderPattern.FindAllString(suffix, -1) {
|
||||
if !oneOfList(found, PagePlaceholders) {
|
||||
return []Problem{{Key: errTemplateUnknown, Params: map[string]string{"placeholder": found}}}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PaginationProblems lists the refusals of the Pagination tab: the form,
|
||||
// a segment outside [a-z0-9-] and unknown suffix placeholders.
|
||||
func (s Settings) PaginationProblems() map[string][]Problem {
|
||||
out := map[string][]Problem{}
|
||||
if s.PageForm != "" && !oneOfList(s.PageForm, []string{PageFormQuery, PageFormPath}) {
|
||||
out["page_form"] = []Problem{{Key: errValueInvalid}}
|
||||
}
|
||||
if seg := strings.TrimSpace(s.PageSegment); seg != "" && !pageSegment.MatchString(seg) {
|
||||
out["page_segment"] = []Problem{{Key: errPageSegmentInvalid}}
|
||||
}
|
||||
for field, v := range map[string]string{"page_title_suffix": s.PageTitleSuffix, "page_description_suffix": s.PageDescriptionSuffix} {
|
||||
if p := SuffixProblems(v); p != nil {
|
||||
out[field] = p
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// pageSegment is a valid page path segment.
|
||||
var pageSegment = regexp.MustCompile(`^[a-z0-9-]+$`)
|
||||
|
||||
// LocalTypes are the LocalBusiness types the settings offer (UI-SPEC C20).
|
||||
var LocalTypes = []string{
|
||||
"LocalBusiness", "ProfessionalService", "Store", "FoodEstablishment",
|
||||
@@ -200,7 +236,8 @@ func (Settings) Fillable() []string {
|
||||
"publisher_phone", "publisher_same_as", "local_enabled", "local_type",
|
||||
"local_price_range", "local_street", "local_postal_code", "local_city",
|
||||
"local_region", "local_country", "local_latitude", "local_longitude",
|
||||
"local_hours",
|
||||
"local_hours", "page_form", "page_segment", "page_title_suffix",
|
||||
"page_description_suffix",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +341,9 @@ func (s Settings) Problems() map[string][]Problem {
|
||||
out[field] = p
|
||||
}
|
||||
}
|
||||
for field, list := range s.PaginationProblems() {
|
||||
out[field] = append(out[field], list...)
|
||||
}
|
||||
for field, list := range s.PublisherProblems() {
|
||||
out[field] = append(out[field], list...)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# SEO settings (UI-SPEC C11, C20): General, one tab per content type (added
|
||||
# by the plugin's form extension after General), Indexing, Addresses,
|
||||
# Links, Publisher, Local business and Verification.
|
||||
# Pagination, Links, Publisher, Local business and Verification.
|
||||
|
||||
fields:
|
||||
_staging:
|
||||
@@ -146,6 +146,38 @@ fields:
|
||||
default: false
|
||||
tab: golem15.seo::lang.settings.tab_urls
|
||||
|
||||
page_form:
|
||||
label: golem15.seo::lang.settings.page_form
|
||||
comment: golem15.seo::lang.settings.page_form_comment
|
||||
type: dropdown
|
||||
span: left
|
||||
default: query
|
||||
options:
|
||||
query: golem15.seo::lang.settings.page_form_query
|
||||
path: golem15.seo::lang.settings.page_form_path
|
||||
tab: golem15.seo::lang.settings.tab_pagination
|
||||
|
||||
page_segment:
|
||||
label: golem15.seo::lang.settings.page_segment
|
||||
comment: golem15.seo::lang.settings.page_segment_comment
|
||||
type: mltext
|
||||
span: right
|
||||
tab: golem15.seo::lang.settings.tab_pagination
|
||||
|
||||
page_title_suffix:
|
||||
label: golem15.seo::lang.settings.page_title_suffix
|
||||
comment: golem15.seo::lang.settings.page_title_suffix_comment
|
||||
type: mltext
|
||||
span: left
|
||||
tab: golem15.seo::lang.settings.tab_pagination
|
||||
|
||||
page_description_suffix:
|
||||
label: golem15.seo::lang.settings.page_description_suffix
|
||||
comment: golem15.seo::lang.settings.page_description_suffix_comment
|
||||
type: mltext
|
||||
span: right
|
||||
tab: golem15.seo::lang.settings.tab_pagination
|
||||
|
||||
links_new_tab:
|
||||
label: golem15.seo::lang.settings.links_new_tab
|
||||
comment: golem15.seo::lang.settings.links_new_tab_comment
|
||||
|
||||
Reference in New Issue
Block a user