Add backend post handling, endpoints, rendering
This commit is contained in:
parent
0567564905
commit
3afdd8c1b4
@ -2,9 +2,12 @@ package writefreely
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"github.com/microcosm-cc/bluemonday"
|
"github.com/microcosm-cc/bluemonday"
|
||||||
stripmd "github.com/writeas/go-strip-markdown"
|
stripmd "github.com/writeas/go-strip-markdown"
|
||||||
"github.com/writeas/saturday"
|
"github.com/writeas/saturday"
|
||||||
|
"github.com/writeas/web-core/stringmanip"
|
||||||
|
"github.com/writeas/writefreely/parse"
|
||||||
"html"
|
"html"
|
||||||
"html/template"
|
"html/template"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -122,6 +125,30 @@ func postTitle(content, friendlyId string) string {
|
|||||||
return friendlyId
|
return friendlyId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: fix duplicated code from postTitle. postTitle is a widely used func we
|
||||||
|
// don't have time to investigate right now.
|
||||||
|
func friendlyPostTitle(content, friendlyId string) string {
|
||||||
|
const maxTitleLen = 80
|
||||||
|
|
||||||
|
// Strip HTML tags with bluemonday's StrictPolicy, then unescape the HTML
|
||||||
|
// entities added in by sanitizing the content.
|
||||||
|
content = html.UnescapeString(bluemonday.StrictPolicy().Sanitize(content))
|
||||||
|
|
||||||
|
content = strings.TrimLeftFunc(stripmd.Strip(content), unicode.IsSpace)
|
||||||
|
eol := strings.IndexRune(content, '\n')
|
||||||
|
blankLine := strings.Index(content, "\n\n")
|
||||||
|
if blankLine != -1 && blankLine <= eol && blankLine <= assumedTitleLen {
|
||||||
|
return strings.TrimSpace(content[:blankLine])
|
||||||
|
} else if eol == -1 && utf8.RuneCountInString(content) <= maxTitleLen {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
title, truncd := parse.TruncToWord(parse.PostLede(content, true), maxTitleLen)
|
||||||
|
if truncd {
|
||||||
|
title += "..."
|
||||||
|
}
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
|
||||||
func getSanitizationPolicy() *bluemonday.Policy {
|
func getSanitizationPolicy() *bluemonday.Policy {
|
||||||
policy := bluemonday.UGCPolicy()
|
policy := bluemonday.UGCPolicy()
|
||||||
policy.AllowAttrs("src", "style").OnElements("iframe", "video")
|
policy.AllowAttrs("src", "style").OnElements("iframe", "video")
|
||||||
@ -133,3 +160,62 @@ func getSanitizationPolicy() *bluemonday.Policy {
|
|||||||
policy.AllowURLSchemes("http", "https", "mailto", "xmpp")
|
policy.AllowURLSchemes("http", "https", "mailto", "xmpp")
|
||||||
return policy
|
return policy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sanitizePost(content string) string {
|
||||||
|
return strings.Replace(content, "<", "<", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// postDescription generates a description based on the given post content,
|
||||||
|
// title, and post ID. This doesn't consider a V2 post field, `title` when
|
||||||
|
// choosing what to generate. In case a post has a title, this function will
|
||||||
|
// fail, and logic should instead be implemented to skip this when there's no
|
||||||
|
// title, like so:
|
||||||
|
// var desc string
|
||||||
|
// if title == "" {
|
||||||
|
// desc = postDescription(content, title, friendlyId)
|
||||||
|
// } else {
|
||||||
|
// desc = shortPostDescription(content)
|
||||||
|
// }
|
||||||
|
func postDescription(content, title, friendlyId string) string {
|
||||||
|
maxLen := 140
|
||||||
|
|
||||||
|
if content == "" {
|
||||||
|
content = "Write Freely is a painless, simple, federated blogging platform."
|
||||||
|
} else {
|
||||||
|
fmtStr := "%s"
|
||||||
|
truncation := 0
|
||||||
|
if utf8.RuneCountInString(content) > maxLen {
|
||||||
|
// Post is longer than the max description, so let's show a better description
|
||||||
|
fmtStr = "%s..."
|
||||||
|
truncation = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
if title == friendlyId {
|
||||||
|
// No specific title was found; simply truncate the post, starting at the beginning
|
||||||
|
content = fmt.Sprintf(fmtStr, strings.Replace(stringmanip.Substring(content, 0, maxLen-truncation), "\n", " ", -1))
|
||||||
|
} else {
|
||||||
|
// There was a title, so return a real description
|
||||||
|
blankLine := strings.Index(content, "\n\n")
|
||||||
|
if blankLine < 0 {
|
||||||
|
blankLine = 0
|
||||||
|
}
|
||||||
|
truncd := stringmanip.Substring(content, blankLine, blankLine+maxLen-truncation)
|
||||||
|
contentNoNL := strings.Replace(truncd, "\n", " ", -1)
|
||||||
|
content = strings.TrimSpace(fmt.Sprintf(fmtStr, contentNoNL))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
func shortPostDescription(content string) string {
|
||||||
|
maxLen := 140
|
||||||
|
fmtStr := "%s"
|
||||||
|
truncation := 0
|
||||||
|
if utf8.RuneCountInString(content) > maxLen {
|
||||||
|
// Post is longer than the max description, so let's show a better description
|
||||||
|
fmtStr = "%s..."
|
||||||
|
truncation = 3
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(fmt.Sprintf(fmtStr, strings.Replace(stringmanip.Substring(content, 0, maxLen-truncation), "\n", " ", -1)))
|
||||||
|
}
|
||||||
|
31
routes.go
31
routes.go
@ -10,10 +10,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datastore) {
|
func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datastore) {
|
||||||
isSingleUser := !cfg.App.MultiUser
|
|
||||||
|
|
||||||
hostSubroute := cfg.App.Host[strings.Index(cfg.App.Host, "://")+3:]
|
hostSubroute := cfg.App.Host[strings.Index(cfg.App.Host, "://")+3:]
|
||||||
if isSingleUser {
|
if cfg.App.SingleUser {
|
||||||
hostSubroute = "{domain}"
|
hostSubroute = "{domain}"
|
||||||
} else {
|
} else {
|
||||||
if strings.HasPrefix(hostSubroute, "localhost") {
|
if strings.HasPrefix(hostSubroute, "localhost") {
|
||||||
@ -21,14 +19,13 @@ func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datasto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isSingleUser {
|
if cfg.App.SingleUser {
|
||||||
log.Info("Adding %s routes (single user)...", hostSubroute)
|
log.Info("Adding %s routes (single user)...", hostSubroute)
|
||||||
|
} else {
|
||||||
return
|
log.Info("Adding %s routes (multi-user)...", hostSubroute)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Primary app routes
|
// Primary app routes
|
||||||
log.Info("Adding %s routes (multi-user)...", hostSubroute)
|
|
||||||
write := r.Host(hostSubroute).Subrouter()
|
write := r.Host(hostSubroute).Subrouter()
|
||||||
|
|
||||||
// Federation endpoints
|
// Federation endpoints
|
||||||
@ -37,4 +34,24 @@ func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datasto
|
|||||||
ni := nodeinfo.NewService(*niCfg, nodeInfoResolver{cfg, db})
|
ni := nodeinfo.NewService(*niCfg, nodeInfoResolver{cfg, db})
|
||||||
write.HandleFunc(nodeinfo.NodeInfoPath, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfoDiscover)))
|
write.HandleFunc(nodeinfo.NodeInfoPath, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfoDiscover)))
|
||||||
write.HandleFunc(niCfg.InfoURL, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfo)))
|
write.HandleFunc(niCfg.InfoURL, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfo)))
|
||||||
|
|
||||||
|
// Handle posts
|
||||||
|
write.HandleFunc("/api/posts", handler.All(newPost)).Methods("POST")
|
||||||
|
posts := write.PathPrefix("/api/posts/").Subrouter()
|
||||||
|
posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.All(fetchPost)).Methods("GET")
|
||||||
|
posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.All(existingPost)).Methods("POST", "PUT")
|
||||||
|
posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.All(deletePost)).Methods("DELETE")
|
||||||
|
posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}/{property}", handler.All(fetchPostProperty)).Methods("GET")
|
||||||
|
posts.HandleFunc("/claim", handler.All(addPost)).Methods("POST")
|
||||||
|
posts.HandleFunc("/disperse", handler.All(dispersePost)).Methods("POST")
|
||||||
|
|
||||||
|
// All the existing stuff
|
||||||
|
write.HandleFunc("/{action}/edit", handler.Web(handleViewPad, UserLevelOptional)).Methods("GET")
|
||||||
|
write.HandleFunc("/{action}/meta", handler.Web(handleViewMeta, UserLevelOptional)).Methods("GET")
|
||||||
|
// Collections
|
||||||
|
if cfg.App.SingleUser {
|
||||||
|
} else {
|
||||||
|
// Posts
|
||||||
|
write.HandleFunc("/{post}", handler.Web(handleViewPost, UserLevelOptional))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user