seo

package
v3.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 13, 2024 License: MIT Imports: 30 Imported by: 1

README

Usage

Introduction

The seo package allows for the management and injection of dynamic data into HTML tags for the purpose of Search Engine Optimization.

Create a SEO builder

  • Create a builder and register global seo by default. db is an instance of gorm.DB

    builder := seo.NewBuilder(db)
    
  • The default global seo name is Global SEO, if you want to customize the name, passing the name through WithGlobalSEOName option to the NewBuilder function.

    builder := seo.NewBuilder(db, seo.WithGlobalSEOName("My Global SEO"))
    
  • Turn off the default inherit the upper level SEO data when the current SEO data is missing

    builder := seo.NewBuilder(db, seo.WithInherited(false))
    
  • The seo package supports localization. you can pass the localization code you want to support through the WithLocales option to the NewBuilder function. if no localization is set, it defaults to empty.

    builder := seo.NewBuilder(db, seo.WithLocales('zh', 'en', 'jp'))
    
  • You can pass multiple options at once

    builder := seo.NewBuilder(db, seo.WithLocales('zh'), seo.WithInherited(false), seo.WithGlobalSEOName("My Global SEO"))
    

Register and remove SEO

All registered SEO names are unique, If you have already registered a SEO named Test, attempting to register SEO with the same name Test will cause the program to panic.

The second parameter named model in the RegisterSEO(name string, model ...interface{}) method is an instance of a type that has a field of type Setting. If you pass a model whose type does not have such a field, the program will panic.

For Example:

builder.RegisterSEO("Test")
type Test struct {
    ...
    // doesn't have a field of type Setting
}
builder.RegisterSEO("Test", &Test{}) // will panic

There are two types of SEOs, one is SEO with model, the other is SEO without model. if you want to register a no model SEO, you can call RegisterSEO method like this:

seoBuilder.RegisterSEO("About Us")

if you want to register a SEO with model, you can call RegisterSEO method like this:

seoBuilder.RegisterSEO("Product", &Product{})
  • Remove a SEO

    NOTE: The global seo cannot be removed

    builder.RemoveSEO(&Product{}).RemoveSEO("Not Found")
    

    When you remove an SEO, the new parent of its child SEO becomes the parent of the seo.

  • The seo supports hierarchy, and you can use the SetParent or AppendChild methods of SEO to configure the hierarchy.

    With AppendChildren method

    builder.RegisterSEO("A").AppendChildren(
        builder.RegisterSEO("B"),
        builder.RegisterSEO("C"),
        builder.RegisterSEO("D"),
    )
    

    With SetParent Method

    seoA := builder.RegisterSEO("A")
    builder.RegisterSEO("B").SetParent(seoA)
    builder.RegisterSEO("C").SetParent(seoA)
    builder.RegisterSEO("D").SetParent(seoA)
    

    The final seo structure is as follows:

    Global SEO
    ├── A
        ├── B
        └── C
        └── D
    

Configure SEO

  • Register setting variables

    builder := NewBuilder(dbForTest)
    builder.RegisterSEO(&Product{}).
      RegisterSettingVariables("Type", "Place of Origin")
    
  • Register context variables

    seoBuilder = seo.NewBuilder(db)
    seoBuilder.RegisterSEO(&models.Post{}).
        RegisterContextVariable(
            "Title",
            func(object interface{}, _ *seo.Setting, _ *http.Request) string {
                if article, ok := object.(models.Post); ok {
                    return article.Title
                }
                return ""
            },
        )
    
  • Register Meta Property

    seoBuilder = seo.NewBuilder(db)
    seoBuilder.RegisterSEO(&models.Post{}).
        RegisterMetaProperty(
            "og:audio",
            func(object interface{}, _ *seo.Setting, _ *http.Request) string {
                if article, ok := object.(models.Post); ok {
                    return article.audio
                }
                return ""
            },
        )
    

