Extract Blog Thumbnails for Archive Page
This commit is contained in:
parent
c556f862e5
commit
7376947e8a
|
@ -15,8 +15,9 @@ func UpdateIndex(p *Post) error {
|
||||||
|
|
||||||
// Index caches high level metadata about the blog's contents for fast access.
|
// Index caches high level metadata about the blog's contents for fast access.
|
||||||
type Index struct {
|
type Index struct {
|
||||||
Posts map[int]Post `json:"posts"`
|
Posts map[int]Post `json:"posts"`
|
||||||
Fragments map[string]int `json:"fragments"`
|
Fragments map[string]int `json:"fragments"`
|
||||||
|
Thumbnails map[int]string `json:"thumbnails"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIndex loads the index, or rebuilds it first if it doesn't exist.
|
// GetIndex loads the index, or rebuilds it first if it doesn't exist.
|
||||||
|
@ -33,8 +34,9 @@ func GetIndex() (*Index, error) {
|
||||||
// RebuildIndex builds the index from scratch.
|
// RebuildIndex builds the index from scratch.
|
||||||
func RebuildIndex() (*Index, error) {
|
func RebuildIndex() (*Index, error) {
|
||||||
idx := &Index{
|
idx := &Index{
|
||||||
Posts: map[int]Post{},
|
Posts: map[int]Post{},
|
||||||
Fragments: map[string]int{},
|
Fragments: map[string]int{},
|
||||||
|
Thumbnails: map[int]string{},
|
||||||
}
|
}
|
||||||
entries, _ := DB.List("blog/posts")
|
entries, _ := DB.List("blog/posts")
|
||||||
for _, doc := range entries {
|
for _, doc := range entries {
|
||||||
|
@ -65,6 +67,12 @@ func (idx *Index) Update(p *Post) error {
|
||||||
Updated: p.Updated,
|
Updated: p.Updated,
|
||||||
}
|
}
|
||||||
idx.Fragments[p.Fragment] = p.ID
|
idx.Fragments[p.Fragment] = p.ID
|
||||||
|
|
||||||
|
// Find a thumbnail image if possible.
|
||||||
|
if thumb, ok := p.ExtractThumbnail(); ok {
|
||||||
|
idx.Thumbnails[p.ID] = thumb
|
||||||
|
}
|
||||||
|
|
||||||
err := DB.Commit("blog/index", idx)
|
err := DB.Commit("blog/index", idx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,12 @@ var DB *jsondb.DB
|
||||||
|
|
||||||
var log *golog.Logger
|
var log *golog.Logger
|
||||||
|
|
||||||
|
// Regexp used to parse a thumbnail image from a blog post. Looks for the first
|
||||||
|
// URI component ending with an image extension.
|
||||||
|
var (
|
||||||
|
ThumbnailImageRegexp = regexp.MustCompile(`['"(]([a-zA-Z0-9-_:/?.=&]+\.(?:jpe?g|png|gif))['")]`)
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
log = golog.GetLogger("blog")
|
log = golog.GetLogger("blog")
|
||||||
}
|
}
|
||||||
|
@ -191,6 +197,16 @@ func (p *Post) Delete() error {
|
||||||
return idx.Delete(p)
|
return idx.Delete(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtractThumbnail searches and returns a thumbnail image to represent the
|
||||||
|
// post. This will be the first image embedded in the post, or nothing.
|
||||||
|
func (p *Post) ExtractThumbnail() (string, bool) {
|
||||||
|
result := ThumbnailImageRegexp.FindStringSubmatch(p.Body)
|
||||||
|
if len(result) < 2 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return result[1], true
|
||||||
|
}
|
||||||
|
|
||||||
// getNextID gets the next blog post ID.
|
// getNextID gets the next blog post ID.
|
||||||
func (p *Post) nextID() int {
|
func (p *Post) nextID() int {
|
||||||
// Highest ID seen so far.
|
// Highest ID seen so far.
|
||||||
|
|
63
models/posts/thumbnail_test.go
Normal file
63
models/posts/thumbnail_test.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package posts_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kirsle/blog/models/posts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestThumbnailRegexp(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
Text string
|
||||||
|
Expect string
|
||||||
|
ExpectFail bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var tests = []testCase{
|
||||||
|
{
|
||||||
|
Text: "Hello world",
|
||||||
|
ExpectFail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "Some text.\n\n![An image](/static/photos/Image-1.jpg)\n" +
|
||||||
|
"![Another image](/static/photos/Image-2.jpg)",
|
||||||
|
Expect: "/static/photos/Image-1.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: `<a href="/static/photos/12Abc456.jpg" target="_blank">` +
|
||||||
|
`<img src="/static/photos/34Xyz123.jpg"></a>`,
|
||||||
|
Expect: "/static/photos/12Abc456.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: `A markdown image: ![With text](/test1.gif) and an HTML ` +
|
||||||
|
`image: <img src="/test2.png">`,
|
||||||
|
Expect: "/test1.gif",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: `<a href="https://google.com/"><img src="https://example.com/logo.gif?query=string.jpg"></a>`,
|
||||||
|
Expect: "https://example.com/logo.gif?query=string.jpg",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
p := &posts.Post{
|
||||||
|
Body: test.Text,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, ok := p.ExtractThumbnail()
|
||||||
|
if !ok && !test.ExpectFail {
|
||||||
|
t.Errorf("Text: %s\nExpected to fail, but did not!\nGot: %s",
|
||||||
|
test.Text,
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != test.Expect {
|
||||||
|
t.Errorf("Text: %s\nExpect: %s\nGot: %s",
|
||||||
|
test.Text,
|
||||||
|
test.Expect,
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,22 +3,40 @@
|
||||||
|
|
||||||
<h1>Archive</h1>
|
<h1>Archive</h1>
|
||||||
|
|
||||||
|
{{ $thumbs := .Data.Thumbnails }}
|
||||||
|
|
||||||
{{ range .Data.Archive }}
|
{{ range .Data.Archive }}
|
||||||
<h3>{{ .Date.Format "January, 2006" }}</h3>
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>{{ .Date.Format "January, 2006" }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
<ul class="list-unstyled">
|
<div class="row">
|
||||||
{{ range .Posts }}
|
{{ range .Posts }}
|
||||||
<li class="list-item">
|
{{ $thumb := index $thumbs .ID }}
|
||||||
<a href="/{{ .Fragment }}">{{ .Title }}</a>
|
<div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-4">
|
||||||
<small class="blog-meta">
|
<div class="card bg-secondary"
|
||||||
{{ .Created.Format "Jan 02 2006" }}
|
style="height: auto; min-height: 150px;
|
||||||
{{ if ne .Privacy "public" }}
|
{{ if $thumb }}background-image: url({{ $thumb }}); background-size: cover{{ end }}
|
||||||
<span class="blog-{{ .Privacy }}">[{{ .Privacy }}]</span>
|
"
|
||||||
|
title="Tags: {{ range .Tags }}#{{ . }} {{ end }}">
|
||||||
|
<span class="p-1" style="background-color: RGBA(255, 255, 255, 0.8)">
|
||||||
|
<a href="/{{ .Fragment }}">{{ .Title }}</a><br>
|
||||||
|
<small class="blog-meta">
|
||||||
|
{{ .Created.Format "Jan 02 2006" }}
|
||||||
|
{{ if ne .Privacy "public" }}
|
||||||
|
<span class="blog-{{ .Privacy }}">[{{ .Privacy }}]</span>
|
||||||
|
{{ end }}
|
||||||
|
</small>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</small>
|
</div>
|
||||||
</li>
|
|
||||||
{{ end }}
|
</div>
|
||||||
</ul>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kirsle/blog/src/middleware/auth"
|
|
||||||
"github.com/kirsle/blog/models/posts"
|
"github.com/kirsle/blog/models/posts"
|
||||||
|
"github.com/kirsle/blog/src/middleware/auth"
|
||||||
"github.com/kirsle/blog/src/render"
|
"github.com/kirsle/blog/src/render"
|
||||||
"github.com/kirsle/blog/src/responses"
|
"github.com/kirsle/blog/src/responses"
|
||||||
"github.com/kirsle/blog/src/types"
|
"github.com/kirsle/blog/src/types"
|
||||||
|
@ -54,7 +54,8 @@ func archiveHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
v := map[string]interface{}{
|
v := map[string]interface{}{
|
||||||
"Archive": result,
|
"Archive": result,
|
||||||
|
"Thumbnails": idx.Thumbnails,
|
||||||
}
|
}
|
||||||
render.Template(w, r, "blog/archive", v)
|
render.Template(w, r, "blog/archive", v)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user