diff options
-rw-r--r-- | content/item.go | 71 | ||||
-rw-r--r-- | management/editor/editor.go | 6 | ||||
-rw-r--r-- | management/editor/elements.go | 2 | ||||
-rw-r--r-- | management/manager/process.go | 72 | ||||
-rw-r--r-- | system/admin/handlers.go | 12 | ||||
-rw-r--r-- | system/db/content.go | 10 |
6 files changed, 81 insertions, 92 deletions
diff --git a/content/item.go b/content/item.go index eb79aa0..9ccd420 100644 --- a/content/item.go +++ b/content/item.go @@ -3,8 +3,13 @@ package content import ( "fmt" "net/http" + "regexp" + "strings" + "unicode" uuid "github.com/satori/go.uuid" + "golang.org/x/text/transform" + "golang.org/x/text/unicode/norm" ) // Sluggable makes a struct locatable by URL with it's own path @@ -27,6 +32,12 @@ type Identifiable interface { String() string } +// Sortable ensures data is sortable by time +type Sortable interface { + Time() int64 + Touch() int64 +} + // Hookable provides our user with an easy way to intercept or add functionality // to the different lifecycles/events a struct may encounter. Item implements // Hookable with no-ops so our user can override only whichever ones necessary. @@ -136,3 +147,63 @@ func (i Item) BeforeReject(req *http.Request) error { func (i Item) AfterReject(req *http.Request) error { return nil } + +// Slug returns a URL friendly string from the title of a post item +func Slug(i Identifiable) (string, error) { + // get the name of the post item + name := strings.TrimSpace(i.String()) + + // filter out non-alphanumeric character or non-whitespace + slug, err := stringToSlug(name) + if err != nil { + return "", err + } + + return slug, nil +} + +func isMn(r rune) bool { + return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks +} + +// modified version of: https://www.socketloop.com/tutorials/golang-format-strings-to-seo-friendly-url-example +func stringToSlug(s string) (string, error) { + src := []byte(strings.ToLower(s)) + + // convert all spaces to dash + rx := regexp.MustCompile("[[:space:]]") + src = rx.ReplaceAll(src, []byte("-")) + + // remove all blanks such as tab + rx = regexp.MustCompile("[[:blank:]]") + src = rx.ReplaceAll(src, []byte("")) + + rx = regexp.MustCompile("[!/:-@[-`{-~]") + src = rx.ReplaceAll(src, []byte("")) + + rx = regexp.MustCompile("/[^\x20-\x7F]/") + src = rx.ReplaceAll(src, []byte("")) + + rx = regexp.MustCompile("`&(amp;)?#?[a-z0-9]+;`i") + src = rx.ReplaceAll(src, []byte("-")) + + rx = regexp.MustCompile("`&([a-z])(acute|uml|circ|grave|ring|cedil|slash|tilde|caron|lig|quot|rsquo);`i") + src = rx.ReplaceAll(src, []byte("\\1")) + + rx = regexp.MustCompile("`[^a-z0-9]`i") + src = rx.ReplaceAll(src, []byte("-")) + + rx = regexp.MustCompile("`[-]+`") + src = rx.ReplaceAll(src, []byte("-")) + + str := strings.Replace(string(src), "'", "", -1) + str = strings.Replace(str, `"`, "", -1) + + t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC) + slug, _, err := transform.String(t, str) + if err != nil { + return "", err + } + + return strings.TrimSpace(slug), nil +} diff --git a/management/editor/editor.go b/management/editor/editor.go index 6b55a38..7194c27 100644 --- a/management/editor/editor.go +++ b/management/editor/editor.go @@ -13,12 +13,6 @@ type Editable interface { MarshalEditor() ([]byte, error) } -// Sortable ensures data is sortable by time -type Sortable interface { - Time() int64 - Touch() int64 -} - // Mergeable allows external post content to be approved and published through // the public-facing API type Mergeable interface { diff --git a/management/editor/elements.go b/management/editor/elements.go index 4a8ae55..bb2cb3f 100644 --- a/management/editor/elements.go +++ b/management/editor/elements.go @@ -242,8 +242,6 @@ func Select(fieldName string, p interface{}, attrs, options map[string]string) [ // find the field value in p to determine if an option is pre-selected fieldVal := valueFromStructField(fieldName, p) - // may need to alloc a buffer, as we will probably loop through options - // and append the []byte from domElement() called for each option attrs["class"] = "browser-default" sel := newElement("select", attrs["label"], fieldName, p, attrs) var opts []*element diff --git a/management/manager/process.go b/management/manager/process.go deleted file mode 100644 index ad6da94..0000000 --- a/management/manager/process.go +++ /dev/null @@ -1,72 +0,0 @@ -package manager - -import ( - "regexp" - "strings" - "unicode" - - "github.com/bosssauce/ponzu/content" - - "golang.org/x/text/transform" - "golang.org/x/text/unicode/norm" -) - -// Slug returns a URL friendly string from the title of a post item -func Slug(i content.Identifiable) (string, error) { - // get the name of the post item - name := strings.TrimSpace(i.String()) - - // filter out non-alphanumeric character or non-whitespace - slug, err := stringToSlug(name) - if err != nil { - return "", err - } - - return slug, nil -} - -func isMn(r rune) bool { - return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks -} - -// modified version of: https://www.socketloop.com/tutorials/golang-format-strings-to-seo-friendly-url-example -func stringToSlug(s string) (string, error) { - src := []byte(strings.ToLower(s)) - - // convert all spaces to dash - rx := regexp.MustCompile("[[:space:]]") - src = rx.ReplaceAll(src, []byte("-")) - - // remove all blanks such as tab - rx = regexp.MustCompile("[[:blank:]]") - src = rx.ReplaceAll(src, []byte("")) - - rx = regexp.MustCompile("[!/:-@[-`{-~]") - src = rx.ReplaceAll(src, []byte("")) - - rx = regexp.MustCompile("/[^\x20-\x7F]/") - src = rx.ReplaceAll(src, []byte("")) - - rx = regexp.MustCompile("`&(amp;)?#?[a-z0-9]+;`i") - src = rx.ReplaceAll(src, []byte("-")) - - rx = regexp.MustCompile("`&([a-z])(acute|uml|circ|grave|ring|cedil|slash|tilde|caron|lig|quot|rsquo);`i") - src = rx.ReplaceAll(src, []byte("\\1")) - - rx = regexp.MustCompile("`[^a-z0-9]`i") - src = rx.ReplaceAll(src, []byte("-")) - - rx = regexp.MustCompile("`[-]+`") - src = rx.ReplaceAll(src, []byte("-")) - - str := strings.Replace(string(src), "'", "", -1) - str = strings.Replace(str, `"`, "", -1) - - t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC) - slug, _, err := transform.String(t, str) - if err != nil { - return "", err - } - - return strings.TrimSpace(slug), nil -} diff --git a/system/admin/handlers.go b/system/admin/handlers.go index 1e6a26c..fed77f7 100644 --- a/system/admin/handlers.go +++ b/system/admin/handlers.go @@ -1048,10 +1048,10 @@ func contentsHandler(res http.ResponseWriter, req *http.Request) { // p is the asserted post as an Editable, t is the Type of the post. // specifier is passed to append a name to a namespace like __pending func adminPostListItem(e editor.Editable, typeName, status string) []byte { - s, ok := e.(editor.Sortable) + s, ok := e.(content.Sortable) if !ok { - log.Println("Content type", typeName, "doesn't implement editor.Sortable") - post := `<li class="col s12">Error retreiving data. Your data type doesn't implement necessary interfaces. (editor.Sortable)</li>` + log.Println("Content type", typeName, "doesn't implement content.Sortable") + post := `<li class="col s12">Error retreiving data. Your data type doesn't implement necessary interfaces. (content.Sortable)</li>` return []byte(post) } @@ -1312,12 +1312,12 @@ func editHandler(res http.ResponseWriter, req *http.Request) { return } } else { - s, ok := post.(content.Identifiable) + item, ok := post.(content.Identifiable) if !ok { - log.Println("Content type", t, "doesn't implement editor.Identifiable") + log.Println("Content type", t, "doesn't implement content.Identifiable") return } - s.SetItemID(-1) + item.SetItemID(-1) } m, err := manager.Manage(post.(editor.Editable), t) diff --git a/system/db/content.go b/system/db/content.go index 87b3e69..3293ff4 100644 --- a/system/db/content.go +++ b/system/db/content.go @@ -11,8 +11,6 @@ import ( "strings" "github.com/bosssauce/ponzu/content" - "github.com/bosssauce/ponzu/management/editor" - "github.com/bosssauce/ponzu/management/manager" "github.com/boltdb/bolt" "github.com/gorilla/schema" @@ -305,7 +303,7 @@ func Query(namespace string, opts QueryOptions) (int, [][]byte) { // correct bad input rather than return nil or error // similar to default case for opts.Order switch below if opts.Count < 0 { - opts.Count = 0 + opts.Count = -1 } if opts.Offset < 0 { @@ -428,7 +426,7 @@ func SortContent(namespace string) { return } - posts = append(posts, post.(editor.Sortable)) + posts = append(posts, post.(content.Sortable)) } // sort posts @@ -469,7 +467,7 @@ func SortContent(namespace string) { } -type sortableContent []editor.Sortable +type sortableContent []content.Sortable func (s sortableContent) Len() int { return len(s) @@ -502,7 +500,7 @@ func postToJSON(ns string, data url.Values) ([]byte, error) { // if the content has no slug, and has no specifier, create a slug, check it // for duplicates, and add it to our values if data.Get("slug") == "" && data.Get("__specifier") == "" { - slug, err := manager.Slug(post.(content.Identifiable)) + slug, err := content.Slug(post.(content.Identifiable)) if err != nil { return nil, err } |