Render SEO html data

Render a single seo
  • Render a single seo with model

    To call Render(obj interface{}, req *http.Request) for rendering a single seo.

    NOTE: obj must be of type *NameObj or a pointer to a struct that has a field of type Setting.

    type Post struct {
        Title   string
        Author  string
        Content string
        SEO     Setting
    }
    
    post := &Post{
        Title:   "TestRender",
        Author:  "iBakuman",
        Content: "Hello, Qor5 SEO",
        SEO: Setting{
            Title:            "{{Title}}",
            Description:      "post for testing",
            EnabledCustomize: true,
        },
    }
    builder := NewBuilder(dbForTest)
    builder.RegisterSEO(&Post{}).RegisterContextVariable(
        "Title",
        func(post interface{}, _ *Setting, _ *http.Request) string {
            if p, ok := post.(*Post); ok {
                return p.Title
            }
            return "No title"
        },
    ).RegisterMetaProperty("og:title",
        func(post interface{}, _ *Setting, _ *http.Request) string {
            return "Title for og:title"
        },
    )
    
    defaultReq, _ := http.NewRequest("POST", "http://www.demo.qor5.com", nil)
    res, err := builder.Render(post, defaultReq).MarshalHTML(context.TODO())
    if err != nil {
        panic(err)
    }
    fmt.Println(string(res))
    

    The output of the above code is as follows:

    <title>TestRender</title>
    
    <meta name='description' content='post for testing'>
    
    <meta name='keywords'>
    
    <meta property='og:description' name='og:description'>
    
    <meta property='og:url' name='og:url'>
    
    <meta property='og:type' name='og:type' content='website'>
    
    <meta property='og:image' name='og:image'>
    
    <meta property='og:title' name='og:title' content='Title for og:title'>
    
  • Render a single seo without model

      seoBuilder.Render(&NameObj{"About US", Locale: "en"})
    
Render multiple SEOs at once
  • Render multiple SEOs with model

    To call BatchRender(objs []interface, req *http.Request) for batch rendering.

    NOTE: You need to ensure that all elements in objs are of the same type.

    BatchRender does not check if all elements in objs are of the same type, as this can be performance-intensive. Therefore, it is the responsibility of the caller to ensure that all elements in objs are of the same type. If you pass objs with various types of SEO, it will only take the type of the first element as the standard to obtain the SEO configuration used in the rendering process.

    type Post struct {
        Title   string
        Author  string
        Content string
        SEO     Setting
        l10n.Locale
    }
    
    posts := []interface{}{
        &Post{
            Title:   "TestRenderA",
            Author:  "iBakuman",
            Content: "Hello, Qor5 SEO",
            SEO: Setting{
                Title:            "{{Title}}",
                Description:      "postA for testing",
                EnabledCustomize: true,
            },
        },
        &Post{
            Title:   "TestB",
            Author:  "iBakuman",
            Content: "Hello, Qor5 SEO",
            SEO: Setting{
                Title:            "{{Title}}",
                Description:      "postB for testing",
                EnabledCustomize: true,
            },
        },
    }
    builder := NewBuilder(dbForTest, WithLocales("en"))
    builder.RegisterSEO(&Post{}).RegisterContextVariable(
        "Title",
        func(post interface{}, _ *Setting, _ *http.Request) string {
            if p, ok := post.(*Post); ok {
                return p.Title
            }
            return "No title"
        },
    ).RegisterMetaProperty("og:title",
        func(post interface{}, _ *Setting, _ *http.Request) string {
            return "Title for og:title"
        },
    )
    
    defaultReq, _ := http.NewRequest("POST", "http://www.demo.qor5.com", nil)
    SEOs := builder.BatchRender(posts, defaultReq)
    for _, seo := range SEOs {
        html, _ := seo.MarshalHTML(context.TODO())
        fmt.Println(string(html))
        fmt.Println("----------------------------")
    }
    

    The output of the above code is as follows:

    <title>TestRenderA</title>
    
    <meta name='description' content='postA for testing'>
    
    <meta name='keywords'>
    
    <meta property='og:description' name='og:description'>
    
    <meta property='og:url' name='og:url'>
    
    <meta property='og:type' name='og:type' content='website'>
    
    <meta property='og:image' name='og:image'>
    
    <meta property='og:title' name='og:title' content='Title for og:title'>
    
    ----------------------------
    
    <title>TestB</title>
    
    <meta name='description' content='postB for testing'>
    
    <meta name='keywords'>
    
    <meta property='og:url' name='og:url'>
    
    <meta property='og:type' name='og:type' content='website'>
    
    <meta property='og:image' name='og:image'>
    
    <meta property='og:title' name='og:title' content='Title for og:title'>
    
    <meta property='og:description' name='og:description'>
    
    ----------------------------
    
  • Render multiple SEOs without model

    seoBuilder.BatchRender(NewNonModelSEOSlice("Product", "en", "zh"))
    

