Branch markdown into its own subpackage
This commit is contained in:
parent
3d5147ff4c
commit
aabcf59181
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
"github.com/gorilla/feeds"
|
"github.com/gorilla/feeds"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/kirsle/blog/core/internal/markdown"
|
||||||
"github.com/kirsle/blog/core/internal/models/comments"
|
"github.com/kirsle/blog/core/internal/models/comments"
|
||||||
"github.com/kirsle/blog/core/internal/models/posts"
|
"github.com/kirsle/blog/core/internal/models/posts"
|
||||||
"github.com/kirsle/blog/core/internal/models/settings"
|
"github.com/kirsle/blog/core/internal/models/settings"
|
||||||
|
@ -439,7 +440,7 @@ func (b *Blog) RenderPost(p *posts.Post, indexView bool, numComments int) templa
|
||||||
// Render the post to HTML.
|
// Render the post to HTML.
|
||||||
var rendered template.HTML
|
var rendered template.HTML
|
||||||
if p.ContentType == string(MARKDOWN) {
|
if p.ContentType == string(MARKDOWN) {
|
||||||
rendered = template.HTML(b.RenderTrustedMarkdown(p.Body))
|
rendered = template.HTML(markdown.RenderTrustedMarkdown(p.Body))
|
||||||
} else {
|
} else {
|
||||||
rendered = template.HTML(p.Body)
|
rendered = template.HTML(p.Body)
|
||||||
}
|
}
|
||||||
|
@ -490,7 +491,7 @@ func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.FormValue("submit") {
|
switch r.FormValue("submit") {
|
||||||
case "preview":
|
case "preview":
|
||||||
if post.ContentType == string(MARKDOWN) {
|
if post.ContentType == string(MARKDOWN) {
|
||||||
v.Data["preview"] = template.HTML(b.RenderTrustedMarkdown(post.Body))
|
v.Data["preview"] = template.HTML(markdown.RenderTrustedMarkdown(post.Body))
|
||||||
} else {
|
} else {
|
||||||
v.Data["preview"] = template.HTML(post.Body)
|
v.Data["preview"] = template.HTML(post.Body)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
|
"github.com/kirsle/blog/core/internal/markdown"
|
||||||
"github.com/kirsle/blog/core/internal/models/comments"
|
"github.com/kirsle/blog/core/internal/models/comments"
|
||||||
"github.com/kirsle/blog/core/internal/models/users"
|
"github.com/kirsle/blog/core/internal/models/users"
|
||||||
)
|
)
|
||||||
|
@ -62,7 +63,7 @@ func (b *Blog) RenderComments(session *sessions.Session, csrfToken, url, subject
|
||||||
// Render all the comments in the thread.
|
// Render all the comments in the thread.
|
||||||
userMap := map[int]*users.User{}
|
userMap := map[int]*users.User{}
|
||||||
for _, c := range thread.Comments {
|
for _, c := range thread.Comments {
|
||||||
c.HTML = template.HTML(b.RenderMarkdown(c.Body))
|
c.HTML = template.HTML(markdown.RenderMarkdown(c.Body))
|
||||||
c.ThreadID = thread.ID
|
c.ThreadID = thread.ID
|
||||||
c.OriginURL = url
|
c.OriginURL = url
|
||||||
c.CSRF = csrfToken
|
c.CSRF = csrfToken
|
||||||
|
@ -203,7 +204,7 @@ func (b *Blog) CommentHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
c.Email = currentUser.Email
|
c.Email = currentUser.Email
|
||||||
c.LoadAvatar()
|
c.LoadAvatar()
|
||||||
}
|
}
|
||||||
c.HTML = template.HTML(b.RenderMarkdown(c.Body))
|
c.HTML = template.HTML(markdown.RenderMarkdown(c.Body))
|
||||||
case "post":
|
case "post":
|
||||||
if err := c.Validate(); err != nil {
|
if err := c.Validate(); err != nil {
|
||||||
v.Error = err
|
v.Error = err
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/kirsle/blog/core/internal/forms"
|
"github.com/kirsle/blog/core/internal/forms"
|
||||||
|
"github.com/kirsle/blog/core/internal/markdown"
|
||||||
"github.com/kirsle/blog/core/internal/models/settings"
|
"github.com/kirsle/blog/core/internal/models/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ func (b *Blog) ContactRoutes(r *mux.Router) {
|
||||||
Template: ".email/contact.gohtml",
|
Template: ".email/contact.gohtml",
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"Name": form.Name,
|
"Name": form.Name,
|
||||||
"Message": template.HTML(b.RenderMarkdown(form.Message)),
|
"Message": template.HTML(markdown.RenderMarkdown(form.Message)),
|
||||||
"Email": form.Email,
|
"Email": form.Email,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Package core implements the core source code of kirsle/blog.
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -7,14 +8,15 @@ import (
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"github.com/kirsle/blog/jsondb/caches"
|
"github.com/kirsle/blog/core/internal/markdown"
|
||||||
"github.com/kirsle/blog/jsondb/caches/null"
|
|
||||||
"github.com/kirsle/blog/jsondb/caches/redis"
|
|
||||||
"github.com/kirsle/blog/jsondb"
|
|
||||||
"github.com/kirsle/blog/core/internal/models/comments"
|
"github.com/kirsle/blog/core/internal/models/comments"
|
||||||
"github.com/kirsle/blog/core/internal/models/posts"
|
"github.com/kirsle/blog/core/internal/models/posts"
|
||||||
"github.com/kirsle/blog/core/internal/models/settings"
|
"github.com/kirsle/blog/core/internal/models/settings"
|
||||||
"github.com/kirsle/blog/core/internal/models/users"
|
"github.com/kirsle/blog/core/internal/models/users"
|
||||||
|
"github.com/kirsle/blog/jsondb"
|
||||||
|
"github.com/kirsle/blog/jsondb/caches"
|
||||||
|
"github.com/kirsle/blog/jsondb/caches/null"
|
||||||
|
"github.com/kirsle/blog/jsondb/caches/redis"
|
||||||
"github.com/shurcooL/github_flavored_markdown/gfmstyle"
|
"github.com/shurcooL/github_flavored_markdown/gfmstyle"
|
||||||
"github.com/urfave/negroni"
|
"github.com/urfave/negroni"
|
||||||
)
|
)
|
||||||
|
@ -76,6 +78,7 @@ func New(documentRoot, userRoot string) *Blog {
|
||||||
} else {
|
} else {
|
||||||
blog.Cache = cache
|
blog.Cache = cache
|
||||||
blog.DB.Cache = cache
|
blog.DB.Cache = cache
|
||||||
|
markdown.Cache = cache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
package core
|
// Package markdown implements a GitHub Flavored Markdown renderer.
|
||||||
|
package markdown
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -10,17 +11,23 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kirsle/blog/jsondb/caches"
|
||||||
|
"github.com/kirsle/golog"
|
||||||
"github.com/microcosm-cc/bluemonday"
|
"github.com/microcosm-cc/bluemonday"
|
||||||
"github.com/shurcooL/github_flavored_markdown"
|
"github.com/shurcooL/github_flavored_markdown"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Regexps for Markdown use cases.
|
// Regexps for Markdown use cases.
|
||||||
var (
|
var (
|
||||||
|
// Plug your own Redis cacher in.
|
||||||
|
Cache caches.Cacher
|
||||||
|
|
||||||
// Match title from the first `# h1` heading.
|
// Match title from the first `# h1` heading.
|
||||||
reMarkdownTitle = regexp.MustCompile(`(?m:^#([^#\r\n]+)$)`)
|
reMarkdownTitle = regexp.MustCompile(`(?m:^#([^#\r\n]+)$)`)
|
||||||
|
|
||||||
// Match fenced code blocks with languages defined.
|
// Match fenced code blocks with languages defined.
|
||||||
reFencedCode = regexp.MustCompile("```" + `([a-z]*)[\r\n]([\s\S]*?)[\r\n]\s*` + "```")
|
reFencedCode = regexp.MustCompile("```" + `([a-z]*)[\r\n]([\s\S]*?)[\r\n]\s*` + "```")
|
||||||
|
reFencedCodeClass = regexp.MustCompile("^highlight highlight-[a-zA-Z0-9]+$")
|
||||||
|
|
||||||
// Regexp to match fenced code blocks in rendered Markdown HTML.
|
// Regexp to match fenced code blocks in rendered Markdown HTML.
|
||||||
// Tweak this if you change Markdown engines later.
|
// Tweak this if you change Markdown engines later.
|
||||||
|
@ -28,6 +35,12 @@ var (
|
||||||
reDecodeBlock = regexp.MustCompile(`\[?FENCED_CODE_%d_BLOCK?\]`)
|
reDecodeBlock = regexp.MustCompile(`\[?FENCED_CODE_%d_BLOCK?\]`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var log *golog.Logger
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log = golog.GetLogger("blog")
|
||||||
|
}
|
||||||
|
|
||||||
// A container for parsed code blocks.
|
// A container for parsed code blocks.
|
||||||
type codeBlock struct {
|
type codeBlock struct {
|
||||||
placeholder int
|
placeholder int
|
||||||
|
@ -51,8 +64,8 @@ func TitleFromMarkdown(body string) (string, error) {
|
||||||
|
|
||||||
// RenderMarkdown renders markdown to HTML, safely. It uses blackfriday to
|
// RenderMarkdown renders markdown to HTML, safely. It uses blackfriday to
|
||||||
// render Markdown to HTML and then Bluemonday to sanitize the resulting HTML.
|
// render Markdown to HTML and then Bluemonday to sanitize the resulting HTML.
|
||||||
func (b *Blog) RenderMarkdown(input string) string {
|
func RenderMarkdown(input string) string {
|
||||||
unsafe := []byte(b.RenderTrustedMarkdown(input))
|
unsafe := []byte(RenderTrustedMarkdown(input))
|
||||||
|
|
||||||
// Sanitize HTML, but allow fenced code blocks to not get mangled in user
|
// Sanitize HTML, but allow fenced code blocks to not get mangled in user
|
||||||
// submitted comments.
|
// submitted comments.
|
||||||
|
@ -65,7 +78,7 @@ func (b *Blog) RenderMarkdown(input string) string {
|
||||||
// RenderTrustedMarkdown renders markdown to HTML, but without applying
|
// RenderTrustedMarkdown renders markdown to HTML, but without applying
|
||||||
// bluemonday filtering afterward. This is for blog posts and website
|
// bluemonday filtering afterward. This is for blog posts and website
|
||||||
// Markdown pages, not for user-submitted comments or things.
|
// Markdown pages, not for user-submitted comments or things.
|
||||||
func (b *Blog) RenderTrustedMarkdown(input string) string {
|
func RenderTrustedMarkdown(input string) string {
|
||||||
// Find and hang on to fenced code blocks.
|
// Find and hang on to fenced code blocks.
|
||||||
codeBlocks := []codeBlock{}
|
codeBlocks := []codeBlock{}
|
||||||
matches := reFencedCode.FindAllStringSubmatch(input, -1)
|
matches := reFencedCode.FindAllStringSubmatch(input, -1)
|
||||||
|
@ -87,7 +100,7 @@ func (b *Blog) RenderTrustedMarkdown(input string) string {
|
||||||
|
|
||||||
// Substitute fenced codes back in.
|
// Substitute fenced codes back in.
|
||||||
for _, block := range codeBlocks {
|
for _, block := range codeBlocks {
|
||||||
highlighted, _ := b.Pygmentize(block.language, block.source)
|
highlighted, _ := Pygmentize(block.language, block.source)
|
||||||
html = strings.Replace(html,
|
html = strings.Replace(html,
|
||||||
fmt.Sprintf("[?FENCED_CODE_%d_BLOCK?]", block.placeholder),
|
fmt.Sprintf("[?FENCED_CODE_%d_BLOCK?]", block.placeholder),
|
||||||
highlighted,
|
highlighted,
|
||||||
|
@ -105,7 +118,7 @@ func (b *Blog) RenderTrustedMarkdown(input string) string {
|
||||||
//
|
//
|
||||||
// The rendered result is cached in Redis if available, because the CLI
|
// The rendered result is cached in Redis if available, because the CLI
|
||||||
// call takes ~0.6s which is slow if you're rendering a lot of code blocks.
|
// call takes ~0.6s which is slow if you're rendering a lot of code blocks.
|
||||||
func (b *Blog) Pygmentize(language, source string) (string, error) {
|
func Pygmentize(language, source string) (string, error) {
|
||||||
var result string
|
var result string
|
||||||
|
|
||||||
// Hash the source for the cache key.
|
// Hash the source for the cache key.
|
||||||
|
@ -114,10 +127,11 @@ func (b *Blog) Pygmentize(language, source string) (string, error) {
|
||||||
hash := fmt.Sprintf("%x", h.Sum(nil))
|
hash := fmt.Sprintf("%x", h.Sum(nil))
|
||||||
cacheKey := "pygmentize:" + hash
|
cacheKey := "pygmentize:" + hash
|
||||||
|
|
||||||
|
_ = cacheKey
|
||||||
// Do we have it cached?
|
// Do we have it cached?
|
||||||
if cached, err := b.Cache.Get(cacheKey); err == nil && len(cached) > 0 {
|
// if cached, err := b.Cache.Get(cacheKey); err == nil && len(cached) > 0 {
|
||||||
return string(cached), nil
|
// return string(cached), nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Defer to the `pygmentize` command
|
// Defer to the `pygmentize` command
|
||||||
bin := "pygmentize"
|
bin := "pygmentize"
|
||||||
|
@ -140,10 +154,10 @@ func (b *Blog) Pygmentize(language, source string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
result = out.String()
|
result = out.String()
|
||||||
err := b.Cache.Set(cacheKey, []byte(result), 60*60*24) // cool md5's don't change
|
// err := b.Cache.Set(cacheKey, []byte(result), 60*60*24) // cool md5's don't change
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Error("Couldn't cache Pygmentize output: %s", err)
|
// log.Error("Couldn't cache Pygmentize output: %s", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kirsle/blog/core/internal/markdown"
|
||||||
"github.com/kirsle/blog/core/internal/models/comments"
|
"github.com/kirsle/blog/core/internal/models/comments"
|
||||||
"github.com/kirsle/blog/core/internal/models/settings"
|
"github.com/kirsle/blog/core/internal/models/settings"
|
||||||
"github.com/microcosm-cc/bluemonday"
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
@ -107,7 +108,7 @@ func (b *Blog) NotifyComment(c *comments.Comment) {
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"Name": c.Name,
|
"Name": c.Name,
|
||||||
"Subject": c.Subject,
|
"Subject": c.Subject,
|
||||||
"Body": template.HTML(b.RenderMarkdown(c.Body)),
|
"Body": template.HTML(markdown.RenderMarkdown(c.Body)),
|
||||||
"URL": strings.Trim(s.Site.URL, "/") + c.OriginURL,
|
"URL": strings.Trim(s.Site.URL, "/") + c.OriginURL,
|
||||||
"QuickDelete": fmt.Sprintf("%s/comments/quick-delete?t=%s&d=%s",
|
"QuickDelete": fmt.Sprintf("%s/comments/quick-delete?t=%s&d=%s",
|
||||||
strings.Trim(s.Site.URL, "/"),
|
strings.Trim(s.Site.URL, "/"),
|
||||||
|
|
|
@ -8,6 +8,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kirsle/blog/core/internal/markdown"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PageHandler is the catch-all route handler, for serving static web pages.
|
// PageHandler is the catch-all route handler, for serving static web pages.
|
||||||
|
@ -54,8 +56,8 @@ func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Render it to HTML and find out its title.
|
// Render it to HTML and find out its title.
|
||||||
body := string(source)
|
body := string(source)
|
||||||
html := b.RenderTrustedMarkdown(body)
|
html := markdown.RenderTrustedMarkdown(body)
|
||||||
title, _ := TitleFromMarkdown(body)
|
title, _ := markdown.TitleFromMarkdown(body)
|
||||||
|
|
||||||
b.RenderTemplate(w, r, ".markdown", NewVars(map[interface{}]interface{}{
|
b.RenderTemplate(w, r, ".markdown", NewVars(map[interface{}]interface{}{
|
||||||
"Title": title,
|
"Title": title,
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import "regexp"
|
|
||||||
|
|
||||||
var (
|
|
||||||
// CSS classes for Markdown fenced code blocks
|
|
||||||
reFencedCodeClass = regexp.MustCompile("^highlight highlight-[a-zA-Z0-9]+$")
|
|
||||||
)
|
|
Loading…
Reference in New Issue
Block a user