Selaa lähdekoodia

Merge pull request #175 from qor5/seo-complement

open graph title,description,meta data
Azuma Daisuke 1 vuosi sitten
vanhempi
commit
bb068adcf4
5 muutettua tiedostoa jossa 164 lisäystä ja 40 poistoa
  1. 67 20
      seo/admin.go
  2. 19 0
      seo/collection.go
  3. 9 0
      seo/messages.go
  4. 49 4
      seo/model.go
  5. 20 16
      seo/model_test.go

+ 67 - 20
seo/admin.go

@@ -58,9 +58,32 @@ func (collection *Collection) Configure(b *presets.Builder, db *gorm.DB) {
 
 func EditSetterFunc(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) (err error) {
 	var setting Setting
-	for key := range ctx.R.Form {
-		if strings.HasPrefix(key, fmt.Sprintf("%s.", field.Name)) {
-			reflectutils.Set(&setting, strings.TrimPrefix(key, fmt.Sprintf("%s.", field.Name)), ctx.R.Form.Get(key))
+	var mediaBox = media_library.MediaBox{}
+	for fieldWithPrefix := range ctx.R.Form {
+		// make sure OpenGraphImageFromMediaLibrary.Description set after OpenGraphImageFromMediaLibrary.Values
+		if fieldWithPrefix == fmt.Sprintf("%s.%s", field.Name, "OpenGraphImageFromMediaLibrary.Values") {
+			err = mediaBox.Scan(ctx.R.FormValue(fieldWithPrefix))
+			if err != nil {
+				return
+			}
+			break
+		}
+	}
+	for fieldWithPrefix := range ctx.R.Form {
+		if strings.HasPrefix(fieldWithPrefix, fmt.Sprintf("%s.%s", field.Name, "OpenGraphImageFromMediaLibrary")) {
+			if fieldWithPrefix == fmt.Sprintf("%s.%s", field.Name, "OpenGraphImageFromMediaLibrary.Description") {
+				mediaBox.Description = ctx.R.Form.Get(fieldWithPrefix)
+				reflectutils.Set(&setting, "OpenGraphImageFromMediaLibrary", mediaBox)
+			}
+			continue
+		}
+		if fieldWithPrefix == fmt.Sprintf("%s.%s", field.Name, "OpenGraphMetadataString") {
+			metadata := GetOpenGraphMetadata(ctx.R.Form.Get(fieldWithPrefix))
+			reflectutils.Set(&setting, "OpenGraphMetadata", metadata)
+			continue
+		}
+		if strings.HasPrefix(fieldWithPrefix, fmt.Sprintf("%s.", field.Name)) {
+			reflectutils.Set(&setting, strings.TrimPrefix(fieldWithPrefix, fmt.Sprintf("%s.", field.Name)), ctx.R.Form.Get(fieldWithPrefix))
 		}
 	}
 	return reflectutils.Set(obj, field.Name, setting)
@@ -93,6 +116,8 @@ func (collection *Collection) EditingComponentFunc(obj interface{}, field *prese
 		setting.Title = modelSetting.GetTitle()
 		setting.Description = modelSetting.GetDescription()
 		setting.Keywords = modelSetting.GetKeywords()
+		setting.OpenGraphTitle = modelSetting.GetOpenGraphTitle()
+		setting.OpenGraphDescription = modelSetting.GetOpenGraphDescription()
 		setting.OpenGraphURL = modelSetting.GetOpenGraphURL()
 		setting.OpenGraphType = modelSetting.GetOpenGraphType()
 		setting.OpenGraphImageURL = modelSetting.GetOpenGraphImageURL()
@@ -207,7 +232,6 @@ func (collection *Collection) pageFunc(ctx *web.EventContext) (_ web.PageRespons
 		)
 		seoComponents = append(seoComponents, comp)
 	}
-
 	return web.PageResponse{
 		PageTitle: msgr.PageTitle,
 		Body: h.If(editIsAllowed(ctx.R) == nil, VContainer(
@@ -238,7 +262,6 @@ func (collection *Collection) vseo(fieldPrefix string, seo *SEO, setting *Settin
 		msgr = i18n.MustGetModuleMessages(req, I18nSeoKey, Messages_en_US).(*Messages)
 		db   = collection.getDBFromContext(req.Context())
 	)
-
 	if seo.name == collection.globalName {
 		seos = append(seos, seo)
 	} else {
@@ -284,9 +307,9 @@ func (collection *Collection) vseo(fieldPrefix string, seo *SEO, setting *Settin
 		),
 		VCard(
 			VCardText(
-				VTextField().Counter(65).FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "Title")).Label(msgr.Title).Value(setting.Title).Attr("@click", fmt.Sprintf("$refs.seo.tagInputsFocus($refs.%s)", fmt.Sprintf("%s_title", refPrefix))).Attr("ref", fmt.Sprintf("%s_title", refPrefix)),
-				VTextField().Counter(150).FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "Description")).Label(msgr.Description).Value(setting.Description).Attr("@click", fmt.Sprintf("$refs.seo.tagInputsFocus($refs.%s)", fmt.Sprintf("%s_description", refPrefix))).Attr("ref", fmt.Sprintf("%s_description", refPrefix)),
-				VTextarea().Counter(255).Rows(2).AutoGrow(true).FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "Keywords")).Label(msgr.Keywords).Value(setting.Keywords).Attr("@click", fmt.Sprintf("$refs.seo.tagInputsFocus($refs.%s)", fmt.Sprintf("%s_keywords", refPrefix))).Attr("ref", fmt.Sprintf("%s_keywords", refPrefix)),
+				VTextField().Counter(65).FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "Title")).Label(msgr.Title).Value(setting.Title).Attr("@focus", fmt.Sprintf("$refs.seo.tagInputsFocus($refs.%s)", fmt.Sprintf("%s_title", refPrefix))).Attr("ref", fmt.Sprintf("%s_title", refPrefix)),
+				VTextField().Counter(150).FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "Description")).Label(msgr.Description).Value(setting.Description).Attr("@focus", fmt.Sprintf("$refs.seo.tagInputsFocus($refs.%s)", fmt.Sprintf("%s_description", refPrefix))).Attr("ref", fmt.Sprintf("%s_description", refPrefix)),
+				VTextarea().Counter(255).Rows(2).AutoGrow(true).FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "Keywords")).Label(msgr.Keywords).Value(setting.Keywords).Attr("@focus", fmt.Sprintf("$refs.seo.tagInputsFocus($refs.%s)", fmt.Sprintf("%s_keywords", refPrefix))).Attr("ref", fmt.Sprintf("%s_keywords", refPrefix)),
 			),
 		).Outlined(true).Flat(true),
 