Documentation

Index

Constants

View Source
const (
	I18nSeoKey i18n.ModuleKey = "I18nSeoKey"
)
View Source
const (
	PermEdit = "perm_seo_edit"
)

Variables

View Source
var Messages_en_US = &Messages{
	Variable:             "Variables Setting",
	Basic:                "Basic",
	Title:                "Title",
	Description:          "Description",
	Keywords:             "Keywords",
	OpenGraphInformation: "Open Graph Information",
	OpenGraphTitle:       "Open Graph Title",
	OpenGraphDescription: "Open Graph Description",
	OpenGraphURL:         "Open Graph URL",
	OpenGraphType:        "Open Graph Type",
	OpenGraphImageURL:    "Open Graph Image URL",
	OpenGraphImage:       "Open Graph Image",
	OpenGraphMetadata:    "Open Graph Metadata",
	Seo:                  "SEO",
	Customize:            "Customize",
}
View Source
var Messages_zh_CN = &Messages{
	Variable:             "变量设置",
	Basic:                "基本信息",
	Title:                "标题",
	Description:          "描述",
	Keywords:             "关键词",
	OpenGraphInformation: "OG 信息",
	OpenGraphTitle:       "OG 标题",
	OpenGraphDescription: "OG 描述",
	OpenGraphURL:         "OG 链接",
	OpenGraphType:        "OG 类型",
	OpenGraphImageURL:    "OG 图片链接",
	OpenGraphImage:       "OG 图片",
	OpenGraphMetadata:    "OG 元数据",
	Seo:                  "搜索引擎优化",
	Customize:            "自定义",
}

Functions

func EditSetterFunc

func EditSetterFunc(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) (err error)

func GetOpenGraphMetadataString

func GetOpenGraphMetadataString(metadata []OpenGraphMetadata) string

func SeoJSComponentsPack

func SeoJSComponentsPack() web.ComponentsPack

Types

type Builder

type Builder struct {
	// contains filtered or unexported fields
}

Builder will hold registered SEO configures and global setting definition and other configures @snippet_begin(SeoBuilderDefinition)

func NewBuilder

func NewBuilder(db *gorm.DB, ops ...Option) *Builder

func (*Builder) AfterSave

func (b *Builder) AfterSave(v func(ctx context.Context, settingName string, locale string) error) *Builder

AfterSave sets the hook called after saving

func (*Builder) BatchRender

func (b *Builder) BatchRender(objs interface{}, req *http.Request) []h.HTMLComponent

BatchRender rendering multiple SEOs at once. objs must be a slice, and each element in objs must be of the same type. It is the responsibility of the caller to ensure that every element in objs is of the same type, as it is performance-intensive to check whether each element in `objs` if of the same type through reflection.

