Settings > Sitemaps no longer prefills a content type's heading with its label in the administrator's language, so a save that did not touch the heading stores none and each locale's HTML sitemap keeps the label translated into it. The field's comment says so.
220 lines
7.4 KiB
Go
220 lines
7.4 KiB
Go
package sitemap
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"git.golem15.com/golem15/sm-sitemap-plugin/classes"
|
|
"git.golem15.com/golem15/sm-sitemap-plugin/controllers"
|
|
"git.golem15.com/golem15/sm-sitemap-plugin/models"
|
|
"git.golem15.com/golem15/summercms/modules/cabana"
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
"git.golem15.com/golem15/summercms/modules/phrasebook"
|
|
"git.golem15.com/golem15/summercms/modules/signpost"
|
|
"github.com/goccy/go-yaml"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// The settings form's tab keys: the Content types tab follows Files; the
|
|
// HTML sitemap tab's address field is added after html_enabled.
|
|
const (
|
|
TabFiles = "golem15.sitemap::lang.settings.tab_files"
|
|
TabTypes = "golem15.sitemap::lang.settings.tab_types"
|
|
TabHTML = "golem15.sitemap::lang.settings.tab_html"
|
|
)
|
|
|
|
// htmlPathField is the HTML sitemap address field: translatable, stored
|
|
// as a locale map in html_paths by the plugin's form extension (the
|
|
// settings row has no translation writer of its own).
|
|
const htmlPathField = "html_path"
|
|
|
|
// AdminActionExtensions extends the plugin's own settings singleton: the
|
|
// Content types tab (one block per signpost content type, in registration
|
|
// order; none without content types) and the HTML sitemap address.
|
|
func (p *Plugin) AdminActionExtensions() []pact.AdminActionExtension {
|
|
var types []pact.MenuItemType
|
|
if reg := p.registry(); reg != nil {
|
|
types = reg.ContentTypes()
|
|
}
|
|
form := p.typeSettingsTab(types)
|
|
if form == nil {
|
|
return nil
|
|
}
|
|
return []pact.AdminActionExtension{{Controller: controllers.SettingsControllerID, Form: form}}
|
|
}
|
|
|
|
func (p *Plugin) registry() *signpost.Registry {
|
|
if p == nil || p.app == nil {
|
|
return nil
|
|
}
|
|
reg, _ := p.app.Lookup[*signpost.Registry]()
|
|
return reg
|
|
}
|
|
|
|
// typeSettingsDocument is the fields document of the extension (UI-SPEC
|
|
// C26): per type, the include switch (labelled with the type's label), the
|
|
// file name and the HTML sitemap heading on the Content types tab, then
|
|
// the HTML sitemap address after html_enabled.
|
|
func typeSettingsDocument(types []pact.MenuItemType) ([]byte, error) {
|
|
fields := yaml.MapSlice{}
|
|
for _, typ := range types {
|
|
base := classes.TypeFieldBase(typ.Key)
|
|
label := typ.Label
|
|
if strings.TrimSpace(label) == "" {
|
|
label = typ.Key
|
|
}
|
|
fields = append(fields,
|
|
yaml.MapItem{Key: base + "include", Value: yaml.MapSlice{
|
|
{Key: "label", Value: label},
|
|
{Key: "comment", Value: "golem15.sitemap::lang.settings.type_include"},
|
|
{Key: "type", Value: "switch"},
|
|
{Key: "span", Value: "left"},
|
|
{Key: "tab", Value: TabTypes},
|
|
}},
|
|
yaml.MapItem{Key: base + "slug", Value: yaml.MapSlice{
|
|
{Key: "label", Value: "golem15.sitemap::lang.settings.type_slug"},
|
|
{Key: "comment", Value: "golem15.sitemap::lang.settings.type_slug_comment"},
|
|
{Key: "type", Value: "text"},
|
|
{Key: "span", Value: "right"},
|
|
{Key: "tab", Value: TabTypes},
|
|
}},
|
|
yaml.MapItem{Key: base + "title", Value: yaml.MapSlice{
|
|
{Key: "label", Value: "golem15.sitemap::lang.settings.type_title"},
|
|
{Key: "comment", Value: "golem15.sitemap::lang.settings.type_title_comment"},
|
|
{Key: "type", Value: "mltext"},
|
|
{Key: "span", Value: "full"},
|
|
{Key: "tab", Value: TabTypes},
|
|
}},
|
|
)
|
|
}
|
|
fields = append(fields, yaml.MapItem{Key: htmlPathField, Value: yaml.MapSlice{
|
|
{Key: "label", Value: "golem15.sitemap::lang.settings.html_path"},
|
|
{Key: "comment", Value: "golem15.sitemap::lang.settings.html_path_comment"},
|
|
{Key: "type", Value: "mltext"},
|
|
{Key: "span", Value: "left"},
|
|
{Key: "tab", Value: TabHTML},
|
|
{Key: "after", Value: "html_enabled"},
|
|
}})
|
|
doc := yaml.MapSlice{}
|
|
if len(types) > 0 {
|
|
doc = append(doc, yaml.MapItem{Key: "tabs", Value: []yaml.MapSlice{{{Key: "name", Value: TabTypes}, {Key: "after", Value: TabFiles}}}})
|
|
}
|
|
return yaml.Marshal(append(doc, yaml.MapItem{Key: "fields", Value: fields}))
|
|
}
|
|
|
|
// typeSettingsTab is the plugin's form extension on its own settings
|
|
// controller: the Content types tab, loaded from and saved to
|
|
// golem15_sitemap_types, and the HTML sitemap address (html_paths).
|
|
func (p *Plugin) typeSettingsTab(types []pact.MenuItemType) *pact.AdminFormExtension {
|
|
doc, err := typeSettingsDocument(types)
|
|
if err != nil {
|
|
pluginLog.Error("golem15.sitemap: content types tab", "error", err)
|
|
return nil
|
|
}
|
|
return &pact.AdminFormExtension{
|
|
FieldsYAML: doc,
|
|
Permissions: []string{controllers.PermissionAccessDefinitions},
|
|
Load: func(ctx context.Context, in pact.AdminFormLoad) (map[string]any, error) {
|
|
rows, err := classes.LoadTypeSettings(in.DB.WithContext(ctx))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := map[string]any{}
|
|
for _, typ := range types {
|
|
cfg := classes.EffectiveType(typ, rows)
|
|
base := classes.TypeFieldBase(typ.Key)
|
|
out[base+"include"] = cfg.Include
|
|
out[base+"slug"] = cfg.Slug
|
|
// Only stored headings (L2 WR-05): a type without one loads
|
|
// empty, so a save that did not touch it stores none and each
|
|
// locale's HTML sitemap keeps the label translated into it.
|
|
titles := cfg.Titles
|
|
if titles == nil {
|
|
titles = map[string]string{}
|
|
}
|
|
out[base+"title"] = titles
|
|
}
|
|
settings, err := classes.LoadSettings(in.DB.WithContext(ctx))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out[htmlPathField] = settings.EffectiveHTMLPaths()
|
|
return out, nil
|
|
},
|
|
Save: func(ctx context.Context, in pact.AdminFormSave) (pact.AdminFormSaveResult, error) {
|
|
if err := saveTypeSettings(ctx, in, types); err != nil {
|
|
return pact.AdminFormSaveResult{}, err
|
|
}
|
|
return pact.AdminFormSaveResult{}, saveHTMLPaths(in)
|
|
},
|
|
}
|
|
}
|
|
|
|
// saveTypeSettings stores the submitted Content types values
|
|
// (classes.SaveTypeSettings).
|
|
func saveTypeSettings(_ context.Context, in pact.AdminFormSave, types []pact.MenuItemType) error {
|
|
refusals, err := classes.SaveTypeSettings(in.DB, types, in.Values)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(refusals) > 0 {
|
|
return pact.AdminFormErrors(refusals)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// saveHTMLPaths stores a submitted HTML sitemap address: the locales sent
|
|
// replace theirs in html_paths (a blank one removes it), the others keep
|
|
// theirs. An address that models.ValidHTMLPath refuses is refused with
|
|
// errors.html_path.
|
|
func saveHTMLPaths(in pact.AdminFormSave) error {
|
|
raw, sent := in.Values[htmlPathField]
|
|
if !sent {
|
|
return nil
|
|
}
|
|
paths := map[string]string{}
|
|
switch v := raw.(type) {
|
|
case map[string]string:
|
|
paths = v
|
|
case map[string]any:
|
|
for k, s := range v {
|
|
text, _ := s.(string)
|
|
paths[k] = text
|
|
}
|
|
}
|
|
merged, problem := classes.MergeHTMLPaths(in.DB, paths)
|
|
if problem {
|
|
return pact.AdminFormErrors{htmlPathField: {models.HTMLPathProblem}}
|
|
}
|
|
return classes.StoreHTMLPaths(in.DB, merged)
|
|
}
|
|
|
|
// translator returns the application's translator, nil when none.
|
|
func (p *Plugin) translator() *phrasebook.Translator {
|
|
if p == nil || p.app == nil {
|
|
return nil
|
|
}
|
|
tr, _ := p.app.Lookup[*phrasebook.Translator]()
|
|
return tr
|
|
}
|
|
|
|
// defaultLocale is the site's default content locale: the translation
|
|
// writer's default, else app.locale, else en.
|
|
func (p *Plugin) defaultLocale(ctx context.Context, db *gorm.DB) string {
|
|
if p != nil && p.app != nil {
|
|
if w, ok := p.app.Lookup[cabana.TranslationWriter](); ok && w != nil {
|
|
if code, err := w.DefaultLocale(ctx, db); err == nil && code != "" {
|
|
return code
|
|
}
|
|
}
|
|
if p.app.Config != nil {
|
|
if code := strings.TrimSpace(p.app.Config.String("app.locale")); code != "" {
|
|
return code
|
|
}
|
|
}
|
|
}
|
|
return "en"
|
|
}
|
|
|
|
var _ pact.HasAdminActionExtensions = (*Plugin)(nil)
|