@@ -294,11 +317,15 @@ func (collection *Collection) vseo(fieldPrefix string, seo *SEO, setting *Settin
 		VCard(
 			VCardText(
 				VRow(
-					VCol(VTextField().FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "OpenGraphURL")).Label(msgr.OpenGraphURL).Value(setting.OpenGraphURL)).Cols(6),
-					VCol(VTextField().FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "OpenGraphType")).Label(msgr.OpenGraphType).Value(setting.OpenGraphType)).Cols(6),
+					VCol(VTextField().FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "OpenGraphTitle")).Label(msgr.OpenGraphTitle).Value(setting.OpenGraphTitle).Attr("@focus", fmt.Sprintf("$refs.seo.tagInputsFocus($refs.%s)", fmt.Sprintf("%s_og_title", refPrefix))).Attr("ref", fmt.Sprintf("%s_og_title", refPrefix))).Cols(6),
+					VCol(VTextField().FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "OpenGraphDescription")).Label(msgr.OpenGraphDescription).Value(setting.OpenGraphDescription).Attr("@focus", fmt.Sprintf("$refs.seo.tagInputsFocus($refs.%s)", fmt.Sprintf("%s_og_description", refPrefix))).Attr("ref", fmt.Sprintf("%s_og_description", refPrefix))).Cols(6),
+				),
+				VRow(
+					VCol(VTextField().FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "OpenGraphURL")).Label(msgr.OpenGraphURL).Value(setting.OpenGraphURL).Attr("@focus", fmt.Sprintf("$refs.seo.tagInputsFocus($refs.%s)", fmt.Sprintf("%s_og_url", refPrefix))).Attr("ref", fmt.Sprintf("%s_og_url", refPrefix))).Cols(6),
+					VCol(VTextField().FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "OpenGraphType")).Label(msgr.OpenGraphType).Value(setting.OpenGraphType).Attr("@focus", fmt.Sprintf("$refs.seo.tagInputsFocus($refs.%s)", fmt.Sprintf("%s_og_type", refPrefix))).Attr("ref", fmt.Sprintf("%s_og_type", refPrefix))).Cols(6),
 				),
 				VRow(
-					VCol(VTextField().FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "OpenGraphImageURL")).Label(msgr.OpenGraphImageURL).Value(setting.OpenGraphImageURL)).Cols(12),
+					VCol(VTextField().FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "OpenGraphImageURL")).Label(msgr.OpenGraphImageURL).Value(setting.OpenGraphImageURL).Attr("@focus", fmt.Sprintf("$refs.seo.tagInputsFocus($refs.%s)", fmt.Sprintf("%s_og_imageurl", refPrefix))).Attr("ref", fmt.Sprintf("%s_og_imageurl", refPrefix))).Cols(12),
 				),
 				VRow(
 					VCol(views.QMediaBox(db).Label(msgr.OpenGraphImage).
@@ -321,6 +348,9 @@ func (collection *Collection) vseo(fieldPrefix string, seo *SEO, setting *Settin
 								},
 							},
 						})).Cols(12)),
