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