field.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package slug
  2. import (
  3. "fmt"
  4. "reflect"
  5. "regexp"
  6. "strings"
  7. syncp "sync"
  8. . "github.com/qor5/ui/vuetify"
  9. "github.com/qor5/web"
  10. "github.com/qor5/x/i18n"
  11. "github.com/qor5/admin/presets"
  12. "github.com/gosimple/unidecode"
  13. "github.com/sunfmin/reflectutils"
  14. h "github.com/theplant/htmlgo"
  15. "golang.org/x/text/language"
  16. )
  17. type Slug string
  18. const (
  19. syncEvent = "slug_sync"
  20. I18nSlugKey i18n.ModuleKey = "I18nSlugKey"
  21. )
  22. var once = syncp.Once{}
  23. func Configure(b *presets.Builder, mb *presets.ModelBuilder) {
  24. once.Do(func() {
  25. b.I18n().
  26. RegisterForModule(language.English, I18nSlugKey, Messages_en_US).
  27. RegisterForModule(language.SimplifiedChinese, I18nSlugKey, Messages_zh_CN)
  28. b.GetWebBuilder().RegisterEventFunc(syncEvent, sync)
  29. })
  30. reflectType := reflect.Indirect(reflect.ValueOf(mb.NewModel())).Type()
  31. if reflectType.Kind() != reflect.Struct {
  32. panic("slug: model must be struct")
  33. }
  34. for i := 0; i < reflectType.NumField(); i++ {
  35. if reflectType.Field(i).Type != reflect.TypeOf(Slug("")) {
  36. continue
  37. }
  38. fieldName := reflectType.Field(i).Name
  39. relatedFieldName := strings.TrimSuffix(fieldName, "WithSlug")
  40. if _, ok := reflectType.FieldByName(relatedFieldName); ok {
  41. editingBuilder := mb.Editing()
  42. if f := editingBuilder.Field(relatedFieldName); f != nil {
  43. f.ComponentFunc(SlugEditingComponentFunc)
  44. }
  45. editingBuilder.Field(fieldName).ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) (r h.HTMLComponent) { return })
  46. editingBuilder.Field(fieldName).SetterFunc(SlugEditingSetterFunc)
  47. }
  48. }
  49. }
  50. func SlugEditingComponentFunc(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  51. msgr := i18n.MustGetModuleMessages(ctx.R, I18nSlugKey, Messages_en_US).(*Messages)
  52. slugFieldName := field.Name + "WithSlug"
  53. slugLabel := field.Label + " Slug"
  54. return VSheet(
  55. VTextField().
  56. Type("text").
  57. FieldName(field.Name).
  58. Label(field.Label).
  59. Value(field.Value(obj)).
  60. Attr("v-debounce:input", "300").
  61. Attr("@input:debounced", web.Plaid().
  62. EventFunc(syncEvent).Query("field_name", field.Name).Query("slug_label", slugLabel).Go()),
  63. VRow(
  64. VCol(
  65. web.Portal(
  66. VTextField().
  67. Type("text").
  68. FieldName(slugFieldName).
  69. Label(slugLabel).
  70. Value(reflectutils.MustGet(obj, slugFieldName).(Slug))).Name(portalName(slugFieldName)),
  71. ).Cols(8),
  72. VCol(
  73. VCheckbox().FieldName(checkBoxName(slugFieldName)).InputValue("checked").Label(fmt.Sprintf(msgr.Sync, strings.ToLower(field.Label))),
  74. ).Cols(4),
  75. ),
  76. )
  77. }
  78. func SlugEditingSetterFunc(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) (err error) {
  79. v := ctx.R.FormValue(field.Name)
  80. err = reflectutils.Set(obj, field.Name, Slug(v))
  81. if err != nil {
  82. return
  83. }
  84. return
  85. }
  86. func sync(ctx *web.EventContext) (r web.EventResponse, err error) {
  87. fieldName := ctx.R.FormValue("field_name")
  88. if fieldName == "" {
  89. return
  90. }
  91. slugFieldName := fieldName + "WithSlug"
  92. if checked := ctx.R.FormValue(checkBoxName(slugFieldName)); checked != "checked" {
  93. return
  94. }
  95. r.UpdatePortals = append(r.UpdatePortals, &web.PortalUpdate{
  96. Name: portalName(slugFieldName),
  97. Body: VTextField().
  98. Type("text").
  99. FieldName(slugFieldName).
  100. Label(ctx.R.FormValue("slug_label")).
  101. Value(slug(ctx.R.FormValue(fieldName))),
  102. })
  103. return
  104. }
  105. var (
  106. regexpNonAuthorizedChars = regexp.MustCompile("[^a-zA-Z0-9-_]")
  107. regexpMultipleDashes = regexp.MustCompile("-+")
  108. )
  109. func slug(value string) string {
  110. value = strings.TrimSpace(value)
  111. value = unidecode.Unidecode(value)
  112. value = strings.ToLower(value)
  113. value = regexpNonAuthorizedChars.ReplaceAllString(value, "-")
  114. value = regexpMultipleDashes.ReplaceAllString(value, "-")
  115. value = strings.Trim(value, "-_")
  116. return value
  117. }
  118. func portalName(field string) string {
  119. return fmt.Sprintf("%s_Portal", field)
  120. }
  121. func checkBoxName(field string) string {
  122. return fmt.Sprintf("%s_Checkbox", field)
  123. }