If you want to render non-model SEO, you must pass a slice of NonModelSEO to objs. For convenience, you can call NewNonModelSEOSlice function to create a slice of NonModelSEO. For Example: Following code will render the non-model SEO named "About Us" with locale "en" and "zh". b.BatchRender(NewNonModelSEOSlice("About Us", "en", "zh"))

func (*Builder) Configure

func (b *Builder) Configure(pb *presets.Builder) (seoModel *presets.ModelBuilder)

func (*Builder) EditingComponentFunc

func (b *Builder) EditingComponentFunc(obj interface{}, _ *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent

func (*Builder) GetGlobalSEO

func (b *Builder) GetGlobalSEO() *SEO

func (*Builder) GetSEO

func (b *Builder) GetSEO(obj interface{}) *SEO

GetSEO retrieves the specified SEO, It accepts two types of parameters. One is a string, where the literal value of the parameter is the name of the SEO. The other is an instance of a struct that has a field of type `Setting`, in which case the SEO corresponding to the type of the struct will be returned.

func (*Builder) GetSEOPriority

func (b *Builder) GetSEOPriority(name string) int

GetSEOPriority gets the priority of the specified SEO, with higher number indicating higher priority. The priority of Global SEO is 1 (the lowest priority)

func (*Builder) RegisterSEO

func (b *Builder) RegisterSEO(name string, model ...interface{}) *SEO

RegisterSEO registers a SEO through name or model. There are two types of SEOs, one is SEO with model, the other is SEO without model aka 'non-model seo'. if you want to register a non-model SEO, you can call RegisterSEO method like this: seoBuilder.RegisterSEO("About Us") if you want to register a SEO with model, you can call RegisterSEO method like this: seoBuilder.RegisterSEO("Product", &Product{}) the first parameter of RegisterSEO method is the name of the SEO, it will be displayed in the admin user interface.

If the SEO to be registered already exists, it will panic. The optional second parameter names `model` is an instance of a type that has a field of type `Setting`, if the type of model does not have such field or len(model) > 1, the program will panic.

The default parent of the registered SEO is global seo. If you need to set its parent, Please call the SetParent method of SEO after invoking RegisterSEO method. For Example: b.RegisterSEO("Region", &Region{}).SetParent(parentSEO) Or you can call appendChild method to add the SEO to the specified parent. For Example: b.GetGlobalSEO().appendChild(b.RegisterSEO("Region", &Region{}))

func (*Builder) RemoveSEO

func (b *Builder) RemoveSEO(obj interface{}) *Builder

RemoveSEO removes the specified SEO, if the SEO has children, the parent of the children will be the parent of the SEO

func (*Builder) Render

func (b *Builder) Render(obj interface{}, req *http.Request) h.HTMLComponent

Render renders the SEO according to the specified object. obj must be of type NonModelSEO or a pointer to a struct that has a field of type `Setting`.

If the obj is an instance of NonModelSEO, it will render the non-model SEO. For Example: following code will render the non-model SEO named "About Us" with locale "en". b.render(NewNonModelSEO("About Us", "en"))

When the locales passed to NewBuilder method is only one, you can call NewNonModelSEO without passing the locale parameter. in this case, the only locale passed to NewBuilder method will be used. For Example: following code will render the non-model SEO named "About Us" with locale "en". b := NewBuilder(db, []string{"en"}) b.Render(NewNonModelSEO("About Us"))

func (*Builder) SortSEOs

func (b *Builder) SortSEOs(SEOs []*QorSEOSetting)

SortSEOs sorts the SEOs in the order of their priority. The global SEO is always the first element in the sorted slice.

type ContextVarFunc

type ContextVarFunc func(interface{}, *Setting, *http.Request) string

type Messages

type Messages struct {
	Variable             string
	VariableDescription  string
	Basic                string
	Title                string
	Description          string
	Keywords             string
	OpenGraphInformation string
	OpenGraphTitle       string
	OpenGraphDescription string
	OpenGraphURL         string
	OpenGraphType        string
	OpenGraphImageURL    string
	OpenGraphImage       string
	OpenGraphMetadata    string
	Seo                  string
	Customize            string
}

type NonModelSEO

type NonModelSEO interface {
	GetName() string
	l10n.L10nInterface
}

NonModelSEO is used to store the name and locale of non-model SEO. If you register a SEO without model like the following: b.RegisterSEO("About Us") When you want to render the SEO, you must pass a NonModelSEO instance to Render method. For Example: b.Render(NewNonModelSEO("About Us", "en")) or b.render(NewNonModelSEO("About Us")) when the locales passed to NewBuilder method is only one.

func NewNonModelSEO

func NewNonModelSEO(name string, locale ...string) NonModelSEO

NewNonModelSEO creates a nonModelSEO instance that implements NonModelSEO interface. This function is only used to create a NonModelSEO instance passed to Render method. the name parameter must be the same as the name you passed to RegisterSEO method. For Example: If you register a SEO like this: b.RegisterSEO("About Us"), when you want render "About US" SEO, you must pass "About Us" to NewNonModelSEO method like this: b := NewBuilder(db, []string{"en", "zh"}) b.Render(NewNonModelSEO("About Us", "en")).

For convenience, you can call NewNonModelSEO function without passing the locale parameter when the locales passed to NewBuilder method is only one. For Example: b := newBuilder(db, []string{"en"}) // only one locale(en) is passed to NewBuilder method. b.render(NewNonModelSEO("About Us")) // the only locale(en) passed to NewBuilder method will be used.

func NewNonModelSEOSlice

func NewNonModelSEOSlice(name string, locales ...string) []NonModelSEO

NewNonModelSEOSlice creates a slice of NonModelSEO. The function is only used to create a slice of NonModelSEO passed to BatchRender method. For Example: following code will render the non-model SEO named "About Us" with locale "en" and "zh". b.BatchRender(NewNonModelSEOSlice("About Us", "en", "zh"))

type OpenGraphMetadata

type OpenGraphMetadata struct {
	Property string
	Content  string
}

OpenGraphMetadata open graph meta data

func GetOpenGraphMetadata

func GetOpenGraphMetadata(in string) (metadata []OpenGraphMetadata)

type Option

type Option func(*Builder)

func WithGlobalSEOName

func WithGlobalSEOName(name string) Option

func WithInherit

func WithInherit(inherited bool) Option

func WithLocales

func WithLocales(locales ...string) Option

type QorSEOSetting

type QorSEOSetting struct {
	Name      string `gorm:"primary_key"`
	Setting   Setting
	Variables Variables `sql:"type:text"`

	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt *time.Time `gorm:"index"`
	l10n.Locale
}

func (*QorSEOSetting) PrimaryColumnValuesBySlug

func (s *QorSEOSetting) PrimaryColumnValuesBySlug(slug string) map[string]string

func (*QorSEOSetting) PrimarySlug

func (s *QorSEOSetting) PrimarySlug() string

type SEO

type SEO struct {
	// contains filtered or unexported fields
}

SEO represents a SEO object for a page @snippet_begin(SeoDefinition)

func (*SEO) AppendChildren

func (seo *SEO) AppendChildren(children ...*SEO) *SEO

AppendChildren sets the parent of each child in the children to the current SEO. Usage: builder.RegisterSEO(parent).AppendChildren(

builder.RegisterSEO(child1),
builder.RegisterSEO(child2),
builder.RegisterSEO(child3)

) Or: builder.RegisterSEO(parent).AppendChildren(

builder.RegisterMultipleSEO(child1, child2, child3)...

)

func (*SEO) GetName

func (seo *SEO) GetName() string

func (*SEO) RegisterContextVariable

func (seo *SEO) RegisterContextVariable(varName string, varFunc ContextVarFunc) *SEO

func (*SEO) RegisterMetaProperty

func (seo *SEO) RegisterMetaProperty(propName string, propFunc ContextVarFunc) *SEO

func (*SEO) RegisterSettingVariables

func (seo *SEO) RegisterSettingVariables(names ...string) *SEO

func (*SEO) SetParent

func (seo *SEO) SetParent(newParent *SEO) *SEO

SetParent sets the parent of the current SEO node to newParent if newParent is already the parent of current SEO node, it returns itself directly. if newParent is the descendant node of the current SEO node, it panics. if newParent is

type Setting

type Setting struct {
	Title                          string                 `gorm:"size:4294967295" json:",omitempty"`
	Description                    string                 `json:",omitempty"`
	Keywords                       string                 `json:",omitempty"`
	OpenGraphTitle                 string                 `json:",omitempty"`
	OpenGraphDescription           string                 `json:",omitempty"`
	OpenGraphURL                   string                 `json:",omitempty"`
	OpenGraphType                  string                 `json:",omitempty"`
	OpenGraphImageURL              string                 `json:",omitempty"`
	OpenGraphImageFromMediaLibrary media_library.MediaBox `json:",omitempty"`
	OpenGraphMetadata              []OpenGraphMetadata    `json:",omitempty"`
	EnabledCustomize               bool                   `json:",omitempty"`
}

Setting defined meta's attributes

func (*Setting) HTMLComponent

func (setting *Setting) HTMLComponent(metaProperties map[string]string) h.HTMLComponent

func (*Setting) IsEmpty

func (setting *Setting) IsEmpty() bool

func (*Setting) Scan

func (setting *Setting) Scan(value interface{}) error

Scan scan value from database into struct

func (Setting) Value

func (setting Setting) Value() (driver.Value, error)

Value get value from struct, and save into database Do not changed it to pointer receiver method, If you change it to a pointer receiver, GORM may encounter errors "cannot found encode plan" when operating the qor_seo_settings table.

type VSeoBuilder

type VSeoBuilder struct {
	// contains filtered or unexported fields
}

func VSeo

func VSeo(children ...h.HTMLComponent) (r *VSeoBuilder)

func (*VSeoBuilder) AppendChildren

func (b *VSeoBuilder) AppendChildren(children ...h.HTMLComponent) (r *VSeoBuilder)

func (*VSeoBuilder) Attr

func (b *VSeoBuilder) Attr(vs ...interface{}) (r *VSeoBuilder)

func (*VSeoBuilder) Children

func (b *VSeoBuilder) Children(children ...h.HTMLComponent) (r *VSeoBuilder)

func (*VSeoBuilder) Class

func (b *VSeoBuilder) Class(names ...string) (r *VSeoBuilder)

func (*VSeoBuilder) ClassIf

func (b *VSeoBuilder) ClassIf(name string, add bool) (r *VSeoBuilder)

func (*VSeoBuilder) MarshalHTML

func (b *VSeoBuilder) MarshalHTML(ctx context.Context) (r []byte, err error)

func (*VSeoBuilder) Placeholder

func (b *VSeoBuilder) Placeholder(v string) (r *VSeoBuilder)

func (*VSeoBuilder) PrependChildren

func (b *VSeoBuilder) PrependChildren(children ...h.HTMLComponent) (r *VSeoBuilder)

func (*VSeoBuilder) SetAttr

func (b *VSeoBuilder) SetAttr(k string, v interface{})

func (*VSeoBuilder) Value

func (b *VSeoBuilder) Value(v string) (r *VSeoBuilder)

type Variables

type Variables map[string]string

func (*Variables) Scan

func (setting *Variables) Scan(value interface{}) error

Scan scan value from database into struct

func (Variables) Value

func (setting Variables) Value() (driver.Value, error)

Value get value from struct, and save into database Do not changed it to pointer receiver method, If you change it to a pointer receiver, GORM may encounter errors "cannot found encode plan" when operating the qor_seo_settings table.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL