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.
|
// endpoints.
|
||||||
package apiutils |
package apiutil |
||||||
|
|
||||||
import ( |
import ( |
||||||
"context" |
"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