+				VRow(
+					VCol(VTextarea().FieldName(fmt.Sprintf("%s.%s", fieldPrefix, "OpenGraphMetadataString")).Label(msgr.OpenGraphMetadata).Value(GetOpenGraphMetadataString(setting.OpenGraphMetadata)).Attr("@focus", fmt.Sprintf("$refs.seo.tagInputsFocus($refs.%s)", fmt.Sprintf("%s_og_metadata", refPrefix))).Attr("ref", fmt.Sprintf("%s_og_metadata", refPrefix))).Cols(12),
+				),
 			),
 		).Outlined(true).Flat(true),
 	).Attr("ref", "seo")
@@ -348,24 +378,33 @@ func (collection *Collection) save(ctx *web.EventContext) (r web.EventResponse,
 		if !strings.HasPrefix(fieldWithPrefix, name) {
 			continue
 		}
-
 		field := strings.Replace(fieldWithPrefix, fmt.Sprintf("%s.", name), "", -1)
-		if strings.HasPrefix(field, "OpenGraphImageFromMediaLibrary") {
-			if field == "OpenGraphImageFromMediaLibrary.Values" {
-				err = mediaBox.Scan(ctx.R.FormValue(fieldWithPrefix))
-				if err != nil {
-					return
-				}
-				settingVals["OpenGraphImageFromMediaLibrary"] = mediaBox
+		// make sure OpenGraphImageFromMediaLibrary.Description set after OpenGraphImageFromMediaLibrary.Values
+		if field == "OpenGraphImageFromMediaLibrary.Values" {
+			err = mediaBox.Scan(ctx.R.FormValue(fieldWithPrefix))
+			if err != nil {
+				return
 			}
+			break
+		}
+	}
 
+	for fieldWithPrefix := range ctx.R.Form {
+		if !strings.HasPrefix(fieldWithPrefix, name) {
+			continue
+		}
+		field := strings.Replace(fieldWithPrefix, fmt.Sprintf("%s.", name), "", -1)
+		if strings.HasPrefix(field, "OpenGraphImageFromMediaLibrary") {
 			if field == "OpenGraphImageFromMediaLibrary.Description" {
 				mediaBox.Description = ctx.R.FormValue(fieldWithPrefix)
 				if err != nil {
 					return
 				}
+				settingVals["OpenGraphImageFromMediaLibrary"] = mediaBox
 			}
-		} else if strings.HasPrefix(field, "Variables") {
+			continue
+		}
+		if strings.HasPrefix(field, "Variables") {
 			key := strings.Replace(field, "Variables.", "", -1)
 			variables[key] = ctx.R.FormValue(fieldWithPrefix)
 		} else {
@@ -374,6 +413,14 @@ func (collection *Collection) save(ctx *web.EventContext) (r web.EventResponse,
 	}
 	s := setting.GetSEOSetting()
 	for k, v := range settingVals {
+		if k == "OpenGraphMetadataString" {
+			metadata := GetOpenGraphMetadata(v.(string))
+			err = reflectutils.Set(&s, "OpenGraphMetadata", metadata)
+			if err != nil {
+				return
+			}
+			continue
+		}
 		err = reflectutils.Set(&s, k, v)
 		if err != nil {
 			return

+ 19 - 0
seo/collection.go

@@ -296,6 +296,12 @@ func (collection Collection) Render(obj interface{}, req *http.Request) h.HTMLCo
 		if s.Keywords != "" && setting.Keywords == "" {
 			setting.Keywords = s.Keywords
 		}
+		if s.OpenGraphTitle != "" && setting.OpenGraphTitle == "" {
+			setting.OpenGraphTitle = s.OpenGraphTitle
+		}
+		if s.OpenGraphDescription != "" && setting.OpenGraphDescription == "" {
+			setting.OpenGraphDescription = s.OpenGraphDescription
+		}
 		if s.OpenGraphURL != "" && setting.OpenGraphURL == "" {
 			setting.OpenGraphURL = s.OpenGraphURL
 		}
@@ -374,6 +380,19 @@ func replaceVariables(setting Setting, values map[string]string) Setting {
 	setting.Title = replace(setting.Title)
 	setting.Description = replace(setting.Description)
 	setting.Keywords = replace(setting.Keywords)
+	setting.OpenGraphTitle = replace(setting.OpenGraphTitle)
+	setting.OpenGraphDescription = replace(setting.OpenGraphDescription)
+	setting.OpenGraphURL = replace(setting.OpenGraphURL)
+	setting.OpenGraphType = replace(setting.OpenGraphType)
+	setting.OpenGraphImageURL = replace(setting.OpenGraphImageURL)
+	var metadata []OpenGraphMetadata
+	for _, m := range setting.OpenGraphMetadata {
+		metadata = append(metadata, OpenGraphMetadata{
+			Property: m.Property,
+			Content:  replace(m.Content),
+		})
+	}
+	setting.OpenGraphMetadata = metadata
 	return setting
 }
 

+ 9 - 0
seo/messages.go

@@ -11,10 +11,13 @@ type Messages struct {
 	Description             string
 	Keywords                string
 	OpenGraphInformation    string
+	OpenGraphTitle          string
+	OpenGraphDescription    string
 	OpenGraphURL            string
 	OpenGraphType           string
 	OpenGraphImageURL       string
 	OpenGraphImage          string
+	OpenGraphMetadata       string
 	Save                    string
 	SavedSuccessfully       string
 	Seo                     string
@@ -32,10 +35,13 @@ var Messages_en_US = &Messages{
 	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",
 	Save:                    "Save",
 	SavedSuccessfully:       "Saved successfully",
 	Seo:                     "SEO",
@@ -53,10 +59,13 @@ var Messages_zh_CN = &Messages{
 	Description:             "描述",
 	Keywords:                "关键词",
 	OpenGraphInformation:    "OG 信息",
+	OpenGraphTitle:          "OG 标题",
+	OpenGraphDescription:    "OG 描述",
 	OpenGraphURL:            "OG 链接",
 	OpenGraphType:           "OG 类型",
 	OpenGraphImageURL:       "OG 图片链接",
 	OpenGraphImage:          "OG 图片",
+	OpenGraphMetadata:       "OG 元数据",
 	Save:                    "保存",
 	SavedSuccessfully:       "成功保存",
 	Seo:                     "搜索引擎优化",

+ 49 - 4
seo/model.go

@@ -1,8 +1,11 @@
 package seo
 
 import (
+	"bytes"
 	"database/sql/driver"
+	"encoding/csv"
 	"encoding/json"
+	"strings"
 	"time"
 
 	"github.com/qor5/admin/l10n"
@@ -24,6 +27,8 @@ type QorSEOSettingInterface interface {
 	GetTitle() string
 	GetDescription() string
 	GetKeywords() string
+	GetOpenGraphTitle() string
+	GetOpenGraphDescription() string
 	GetOpenGraphURL() string
 	GetOpenGraphType() string
 	GetOpenGraphImageURL() string
@@ -49,6 +54,8 @@ type Setting struct {
 	Title                          string `gorm:"size:4294967295"`
 	Description                    string
 	Keywords                       string
+	OpenGraphTitle                 string
+	OpenGraphDescription           string
 	OpenGraphURL                   string
 	OpenGraphType                  string
 	OpenGraphImageURL              string
@@ -102,6 +109,12 @@ func (s *QorSEOSetting) SetName(name string) {
 	s.Name = name
 }
 
+func (s QorSEOSetting) GetOpenGraphTitle() string {
+	return s.Setting.OpenGraphTitle
+}
+func (s QorSEOSetting) GetOpenGraphDescription() string {
+	return s.Setting.OpenGraphDescription
+}
 func (s QorSEOSetting) GetOpenGraphURL() string {
 	return s.Setting.OpenGraphURL
 }
@@ -154,7 +167,10 @@ func (setting Setting) Value() (driver.Value, error) {
 }
 
 func (setting Setting) IsEmpty() bool {
-	return setting.Title == "" && setting.Description == "" && setting.Keywords == "" && setting.OpenGraphURL == "" && setting.OpenGraphType == "" && setting.OpenGraphImageURL == "" && setting.OpenGraphImageFromMediaLibrary.Url == "" && len(setting.OpenGraphMetadata) == 0
+	return setting.Title == "" && setting.Description == "" && setting.Keywords == "" &&
+		setting.OpenGraphTitle == "" && setting.OpenGraphDescription == "" &&
+		setting.OpenGraphURL == "" && setting.OpenGraphType == "" && setting.OpenGraphImageURL == "" &&
+		setting.OpenGraphImageFromMediaLibrary.Url == "" && len(setting.OpenGraphMetadata) == 0
 }
 
 type Variables map[string]string
@@ -181,8 +197,8 @@ func (setting Variables) Value() (driver.Value, error) {
 
 func (setting Setting) HTMLComponent(tags map[string]string) h.HTMLComponent {
 	openGraphData := map[string]string{
-		"og:title":       setting.Title,
-		"og:description": setting.Description,
+		"og:title":       setting.OpenGraphTitle,
+		"og:description": setting.OpenGraphDescription,
 		"og:url":         setting.OpenGraphURL,
 		"og:type":        setting.OpenGraphType,
 		"og:image":       setting.OpenGraphImageURL,
@@ -192,7 +208,7 @@ func (setting Setting) HTMLComponent(tags map[string]string) h.HTMLComponent {
 		openGraphData[metavalue.Property] = metavalue.Content
 	}
 
-	for _, key := range []string{"og:url", "og:type", "og:image", "og:title", "og:description"} {
+	for _, key := range []string{"og:title", "og:description", "og:url", "og:type", "og:image"} {
 		if v := openGraphData[key]; v == "" {
 			if v, ok := tags[key]; ok {
 				openGraphData[key] = v
@@ -222,3 +238,32 @@ func (setting Setting) HTMLComponent(tags map[string]string) h.HTMLComponent {
 		openGraphDataComponents,
 	}
 }
+
+func GetOpenGraphMetadata(in string) (metadata []OpenGraphMetadata) {
+	r := csv.NewReader(strings.NewReader(in))
+	records, err := r.ReadAll()
+	if err != nil {
+		return
+	}
+	for _, row := range records {
+		if len(row) != 2 {
+			continue
+		}
+		metadata = append(metadata, OpenGraphMetadata{
+			Property: row[0],
+			Content:  row[1],
+		})
+	}
+	return
+}
+
+func GetOpenGraphMetadataString(metadata []OpenGraphMetadata) string {
+	records := [][]string{}
+	for _, m := range metadata {
+		records = append(records, []string{m.Property, m.Content})
+	}
+	buf := new(bytes.Buffer)
+	w := csv.NewWriter(buf)
+	w.WriteAll(records)
+	return buf.String()
+}

+ 20 - 16
seo/model_test.go

@@ -15,34 +15,38 @@ func TestSettingHTMLComponent(t *testing.T) {
 		{
 			name: "Render the seo html",
 			setting: Setting{
-				Title:             "title",
-				Description:       "description",
-				Keywords:          "keyword",
-				OpenGraphURL:      "http://dev.qor5.com/product/1",
-				OpenGraphType:     "",
-				OpenGraphImageURL: "http://dev.qor5.com/product/1/og.jpg",
+				Title:                "title",
+				Description:          "description",
+				Keywords:             "keyword",
+				OpenGraphTitle:       "og title",
+				OpenGraphDescription: "og description",
+				OpenGraphURL:         "http://dev.qor5.com/product/1",
+				OpenGraphType:        "",
+				OpenGraphImageURL:    "http://dev.qor5.com/product/1/og.jpg",
 			},
 			tags: map[string]string{},
 			want: `
 			<title>title</title>
 			<meta name='description' content='description'>
 			<meta name='keywords' content='keyword'>
+			<meta property='og:title' name='og:title' content='og title'>
+			<meta property='og:description' name='og:description' content='og description'>
 			<meta property='og:type' name='og:type' content='website'>
 			<meta property='og:image' name='og:image' content='http://dev.qor5.com/product/1/og.jpg'>
-			<meta property='og:title' name='og:title' content='title'>
-			<meta property='og:description' name='og:description' content='description'>
 			<meta property='og:url' name='og:url' content='http://dev.qor5.com/product/1'>`,
 		},
 
 		{
 			name: "Render the seo html using the tag data",
 			setting: Setting{
-				Title:             "title",
-				Description:       "description",
-				Keywords:          "keyword",
-				OpenGraphURL:      "http://dev.qor5.com/product/1",
-				OpenGraphType:     "",
-				OpenGraphImageURL: "http://dev.qor5.com/product/1/og.jpg",
+				Title:                "title",
+				Description:          "description",
+				Keywords:             "keyword",
+				OpenGraphTitle:       "og title",
+				OpenGraphDescription: "og description",
+				OpenGraphURL:         "http://dev.qor5.com/product/1",
+				OpenGraphType:        "",
+				OpenGraphImageURL:    "http://dev.qor5.com/product/1/og.jpg",
 			},
 			tags: map[string]string{
 				"og:type":       "product",
@@ -52,10 +56,10 @@ func TestSettingHTMLComponent(t *testing.T) {
 			<title>title</title>
 			<meta name='description' content='description'>
 			<meta name='keywords' content='keyword'>
+			<meta property='og:title' name='og:title' content='og title'>
+			<meta property='og:description' name='og:description' content='og description'>
 			<meta property='og:type' name='og:type' content='product'>
 			<meta property='og:image' name='og:image' content='http://dev.qor5.com/product/1/og.jpg'>
-			<meta property='og:title' name='og:title' content='title'>
-			<meta property='og:description' name='og:description' content='description'>
 			<meta property='og:url' name='og:url' content='http://dev.qor5.com/product/1'>
 			<meta property='twiiter:image' name='twiiter:image' content='http://dev.qor5.com/product/1/twitter.jpg'>`,
 		},