mirror of https://github.com/jlelse/GoBlog
Use more writers and streaming when possible
This commit is contained in:
parent
5771a945cf
commit
68daab64e3
|
@ -113,7 +113,7 @@ func (a *goBlog) toASNote(p *post) *asNote {
|
|||
as.Type = "Note"
|
||||
}
|
||||
// Content
|
||||
as.Content = string(a.postHtml(p, true))
|
||||
as.Content = a.postHtml(p, true)
|
||||
// Attachments
|
||||
if images := p.Parameters[a.cfg.Micropub.PhotoParam]; len(images) > 0 {
|
||||
for _, image := range images {
|
||||
|
|
6
check.go
6
check.go
|
@ -12,6 +12,7 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go.goblog.app/app/pkgs/bufferpool"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
|
@ -126,7 +127,10 @@ func (a *goBlog) checkLinks(w io.Writer, posts ...*post) error {
|
|||
|
||||
func (a *goBlog) allLinks(posts ...*post) (allLinks []*stringPair, err error) {
|
||||
for _, p := range posts {
|
||||
links, err := allLinksFromHTMLString(string(a.postHtml(p, true)), a.fullPostURL(p))
|
||||
contentBuf := bufferpool.Get()
|
||||
a.postHtmlToWriter(contentBuf, p, true)
|
||||
links, err := allLinksFromHTML(contentBuf, a.fullPostURL(p))
|
||||
bufferpool.Put(contentBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
10
editor.go
10
editor.go
|
@ -45,7 +45,7 @@ func (a *goBlog) serveEditorPreview(w http.ResponseWriter, r *http.Request) {
|
|||
continue
|
||||
}
|
||||
// Create preview
|
||||
preview, err := a.createMarkdownPreview(blog, message)
|
||||
preview, err := a.createMarkdownPreview(blog, string(message))
|
||||
if err != nil {
|
||||
preview = []byte(err.Error())
|
||||
}
|
||||
|
@ -57,12 +57,10 @@ func (a *goBlog) serveEditorPreview(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func (a *goBlog) createMarkdownPreview(blog string, markdown []byte) (rendered []byte, err error) {
|
||||
func (a *goBlog) createMarkdownPreview(blog string, markdown string) (rendered []byte, err error) {
|
||||
p := &post{
|
||||
Content: string(markdown),
|
||||
Blog: blog,
|
||||
Path: "/editor/preview",
|
||||
Published: localNowString(),
|
||||
Blog: blog,
|
||||
Content: markdown,
|
||||
}
|
||||
err = a.computeExtraPostParameters(p)
|
||||
if err != nil {
|
||||
|
|
7
feeds.go
7
feeds.go
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/araddon/dateparse"
|
||||
"github.com/jlelse/feeds"
|
||||
"go.goblog.app/app/pkgs/bufferpool"
|
||||
"go.goblog.app/app/pkgs/contenttype"
|
||||
)
|
||||
|
||||
|
@ -38,16 +39,18 @@ func (a *goBlog) generateFeed(blog string, f feedType, w http.ResponseWriter, r
|
|||
},
|
||||
}
|
||||
for _, p := range posts {
|
||||
content, _ := a.min.MinifyString(contenttype.HTML, a.feedHtml(p))
|
||||
buf := bufferpool.Get()
|
||||
a.feedHtml(buf, p)
|
||||
feed.Add(&feeds.Item{
|
||||
Title: p.RenderedTitle,
|
||||
Link: &feeds.Link{Href: a.fullPostURL(p)},
|
||||
Description: a.postSummary(p),
|
||||
Id: p.Path,
|
||||
Content: content,
|
||||
Content: buf.String(),
|
||||
Created: timeNoErr(dateparse.ParseLocal(p.Published)),
|
||||
Updated: timeNoErr(dateparse.ParseLocal(p.Updated)),
|
||||
})
|
||||
bufferpool.Put(buf)
|
||||
}
|
||||
var err error
|
||||
var feedBuffer bytes.Buffer
|
||||
|
|
14
markdown.go
14
markdown.go
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"html/template"
|
||||
"io"
|
||||
|
||||
marktag "git.jlel.se/jlelse/goldmark-mark"
|
||||
|
@ -94,19 +93,6 @@ func (a *goBlog) renderMarkdownToWriter(w io.Writer, source string, absoluteLink
|
|||
return err
|
||||
}
|
||||
|
||||
func (a *goBlog) renderMarkdownAsHTML(source string, absoluteLinks bool) (rendered template.HTML, err error) {
|
||||
b, err := a.renderMarkdown(source, absoluteLinks)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return template.HTML(b), nil
|
||||
}
|
||||
|
||||
func (a *goBlog) safeRenderMarkdownAsHTML(source string) template.HTML {
|
||||
h, _ := a.renderMarkdownAsHTML(source, false)
|
||||
return h
|
||||
}
|
||||
|
||||
func (a *goBlog) renderText(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
|
|
|
@ -79,11 +79,6 @@ func Test_markdown(t *testing.T) {
|
|||
assert.Equal(t, "Test’s", app.renderMdTitle("Test's"))
|
||||
assert.Equal(t, "😂", app.renderMdTitle(":joy:"))
|
||||
assert.Equal(t, "<b></b>", app.renderMdTitle("<b></b>"))
|
||||
|
||||
// Template func
|
||||
|
||||
renderedText = string(app.safeRenderMarkdownAsHTML("[Relative](/relative)"))
|
||||
assert.Contains(t, renderedText, `href="/relative"`)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"github.com/thoas/go-funk"
|
||||
|
@ -397,7 +398,7 @@ func (a *goBlog) computeExtraPostParameters(p *post) error {
|
|||
}
|
||||
if p.Published == "" && p.Section != "" {
|
||||
// Has no published date, but section -> published now
|
||||
p.Published = localNowString()
|
||||
p.Published = time.Now().Local().Format(time.RFC3339)
|
||||
}
|
||||
// Add images not in content
|
||||
images := p.Parameters[a.cfg.Micropub.PhotoParam]
|
||||
|
|
4
posts.go
4
posts.go
|
@ -4,12 +4,10 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
@ -31,8 +29,6 @@ type post struct {
|
|||
// Not persisted
|
||||
Slug string
|
||||
RenderedTitle string
|
||||
renderCache map[bool]template.HTML
|
||||
renderMutex sync.RWMutex
|
||||
}
|
||||
|
||||
type postStatus string
|
||||
|
|
106
postsFuncs.go
106
postsFuncs.go
|
@ -2,13 +2,13 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
gogeouri "git.jlel.se/jlelse/go-geouri"
|
||||
"github.com/araddon/dateparse"
|
||||
"go.goblog.app/app/pkgs/bufferpool"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
@ -34,69 +34,56 @@ func (p *post) firstParameter(parameter string) (result string) {
|
|||
return
|
||||
}
|
||||
|
||||
func (a *goBlog) postHtml(p *post, absolute bool) template.HTML {
|
||||
p.renderMutex.RLock()
|
||||
// Check cache
|
||||
if r, ok := p.renderCache[absolute]; ok && r != "" {
|
||||
p.renderMutex.RUnlock()
|
||||
return r
|
||||
}
|
||||
p.renderMutex.RUnlock()
|
||||
// No cache, build it...
|
||||
p.renderMutex.Lock()
|
||||
defer p.renderMutex.Unlock()
|
||||
// Build HTML
|
||||
var htmlBuilder strings.Builder
|
||||
// Add audio to the top
|
||||
for _, a := range p.Parameters[a.cfg.Micropub.AudioParam] {
|
||||
htmlBuilder.WriteString(`<audio controls preload=none><source src="`)
|
||||
htmlBuilder.WriteString(a)
|
||||
htmlBuilder.WriteString(`"/></audio>`)
|
||||
}
|
||||
// Render markdown
|
||||
htmlContent, err := a.renderMarkdown(p.Content, absolute)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return ""
|
||||
}
|
||||
htmlBuilder.Write(htmlContent)
|
||||
// Add bookmark links to the bottom
|
||||
for _, l := range p.Parameters[a.cfg.Micropub.BookmarkParam] {
|
||||
htmlBuilder.WriteString(`<p><a class=u-bookmark-of href="`)
|
||||
htmlBuilder.WriteString(l)
|
||||
htmlBuilder.WriteString(`" target=_blank rel=noopener>`)
|
||||
htmlBuilder.WriteString(l)
|
||||
htmlBuilder.WriteString(`</a></p>`)
|
||||
}
|
||||
// Cache
|
||||
html := template.HTML(htmlBuilder.String())
|
||||
if p.renderCache == nil {
|
||||
p.renderCache = map[bool]template.HTML{}
|
||||
}
|
||||
p.renderCache[absolute] = html
|
||||
return html
|
||||
func (a *goBlog) postHtml(p *post, absolute bool) (res string) {
|
||||
buf := bufferpool.Get()
|
||||
a.postHtmlToWriter(buf, p, absolute)
|
||||
res = buf.String()
|
||||
bufferpool.Put(buf)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *goBlog) feedHtml(p *post) string {
|
||||
var htmlBuilder strings.Builder
|
||||
func (a *goBlog) postHtmlToWriter(w io.Writer, p *post, absolute bool) {
|
||||
// Build HTML
|
||||
hb := newHtmlBuilder(w)
|
||||
// Add audio to the top
|
||||
for _, a := range p.Parameters[a.cfg.Micropub.AudioParam] {
|
||||
hb.writeElementOpen("audio", "controls", "preload", "none")
|
||||
hb.writeElementOpen("source", "src", a)
|
||||
hb.writeElementClose("source")
|
||||
hb.writeElementClose("audio")
|
||||
}
|
||||
// Render markdown
|
||||
_ = a.renderMarkdownToWriter(w, p.Content, absolute)
|
||||
// Add bookmark links to the bottom
|
||||
for _, l := range p.Parameters[a.cfg.Micropub.BookmarkParam] {
|
||||
hb.writeElementOpen("p")
|
||||
hb.writeElementOpen("a", "class", "u-bookmark-of", "href", l, "target", "_blank", "rel", "noopener noreferrer")
|
||||
hb.writeEscaped(l)
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("p")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *goBlog) feedHtml(w io.Writer, p *post) {
|
||||
hb := newHtmlBuilder(w)
|
||||
// Add TTS audio to the top
|
||||
for _, a := range p.Parameters[ttsParameter] {
|
||||
htmlBuilder.WriteString(`<audio controls preload=none><source src="`)
|
||||
htmlBuilder.WriteString(a)
|
||||
htmlBuilder.WriteString(`"/></audio>`)
|
||||
hb.writeElementOpen("audio", "controls", "preload", "none")
|
||||
hb.writeElementOpen("source", "src", a)
|
||||
hb.writeElementClose("source")
|
||||
hb.writeElementClose("audio")
|
||||
}
|
||||
// Add post HTML
|
||||
htmlBuilder.WriteString(string(a.postHtml(p, true)))
|
||||
a.postHtmlToWriter(hb, p, true)
|
||||
// Add link to interactions and comments
|
||||
blogConfig := a.cfg.Blogs[defaultIfEmpty(p.Blog, a.cfg.DefaultBlog)]
|
||||
if cc := blogConfig.Comments; cc != nil && cc.Enabled {
|
||||
htmlBuilder.WriteString(`<p><a href="`)
|
||||
htmlBuilder.WriteString(a.getFullAddress(p.Path))
|
||||
htmlBuilder.WriteString(`#interactions">`)
|
||||
htmlBuilder.WriteString(a.ts.GetTemplateStringVariant(blogConfig.Lang, "interactions"))
|
||||
htmlBuilder.WriteString(`</a></p>`)
|
||||
hb.writeElementOpen("p")
|
||||
hb.writeElementOpen("a", "href", a.getFullAddress(p.Path)+"#interactions")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(blogConfig.Lang, "interactions"))
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("p")
|
||||
}
|
||||
return htmlBuilder.String()
|
||||
}
|
||||
|
||||
const summaryDivider = "<!--more-->"
|
||||
|
@ -106,11 +93,12 @@ func (a *goBlog) postSummary(p *post) (summary string) {
|
|||
if summary != "" {
|
||||
return
|
||||
}
|
||||
html := string(a.postHtml(p, false))
|
||||
if splitted := strings.Split(html, summaryDivider); len(splitted) > 1 {
|
||||
summary = htmlText(splitted[0])
|
||||
if splitted := strings.Split(p.Content, summaryDivider); len(splitted) > 1 {
|
||||
rendered, _ := a.renderMarkdown(splitted[0], false)
|
||||
summary = htmlText(string(rendered))
|
||||
} else {
|
||||
summary = strings.Split(htmlText(html), "\n\n")[0]
|
||||
rendered, _ := a.renderMarkdown(splitted[0], false)
|
||||
summary = strings.Split(htmlText(string(rendered)), "\n\n")[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
2
tts.go
2
tts.go
|
@ -65,7 +65,7 @@ func (a *goBlog) createPostTTSAudio(p *post) error {
|
|||
parts = append(parts, a.renderMdTitle(title))
|
||||
}
|
||||
// Add body split into paragraphs because of 5000 character limit
|
||||
parts = append(parts, strings.Split(htmlText(string(a.postHtml(p, false))), "\n\n")...)
|
||||
parts = append(parts, strings.Split(htmlText(a.postHtml(p, false)), "\n\n")...)
|
||||
|
||||
// Create TTS audio for each part
|
||||
partsBuffers := make([]io.Reader, len(parts))
|
||||
|
|
68
ui.go
68
ui.go
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hacdias/indieauth"
|
||||
|
@ -20,7 +19,7 @@ func (a *goBlog) renderEditorPreview(hb *htmlBuilder, bc *configBlog, p *post) {
|
|||
a.renderPostMeta(hb, p, bc, "preview")
|
||||
if p.Content != "" {
|
||||
hb.writeElementOpen("div")
|
||||
hb.writeHtml(a.postHtml(p, true))
|
||||
a.postHtmlToWriter(hb, p, true)
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
a.renderPostTax(hb, p, bc)
|
||||
|
@ -859,14 +858,11 @@ func (a *goBlog) renderPost(hb *htmlBuilder, rd *renderData) {
|
|||
if !ok {
|
||||
return
|
||||
}
|
||||
postHtml := a.postHtml(p, false)
|
||||
a.renderBase(
|
||||
hb, rd,
|
||||
func(hb *htmlBuilder) {
|
||||
a.renderTitleTag(hb, rd.Blog, p.RenderedTitle)
|
||||
if strings.Contains(string(postHtml), "c-chroma") {
|
||||
hb.writeElementOpen("link", "rel", "stylesheet", "href", a.assetFileName("css/chroma.css"))
|
||||
}
|
||||
hb.writeElementOpen("link", "rel", "stylesheet", "href", a.assetFileName("css/chroma.css"))
|
||||
a.renderPostHeadMeta(hb, p, rd.Canonical)
|
||||
if su := a.shortPostURL(p); su != "" {
|
||||
hb.writeElementOpen("link", "rel", "shortlink", "href", su)
|
||||
|
@ -924,7 +920,7 @@ func (a *goBlog) renderPost(hb *htmlBuilder, rd *renderData) {
|
|||
if p.Content != "" {
|
||||
// Content
|
||||
hb.writeElementOpen("div", "class", "e-content")
|
||||
hb.writeHtml(postHtml)
|
||||
a.postHtmlToWriter(hb, p, false)
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
// GPS Track
|
||||
|
@ -1027,7 +1023,7 @@ func (a *goBlog) renderStaticHome(hb *htmlBuilder, rd *renderData) {
|
|||
if p.Content != "" {
|
||||
// Content
|
||||
hb.writeElementOpen("div", "class", "e-content")
|
||||
hb.writeHtml(a.postHtml(p, false))
|
||||
a.postHtmlToWriter(hb, p, false)
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
// Author
|
||||
|
@ -1142,7 +1138,7 @@ func (a *goBlog) renderEditorFiles(hb *htmlBuilder, rd *renderData) {
|
|||
usesString := a.ts.GetTemplateStringVariant(rd.Blog.Lang, "fileuses")
|
||||
for i, f := range ef.files {
|
||||
hb.writeElementOpen("option", "value", f.Name)
|
||||
hb.writeEscaped(fmt.Sprintf("%s (%s), %s, ~%d %s", f.Name, isoDateFormat(f.Time.String()), mBytesString(f.Size), ef.uses[i], usesString))
|
||||
hb.writeEscaped(fmt.Sprintf("%s (%s), %s, ~%d %s", f.Name, f.Time.Local().Format(isoDateFormat), mBytesString(f.Size), ef.uses[i], usesString))
|
||||
hb.writeElementClose("option")
|
||||
}
|
||||
hb.writeElementClose("select")
|
||||
|
@ -1305,6 +1301,7 @@ func (a *goBlog) renderWebmentionAdmin(hb *htmlBuilder, rd *renderData) {
|
|||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "webmentions"))
|
||||
hb.writeElementClose("h1")
|
||||
// Notifications
|
||||
tdLocale := matchTimeDiffLocale(rd.Blog.Lang)
|
||||
for _, m := range wrd.mentions {
|
||||
hb.writeElementOpen("div", "id", fmt.Sprintf("mention-%d", m.ID), "class", "p")
|
||||
hb.writeElementOpen("p")
|
||||
|
@ -1330,7 +1327,7 @@ func (a *goBlog) renderWebmentionAdmin(hb *htmlBuilder, rd *renderData) {
|
|||
hb.writeElementOpen("br")
|
||||
// Date
|
||||
hb.writeEscaped("Created: ")
|
||||
hb.writeEscaped(unixToLocalDateString(m.Created))
|
||||
hb.writeEscaped(timediff.TimeDiff(time.Unix(m.Created, 0), timediff.WithLocale(tdLocale)))
|
||||
hb.writeElementOpen("br")
|
||||
hb.writeElementOpen("br")
|
||||
// Author
|
||||
|
@ -1447,36 +1444,24 @@ func (a *goBlog) renderEditor(hb *htmlBuilder, rd *renderData) {
|
|||
hb.writeElementOpen("h2")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "posts"))
|
||||
hb.writeElementClose("h2")
|
||||
// Template
|
||||
postsListLink := func(path, title string) {
|
||||
hb.writeElementOpen("p")
|
||||
hb.writeElementOpen("a", "href", rd.Blog.getRelativePath(path))
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, title))
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("p")
|
||||
}
|
||||
// Drafts
|
||||
hb.writeElementOpen("p")
|
||||
hb.writeElementOpen("a", "href", rd.Blog.getRelativePath("/editor/drafts"))
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "drafts"))
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("p")
|
||||
postsListLink("/editor/drafts", "drafts")
|
||||
// Private
|
||||
hb.writeElementOpen("p")
|
||||
hb.writeElementOpen("a", "href", rd.Blog.getRelativePath("/editor/private"))
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "privateposts"))
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("p")
|
||||
postsListLink("/editor/private", "privateposts")
|
||||
// Unlisted
|
||||
hb.writeElementOpen("p")
|
||||
hb.writeElementOpen("a", "href", rd.Blog.getRelativePath("/editor/unlisted"))
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "unlistedposts"))
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("p")
|
||||
postsListLink("/editor/unlisted", "unlistedposts")
|
||||
// Scheduled
|
||||
hb.writeElementOpen("p")
|
||||
hb.writeElementOpen("a", "href", rd.Blog.getRelativePath("/editor/scheduled"))
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "scheduledposts"))
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("p")
|
||||
postsListLink("/editor/scheduled", "scheduledposts")
|
||||
// Deleted
|
||||
hb.writeElementOpen("p")
|
||||
hb.writeElementOpen("a", "href", rd.Blog.getRelativePath("/editor/deleted"))
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "deletedposts"))
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("p")
|
||||
postsListLink("/editor/deleted", "deletedposts")
|
||||
|
||||
// Upload
|
||||
hb.writeElementOpen("h2")
|
||||
|
@ -1524,15 +1509,10 @@ func (a *goBlog) renderEditor(hb *htmlBuilder, rd *renderData) {
|
|||
hb.writeElementClose("main")
|
||||
|
||||
// Scripts
|
||||
// Editor preview
|
||||
hb.writeElementOpen("script", "src", a.assetFileName("js/mdpreview.js"), "defer", "")
|
||||
hb.writeElementClose("script")
|
||||
// Geohelper
|
||||
hb.writeElementOpen("script", "src", a.assetFileName("js/geohelper.js"), "defer", "")
|
||||
hb.writeElementClose("script")
|
||||
// Formcache
|
||||
hb.writeElementOpen("script", "src", a.assetFileName("js/formcache.js"), "defer", "")
|
||||
hb.writeElementClose("script")
|
||||
for _, script := range []string{"js/mdpreview.js", "js/geohelper.js", "js/formcache.js"} {
|
||||
hb.writeElementOpen("script", "src", a.assetFileName(script), "defer", "")
|
||||
hb.writeElementClose("script")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type summaryTyp string
|
||||
|
||||
|
@ -46,7 +49,7 @@ func (a *goBlog) renderSummary(hb *htmlBuilder, bc *configBlog, p *post, typ sum
|
|||
if typ != photoSummary && a.showFull(p) {
|
||||
// Show full content
|
||||
hb.writeElementOpen("div", "class", "e-content")
|
||||
hb.write(string(a.postHtml(p, false)))
|
||||
a.postHtmlToWriter(hb, p, false)
|
||||
hb.writeElementClose("div")
|
||||
} else {
|
||||
// Show summary
|
||||
|
@ -114,12 +117,12 @@ func (a *goBlog) renderPostMeta(hb *htmlBuilder, p *post, b *configBlog, typ str
|
|||
hb.writeElementOpen("div", "class", "p")
|
||||
}
|
||||
// Published time
|
||||
if published := p.Published; published != "" {
|
||||
if published := toLocalTime(p.Published); !published.IsZero() {
|
||||
hb.writeElementOpen("div")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "publishedon"))
|
||||
hb.write(" ")
|
||||
hb.writeElementOpen("time", "class", "dt-published", "datetime", dateFormat(published, "2006-01-02T15:04:05Z07:00"))
|
||||
hb.writeEscaped(isoDateFormat(published))
|
||||
hb.writeElementOpen("time", "class", "dt-published", "datetime", published.Format(time.RFC3339))
|
||||
hb.writeEscaped(published.Format(isoDateFormat))
|
||||
hb.writeElementClose("time")
|
||||
// Section
|
||||
if p.Section != "" {
|
||||
|
@ -133,12 +136,12 @@ func (a *goBlog) renderPostMeta(hb *htmlBuilder, p *post, b *configBlog, typ str
|
|||
hb.writeElementClose("div")
|
||||
}
|
||||
// Updated time
|
||||
if updated := p.Updated; updated != "" {
|
||||
if updated := toLocalTime(p.Updated); !updated.IsZero() {
|
||||
hb.writeElementOpen("div")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "updatedon"))
|
||||
hb.write(" ")
|
||||
hb.writeElementOpen("time", "class", "dt-updated", "datetime", dateFormat(updated, "2006-01-02T15:04:05Z07:00"))
|
||||
hb.writeEscaped(isoDateFormat(updated))
|
||||
hb.writeElementOpen("time", "class", "dt-updated", "datetime", updated.Format(time.RFC3339))
|
||||
hb.writeEscaped(updated.Format(isoDateFormat))
|
||||
hb.writeElementClose("time")
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
|
@ -335,11 +338,11 @@ func (a *goBlog) renderPostHeadMeta(hb *htmlBuilder, p *post, canonical string)
|
|||
hb.writeElementOpen("meta", "property", "og:description", "content", summary)
|
||||
hb.writeElementOpen("meta", "property", "twitter:description", "content", summary)
|
||||
}
|
||||
if p.Published != "" {
|
||||
hb.writeElementOpen("meta", "itemprop", "datePublished", "content", dateFormat(p.Published, "2006-01-02T15:04:05-07:00"))
|
||||
if published := toLocalTime(p.Published); !published.IsZero() {
|
||||
hb.writeElementOpen("meta", "itemprop", "datePublished", "content", published.Format(time.RFC3339))
|
||||
}
|
||||
if p.Updated != "" {
|
||||
hb.writeElementOpen("meta", "itemprop", "dateModified", "content", dateFormat(p.Updated, "2006-01-02T15:04:05-07:00"))
|
||||
if updated := toLocalTime(p.Updated); !updated.IsZero() {
|
||||
hb.writeElementOpen("meta", "itemprop", "dateModified", "content", updated.Format(time.RFC3339))
|
||||
}
|
||||
for _, img := range a.photoLinks(p) {
|
||||
hb.writeElementOpen("meta", "itemprop", "image", "content", img)
|
||||
|
|
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
textTemplate "text/template"
|
||||
)
|
||||
|
@ -33,10 +32,6 @@ func (h *htmlBuilder) write(s string) {
|
|||
_, _ = h.WriteString(s)
|
||||
}
|
||||
|
||||
func (h *htmlBuilder) writeHtml(s template.HTML) {
|
||||
h.write(string(s))
|
||||
}
|
||||
|
||||
func (h *htmlBuilder) writeEscaped(s string) {
|
||||
textTemplate.HTMLEscape(h, []byte(s))
|
||||
}
|
||||
|
|
36
utils.go
36
utils.go
|
@ -14,6 +14,7 @@ import (
|
|||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
|
@ -164,25 +165,18 @@ func toUTC(s string) (string, error) {
|
|||
return d.UTC().Format(time.RFC3339), nil
|
||||
}
|
||||
|
||||
func dateFormat(date string, format string) string {
|
||||
func toLocalTime(date string) time.Time {
|
||||
if date == "" {
|
||||
return time.Time{}
|
||||
}
|
||||
d, err := dateparse.ParseLocal(date)
|
||||
if err != nil {
|
||||
return ""
|
||||
return time.Time{}
|
||||
}
|
||||
return d.Local().Format(format)
|
||||
return d.Local()
|
||||
}
|
||||
|
||||
func isoDateFormat(date string) string {
|
||||
return dateFormat(date, "2006-01-02")
|
||||
}
|
||||
|
||||
func unixToLocalDateString(unix int64) string {
|
||||
return time.Unix(unix, 0).Local().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func localNowString() string {
|
||||
return time.Now().Local().Format(time.RFC3339)
|
||||
}
|
||||
const isoDateFormat = "2006-01-02"
|
||||
|
||||
func utcNowString() string {
|
||||
return time.Now().UTC().Format(time.RFC3339)
|
||||
|
@ -366,7 +360,17 @@ func (valueOnlyContext) Err() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var timeDiffLocaleMap = map[string]tdl.Locale{}
|
||||
var timeDiffLocaleMutex sync.RWMutex
|
||||
|
||||
func matchTimeDiffLocale(lang string) tdl.Locale {
|
||||
timeDiffLocaleMutex.RLock()
|
||||
if locale, ok := timeDiffLocaleMap[lang]; ok {
|
||||
return locale
|
||||
}
|
||||
timeDiffLocaleMutex.RUnlock()
|
||||
timeDiffLocaleMutex.Lock()
|
||||
defer timeDiffLocaleMutex.Unlock()
|
||||
supportedLangs := []string{"en", "de", "es", "hi", "pt", "ru", "zh-CN"}
|
||||
supportedTags := []language.Tag{}
|
||||
for _, lang := range supportedLangs {
|
||||
|
@ -374,5 +378,7 @@ func matchTimeDiffLocale(lang string) tdl.Locale {
|
|||
}
|
||||
matcher := language.NewMatcher(supportedTags)
|
||||
_, idx, _ := matcher.Match(language.Make(lang))
|
||||
return tdl.Locale(supportedLangs[idx])
|
||||
locale := tdl.Locale(supportedLangs[idx])
|
||||
timeDiffLocaleMap[lang] = locale
|
||||
return locale
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/carlmjohnson/requests"
|
||||
"github.com/thoas/go-funk"
|
||||
"github.com/tomnomnom/linkheader"
|
||||
"go.goblog.app/app/pkgs/bufferpool"
|
||||
)
|
||||
|
||||
const postParamWebmention = "webmention"
|
||||
|
@ -32,7 +33,10 @@ func (a *goBlog) sendWebmentions(p *post) error {
|
|||
return nil
|
||||
}
|
||||
links := []string{}
|
||||
contentLinks, err := allLinksFromHTML(strings.NewReader(string(a.postHtml(p, false))), a.fullPostURL(p))
|
||||
contentBuf := bufferpool.Get()
|
||||
a.postHtmlToWriter(contentBuf, p, false)
|
||||
contentLinks, err := allLinksFromHTML(contentBuf, a.fullPostURL(p))
|
||||
bufferpool.Put(contentBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue