parent
dd354bc323
commit
4c04177c05
@ -1,6 +1,6 @@ |
||||
// Package apiutils contains utilities which are useful for implementing api
|
||||
// Package apiutil contains utilities which are useful for implementing api
|
||||
// endpoints.
|
||||
package apiutils |
||||
package apiutil |
||||
|
||||
import ( |
||||
"context" |
@ -1,96 +0,0 @@ |
||||
package post |
||||
|
||||
import ( |
||||
_ "embed" |
||||
"fmt" |
||||
"html/template" |
||||
"io" |
||||
|
||||
"github.com/gomarkdown/markdown" |
||||
"github.com/gomarkdown/markdown/html" |
||||
"github.com/gomarkdown/markdown/parser" |
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/tpl" |
||||
) |
||||
|
||||
// RenderablePost is a Post wrapped with extra information necessary for
|
||||
// rendering.
|
||||
type RenderablePost struct { |
||||
StoredPost |
||||
SeriesPrevious, SeriesNext *StoredPost |
||||
} |
||||
|
||||
// NewRenderablePost wraps an existing Post such that it can be rendered.
|
||||
func NewRenderablePost(store Store, post StoredPost) (RenderablePost, error) { |
||||
|
||||
renderablePost := RenderablePost{ |
||||
StoredPost: post, |
||||
} |
||||
|
||||
if post.Series != "" { |
||||
|
||||
seriesPosts, err := store.GetBySeries(post.Series) |
||||
if err != nil { |
||||
return RenderablePost{}, fmt.Errorf( |
||||
"fetching posts for series %q: %w", |
||||
post.Series, err, |
||||
) |
||||
} |
||||
|
||||
var foundThis bool |
||||
|
||||
for i := range seriesPosts { |
||||
|
||||
seriesPost := seriesPosts[i] |
||||
|
||||
if seriesPost.ID == post.ID { |
||||
foundThis = true |
||||
continue |
||||
} |
||||
|
||||
if !foundThis { |
||||
renderablePost.SeriesPrevious = &seriesPost |
||||
continue |
||||
} |
||||
|
||||
renderablePost.SeriesNext = &seriesPost |
||||
break |
||||
} |
||||
} |
||||
|
||||
return renderablePost, nil |
||||
} |
||||
|
||||
// Renderer takes a Post and renders it to some encoding.
|
||||
type Renderer interface { |
||||
Render(io.Writer, RenderablePost) error |
||||
} |
||||
|
||||
func mdBodyToHTML(body []byte) []byte { |
||||
parserExt := parser.CommonExtensions | parser.AutoHeadingIDs |
||||
parser := parser.NewWithExtensions(parserExt) |
||||
|
||||
htmlFlags := html.CommonFlags | html.HrefTargetBlank |
||||
htmlRenderer := html.NewRenderer(html.RendererOptions{Flags: htmlFlags}) |
||||
|
||||
return markdown.ToHTML(body, parser, htmlRenderer) |
||||
} |
||||
|
||||
type mdHTMLRenderer struct{} |
||||
|
||||
// NewMarkdownToHTMLRenderer renders Posts from markdown to HTML.
|
||||
func NewMarkdownToHTMLRenderer() Renderer { |
||||
return mdHTMLRenderer{} |
||||
} |
||||
|
||||
func (r mdHTMLRenderer) Render(into io.Writer, post RenderablePost) error { |
||||
|
||||
data := struct { |
||||
RenderablePost |
||||
Body template.HTML |
||||
}{ |
||||
RenderablePost: post, |
||||
Body: template.HTML(mdBodyToHTML([]byte(post.Body))), |
||||
} |
||||
|
||||
return tpl.HTML.ExecuteTemplate(into, "post.html", data) |
||||
} |
@ -1,92 +0,0 @@ |
||||
package post |
||||
|
||||
import ( |
||||
"bytes" |
||||
"strconv" |
||||
"strings" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestMarkdownBodyToHTML(t *testing.T) { |
||||
|
||||
tests := []struct { |
||||
body string |
||||
exp string |
||||
}{ |
||||
{ |
||||
body: ` |
||||
# Foo |
||||
`, |
||||
exp: `<h1 id="foo">Foo</h1>`, |
||||
}, |
||||
{ |
||||
body: ` |
||||
this is a body |
||||
|
||||
this is another |
||||
`, |
||||
exp: ` |
||||
<p>this is a body</p> |
||||
|
||||
<p>this is another</p>`, |
||||
}, |
||||
{ |
||||
body: `this is a [link](somewhere.html)`, |
||||
exp: `<p>this is a <a href="somewhere.html" target="_blank">link</a></p>`, |
||||
}, |
||||
} |
||||
|
||||
for i, test := range tests { |
||||
t.Run(strconv.Itoa(i), func(t *testing.T) { |
||||
|
||||
outB := mdBodyToHTML([]byte(test.body)) |
||||
out := string(outB) |
||||
|
||||
// just to make the tests nicer
|
||||
out = strings.TrimSpace(out) |
||||
test.exp = strings.TrimSpace(test.exp) |
||||
|
||||
assert.Equal(t, test.exp, out) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestMarkdownToHTMLRenderer(t *testing.T) { |
||||
|
||||
r := NewMarkdownToHTMLRenderer() |
||||
|
||||
post := RenderablePost{ |
||||
StoredPost: StoredPost{ |
||||
Post: Post{ |
||||
ID: "foo", |
||||
Title: "Foo", |
||||
Description: "Bar.", |
||||
Body: "This is the body.", |
||||
Series: "baz", |
||||
}, |
||||
PublishedAt: time.Now(), |
||||
}, |
||||
|
||||
SeriesPrevious: &StoredPost{ |
||||
Post: Post{ |
||||
ID: "foo-prev", |
||||
Title: "Foo Prev", |
||||
}, |
||||
}, |
||||
|
||||
SeriesNext: &StoredPost{ |
||||
Post: Post{ |
||||
ID: "foo-next", |
||||
Title: "Foo Next", |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
buf := new(bytes.Buffer) |
||||
err := r.Render(buf, post) |
||||
assert.NoError(t, err) |
||||
t.Log(buf.String()) |
||||
} |
@ -1,12 +0,0 @@ |
||||
// Package tpl contains template files which are used to render the blog.
|
||||
package tpl |
||||
|
||||
import ( |
||||
"embed" |
||||
html_tpl "html/template" |
||||
) |
||||
|
||||
//go:embed *
|
||||
var fs embed.FS |
||||
|
||||
var HTML = html_tpl.Must(html_tpl.ParseFS(fs, "html/*")) |
Loading…
Reference in new issue