mirror of https://github.com/jlelse/GoBlog
Render all non-login using new method to use less allocations and memory
This commit is contained in:
parent
049fee72bd
commit
aa319b70fb
|
@ -62,12 +62,12 @@ func (a *goBlog) authMiddleware(next http.Handler) http.Handler {
|
|||
_ = r.ParseForm()
|
||||
b = []byte(r.PostForm.Encode())
|
||||
}
|
||||
a.render(w, r, templateLogin, &renderData{
|
||||
Data: map[string]interface{}{
|
||||
"loginmethod": r.Method,
|
||||
"loginheaders": base64.StdEncoding.EncodeToString(h),
|
||||
"loginbody": base64.StdEncoding.EncodeToString(b),
|
||||
"totp": a.cfg.User.TOTP != "",
|
||||
a.renderNew(w, r, a.renderLogin, &renderData{
|
||||
Data: &loginRenderData{
|
||||
loginMethod: r.Method,
|
||||
loginHeaders: base64.StdEncoding.EncodeToString(h),
|
||||
loginBody: base64.StdEncoding.EncodeToString(b),
|
||||
totp: a.cfg.User.TOTP != "",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
|
12
blogroll.go
12
blogroll.go
|
@ -29,13 +29,13 @@ func (a *goBlog) serveBlogroll(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
c := bc.Blogroll
|
||||
can := bc.getRelativePath(defaultIfEmpty(c.Path, defaultBlogrollPath))
|
||||
a.render(w, r, templateBlogroll, &renderData{
|
||||
a.renderNew(w, r, a.renderBlogroll, &renderData{
|
||||
Canonical: a.getFullAddress(can),
|
||||
Data: map[string]interface{}{
|
||||
"Title": c.Title,
|
||||
"Description": c.Description,
|
||||
"Outlines": outlines,
|
||||
"Download": can + ".opml",
|
||||
Data: &blogrollRenderData{
|
||||
title: c.Title,
|
||||
description: c.Description,
|
||||
outlines: outlines.([]*opml.Outline),
|
||||
download: can + ".opml",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -25,10 +25,10 @@ func (a *goBlog) initBlogStats() {
|
|||
func (a *goBlog) serveBlogStats(w http.ResponseWriter, r *http.Request) {
|
||||
_, bc := a.getBlog(r)
|
||||
canonical := bc.getRelativePath(defaultIfEmpty(bc.BlogStats.Path, defaultBlogStatsPath))
|
||||
a.render(w, r, templateBlogStats, &renderData{
|
||||
a.renderNew(w, r, a.renderBlogStats, &renderData{
|
||||
Canonical: a.getFullAddress(canonical),
|
||||
Data: map[string]interface{}{
|
||||
"TableUrl": canonical + blogStatsTablePath,
|
||||
Data: &blogStatsRenderData{
|
||||
tableUrl: canonical + blogStatsTablePath,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ func (a *goBlog) serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
// Render
|
||||
a.render(w, r, templateBlogStatsTable, &renderData{
|
||||
a.renderNew(w, r, a.renderBlogStatsTable, &renderData{
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
|
12
captcha.go
12
captcha.go
|
@ -65,12 +65,12 @@ func (a *goBlog) captchaMiddleware(next http.Handler) http.Handler {
|
|||
// Render captcha
|
||||
_ = ses.Save(r, w)
|
||||
w.Header().Set("Cache-Control", "no-store,max-age=0")
|
||||
a.renderWithStatusCode(w, r, http.StatusUnauthorized, templateCaptcha, &renderData{
|
||||
Data: map[string]string{
|
||||
"captchamethod": r.Method,
|
||||
"captchaheaders": base64.StdEncoding.EncodeToString(h),
|
||||
"captchabody": base64.StdEncoding.EncodeToString(b),
|
||||
"captchaid": captchaId,
|
||||
a.renderNewWithStatusCode(w, r, http.StatusUnauthorized, a.renderCaptcha, &renderData{
|
||||
Data: &captchaRenderData{
|
||||
captchaMethod: r.Method,
|
||||
captchaHeaders: base64.StdEncoding.EncodeToString(h),
|
||||
captchaBody: base64.StdEncoding.EncodeToString(b),
|
||||
captchaId: captchaId,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
|
|
@ -41,7 +41,7 @@ func (a *goBlog) serveComment(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
_, bc := a.getBlog(r)
|
||||
a.render(w, r, templateComment, &renderData{
|
||||
a.renderNew(w, r, a.renderComment, &renderData{
|
||||
Canonical: a.getFullAddress(bc.getRelativePath(path.Join(commentPath, strconv.Itoa(id)))),
|
||||
Data: comment,
|
||||
})
|
||||
|
|
16
contact.go
16
contact.go
|
@ -17,11 +17,11 @@ const defaultContactPath = "/contact"
|
|||
func (a *goBlog) serveContactForm(w http.ResponseWriter, r *http.Request) {
|
||||
_, bc := a.getBlog(r)
|
||||
cc := bc.Contact
|
||||
a.render(w, r, templateContact, &renderData{
|
||||
Data: map[string]interface{}{
|
||||
"title": cc.Title,
|
||||
"description": cc.Description,
|
||||
"privacy": cc.PrivacyPolicy,
|
||||
a.renderNew(w, r, a.renderContact, &renderData{
|
||||
Data: &contactRenderData{
|
||||
title: cc.Title,
|
||||
description: cc.Description,
|
||||
privacy: cc.PrivacyPolicy,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -63,9 +63,9 @@ func (a *goBlog) sendContactSubmission(w http.ResponseWriter, r *http.Request) {
|
|||
// Send notification
|
||||
a.sendNotification(message.String())
|
||||
// Give feedback
|
||||
a.render(w, r, templateContact, &renderData{
|
||||
Data: map[string]interface{}{
|
||||
"sent": true,
|
||||
a.renderNew(w, r, a.renderContact, &renderData{
|
||||
Data: &contactRenderData{
|
||||
sent: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ func (a *goBlog) createMarkdownPreview(blog string, markdown []byte) (rendered [
|
|||
// Render post
|
||||
var hb htmlBuilder
|
||||
a.renderEditorPreview(&hb, a.cfg.Blogs[blog], p)
|
||||
rendered = []byte(hb.String())
|
||||
rendered = hb.Bytes()
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -8,11 +8,6 @@ import (
|
|||
"go.goblog.app/app/pkgs/contenttype"
|
||||
)
|
||||
|
||||
type errorData struct {
|
||||
Title string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (a *goBlog) serve404(w http.ResponseWriter, r *http.Request) {
|
||||
a.serveError(w, r, fmt.Sprintf("%s was not found", r.RequestURI), http.StatusNotFound)
|
||||
}
|
||||
|
@ -40,8 +35,8 @@ func (a *goBlog) serveError(w http.ResponseWriter, r *http.Request, message stri
|
|||
http.Error(w, message, status)
|
||||
return
|
||||
}
|
||||
a.renderWithStatusCode(w, r, status, templateError, &renderData{
|
||||
Data: &errorData{
|
||||
a.renderNewWithStatusCode(w, r, status, a.renderError, &renderData{
|
||||
Data: &errorRenderData{
|
||||
Title: fmt.Sprintf("%d %s", status, http.StatusText(status)),
|
||||
Message: message,
|
||||
},
|
||||
|
|
20
geoMap.go
20
geoMap.go
|
@ -25,10 +25,10 @@ func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
if len(allPostsWithLocation) == 0 {
|
||||
a.render(w, r, templateGeoMap, &renderData{
|
||||
a.renderNew(w, r, a.renderGeoMap, &renderData{
|
||||
Canonical: canonical,
|
||||
Data: map[string]interface{}{
|
||||
"nolocations": true,
|
||||
Data: &geoMapRenderData{
|
||||
noLocations: true,
|
||||
},
|
||||
})
|
||||
return
|
||||
|
@ -85,14 +85,14 @@ func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
|
|||
tracksJson = string(tracksJsonBytes)
|
||||
}
|
||||
|
||||
a.render(w, r, templateGeoMap, &renderData{
|
||||
a.renderNew(w, r, a.renderGeoMap, &renderData{
|
||||
Canonical: canonical,
|
||||
Data: map[string]interface{}{
|
||||
"locations": locationsJson,
|
||||
"tracks": tracksJson,
|
||||
"attribution": a.getMapAttribution(),
|
||||
"minzoom": a.getMinZoom(),
|
||||
"maxzoom": a.getMaxZoom(),
|
||||
Data: &geoMapRenderData{
|
||||
locations: locationsJson,
|
||||
tracks: tracksJson,
|
||||
attribution: a.getMapAttribution(),
|
||||
minZoom: a.getMinZoom(),
|
||||
maxZoom: a.getMaxZoom(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
2
go.mod
2
go.mod
|
@ -46,7 +46,7 @@ require (
|
|||
github.com/spf13/cast v1.4.1
|
||||
github.com/spf13/viper v1.10.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tdewolff/minify/v2 v2.9.28
|
||||
github.com/tdewolff/minify/v2 v2.9.29
|
||||
github.com/thoas/go-funk v0.9.1
|
||||
github.com/tkrajina/gpxgo v1.2.0
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
||||
|
|
4
go.sum
4
go.sum
|
@ -415,8 +415,8 @@ github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQ
|
|||
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
|
||||
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
|
||||
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8=
|
||||
github.com/tdewolff/minify/v2 v2.9.28 h1:70GT62rRyLcH4jbkTFGcy6rktHKGiaSNZb/h8pnuQYw=
|
||||
github.com/tdewolff/minify/v2 v2.9.28/go.mod h1:6XAjcHM46pFcRE0eztigFPm0Q+Cxsw8YhEWT+rDkcZM=
|
||||
github.com/tdewolff/minify/v2 v2.9.29 h1:QMVJaCJzWL0mXS33cX792YD074xz4lOhkyBS8hAzYAY=
|
||||
github.com/tdewolff/minify/v2 v2.9.29/go.mod h1:6XAjcHM46pFcRE0eztigFPm0Q+Cxsw8YhEWT+rDkcZM=
|
||||
github.com/tdewolff/parse/v2 v2.5.27 h1:PL3LzzXaOpmdrknnOlIeO2muIBHAwiKp6TxN1RbU5gI=
|
||||
github.com/tdewolff/parse/v2 v2.5.27/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
|
||||
|
|
|
@ -254,7 +254,7 @@ details summary {
|
|||
height: 400px;
|
||||
}
|
||||
|
||||
#post-actions {
|
||||
#post-actions, #posteditactions {
|
||||
@extend .p;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -39,7 +39,7 @@ func (m *Minifier) Get() *minify.M {
|
|||
|
||||
func (m *Minifier) Write(w io.Writer, mediatype string, b []byte) (int, error) {
|
||||
mw := m.Get().Writer(mediatype, w)
|
||||
defer func() { _ = mw.Close() }()
|
||||
defer mw.Close()
|
||||
return mw.Write(b)
|
||||
}
|
||||
|
||||
|
|
49
posts.go
49
posts.go
|
@ -73,16 +73,16 @@ func (a *goBlog) servePost(w http.ResponseWriter, r *http.Request) {
|
|||
if canonical == "" {
|
||||
canonical = a.fullPostURL(p)
|
||||
}
|
||||
template := templatePost
|
||||
renderMethod := a.renderPost
|
||||
if p.Path == a.getRelativePath(p.Blog, "") {
|
||||
template = templateStaticHome
|
||||
renderMethod = a.renderStaticHome
|
||||
}
|
||||
w.Header().Add("Link", fmt.Sprintf("<%s>; rel=shortlink", a.shortPostURL(p)))
|
||||
status := http.StatusOK
|
||||
if strings.HasSuffix(string(p.Status), statusDeletedSuffix) {
|
||||
status = http.StatusGone
|
||||
}
|
||||
a.renderWithStatusCode(w, r, status, template, &renderData{
|
||||
a.renderNewWithStatusCode(w, r, status, renderMethod, &renderData{
|
||||
BlogString: p.Blog,
|
||||
Canonical: canonical,
|
||||
Data: p,
|
||||
|
@ -201,23 +201,22 @@ func (a *goBlog) serveDate(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
var title, dPath strings.Builder
|
||||
if year != 0 {
|
||||
ys := fmt.Sprintf("%0004d", year)
|
||||
title.WriteString(ys)
|
||||
dPath.WriteString(ys)
|
||||
_, _ = fmt.Fprintf(&title, "%0004d", year)
|
||||
_, _ = fmt.Fprintf(&dPath, "%0004d", year)
|
||||
} else {
|
||||
title.WriteString("XXXX")
|
||||
dPath.WriteString("x")
|
||||
_, _ = title.WriteString("XXXX")
|
||||
_, _ = dPath.WriteString("x")
|
||||
}
|
||||
if month != 0 {
|
||||
title.WriteString(fmt.Sprintf("-%02d", month))
|
||||
dPath.WriteString(fmt.Sprintf("/%02d", month))
|
||||
_, _ = fmt.Fprintf(&title, "-%02d", month)
|
||||
_, _ = fmt.Fprintf(&dPath, "/%02d", month)
|
||||
} else if day != 0 {
|
||||
title.WriteString("-XX")
|
||||
dPath.WriteString("/x")
|
||||
_, _ = title.WriteString("-XX")
|
||||
_, _ = dPath.WriteString("/x")
|
||||
}
|
||||
if day != 0 {
|
||||
title.WriteString(fmt.Sprintf("-%02d", day))
|
||||
dPath.WriteString(fmt.Sprintf("/%02d", day))
|
||||
_, _ = fmt.Fprintf(&title, "-%02d", day)
|
||||
_, _ = fmt.Fprintf(&dPath, "/%02d", day)
|
||||
}
|
||||
_, bc := a.getBlog(r)
|
||||
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
||||
|
@ -339,18 +338,18 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
|
|||
if summaryTemplate == "" {
|
||||
summaryTemplate = defaultSummary
|
||||
}
|
||||
a.render(w, r, templateIndex, &renderData{
|
||||
a.renderNew(w, r, a.renderIndex, &renderData{
|
||||
Canonical: a.getFullAddress(path),
|
||||
Data: map[string]interface{}{
|
||||
"Title": title,
|
||||
"Description": description,
|
||||
"Posts": posts,
|
||||
"HasPrev": hasPrev,
|
||||
"HasNext": hasNext,
|
||||
"First": path,
|
||||
"Prev": prevPath,
|
||||
"Next": nextPath,
|
||||
"SummaryTemplate": summaryTemplate,
|
||||
Data: &indexRenderData{
|
||||
title: title,
|
||||
description: description,
|
||||
posts: posts,
|
||||
hasPrev: hasPrev,
|
||||
hasNext: hasNext,
|
||||
first: path,
|
||||
prev: prevPath,
|
||||
next: nextPath,
|
||||
summaryTemplate: summaryTemplate,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
77
render.go
77
render.go
|
@ -17,25 +17,11 @@ const (
|
|||
templatesExt = ".gohtml"
|
||||
|
||||
templateBase = "base"
|
||||
templatePost = "post"
|
||||
templateError = "error"
|
||||
templateIndex = "index"
|
||||
templateTaxonomy = "taxonomy"
|
||||
templateSearch = "search"
|
||||
templateEditor = "editor"
|
||||
templateEditorFiles = "editorfiles"
|
||||
templateLogin = "login"
|
||||
templateStaticHome = "statichome"
|
||||
templateBlogStats = "blogstats"
|
||||
templateBlogStatsTable = "blogstatstable"
|
||||
templateComment = "comment"
|
||||
templateCaptcha = "captcha"
|
||||
templateCommentsAdmin = "commentsadmin"
|
||||
templateNotificationsAdmin = "notificationsadmin"
|
||||
templateWebmentionAdmin = "webmentionadmin"
|
||||
templateBlogroll = "blogroll"
|
||||
templateGeoMap = "geomap"
|
||||
templateContact = "contact"
|
||||
templateIndieAuth = "indieauth"
|
||||
)
|
||||
|
||||
|
@ -45,49 +31,10 @@ func (a *goBlog) initRendering() error {
|
|||
"md": a.safeRenderMarkdownAsHTML,
|
||||
"mdtitle": a.renderMdTitle,
|
||||
"html": wrapStringAsHTML,
|
||||
// Post specific
|
||||
"content": a.postHtml,
|
||||
"shorturl": a.shortPostURL,
|
||||
"gettrack": a.getTrack,
|
||||
// Code based rendering
|
||||
"posttax": func(p *post, b *configBlog) template.HTML {
|
||||
"tor": func(rd *renderData) template.HTML {
|
||||
var hb htmlBuilder
|
||||
a.renderPostTax(&hb, p, b)
|
||||
return hb.html()
|
||||
},
|
||||
"postmeta": func(p *post, b *configBlog, typ string) template.HTML {
|
||||
var hb htmlBuilder
|
||||
a.renderPostMeta(&hb, p, b, typ)
|
||||
return hb.html()
|
||||
},
|
||||
"oldcontentwarning": func(p *post, b *configBlog) template.HTML {
|
||||
var hb htmlBuilder
|
||||
a.renderOldContentWarning(&hb, p, b)
|
||||
return hb.html()
|
||||
},
|
||||
"interactions": func(b *configBlog, c string) template.HTML {
|
||||
var hb htmlBuilder
|
||||
a.renderInteractions(&hb, b, c)
|
||||
return hb.html()
|
||||
},
|
||||
"author": func() template.HTML {
|
||||
var hb htmlBuilder
|
||||
a.renderAuthor(&hb)
|
||||
return hb.html()
|
||||
},
|
||||
"postheadmeta": func(p *post, c string) template.HTML {
|
||||
var hb htmlBuilder
|
||||
a.renderPostHeadMeta(&hb, p, c)
|
||||
return hb.html()
|
||||
},
|
||||
"tor": func(b *configBlog, torUsed bool, torAddress string) template.HTML {
|
||||
var hb htmlBuilder
|
||||
a.renderTorNotice(&hb, b, torUsed, torAddress)
|
||||
return hb.html()
|
||||
},
|
||||
"summary": func(bc *configBlog, p *post, typ summaryTyp) template.HTML {
|
||||
var hb htmlBuilder
|
||||
a.renderSummary(&hb, bc, p, typ)
|
||||
a.renderTorNotice(&hb, rd)
|
||||
return hb.html()
|
||||
},
|
||||
// Others
|
||||
|
@ -97,13 +44,11 @@ func (a *goBlog) initRendering() error {
|
|||
"now": localNowString,
|
||||
"asset": a.assetFileName,
|
||||
"string": a.ts.GetTemplateStringVariantFunc(),
|
||||
"urlize": urlize,
|
||||
"absolute": a.getFullAddress,
|
||||
"opensearch": openSearchUrl,
|
||||
"mbytes": mBytesString,
|
||||
"editortemplate": a.editorPostTemplate,
|
||||
"editorpostdesc": a.editorPostDesc,
|
||||
"ttsenabled": a.ttsEnabled,
|
||||
}
|
||||
baseTemplate, err := template.New("base").Funcs(templateFunctions).ParseFiles(path.Join(templatesDir, templateBase+templatesExt))
|
||||
if err != nil {
|
||||
|
@ -170,6 +115,24 @@ func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, st
|
|||
}
|
||||
}
|
||||
|
||||
func (a *goBlog) renderNew(w http.ResponseWriter, r *http.Request, f func(*htmlBuilder, *renderData), data *renderData) {
|
||||
a.renderNewWithStatusCode(w, r, http.StatusOK, f, data)
|
||||
}
|
||||
|
||||
func (a *goBlog) renderNewWithStatusCode(w http.ResponseWriter, r *http.Request, statusCode int, f func(*htmlBuilder, *renderData), data *renderData) {
|
||||
// Check render data
|
||||
a.checkRenderData(r, data)
|
||||
// Set content type
|
||||
w.Header().Set(contentType, contenttype.HTMLUTF8)
|
||||
// Write status code
|
||||
w.WriteHeader(statusCode)
|
||||
// Render
|
||||
minWriter := a.min.Get().Writer(contenttype.HTML, w)
|
||||
defer minWriter.Close()
|
||||
hb := newHtmlBuilder(minWriter)
|
||||
f(hb, data)
|
||||
}
|
||||
|
||||
func (a *goBlog) checkRenderData(r *http.Request, data *renderData) {
|
||||
if data.app == nil {
|
||||
data.app = a
|
||||
|
|
|
@ -26,7 +26,7 @@ func (a *goBlog) serveSearch(w http.ResponseWriter, r *http.Request) {
|
|||
http.Redirect(w, r, path.Join(servePath, searchEncode(q)), http.StatusFound)
|
||||
return
|
||||
}
|
||||
a.render(w, r, templateSearch, &renderData{
|
||||
a.renderNew(w, r, a.renderSearch, &renderData{
|
||||
Canonical: a.getFullAddress(servePath),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,11 +20,11 @@ func (a *goBlog) serveTaxonomy(w http.ResponseWriter, r *http.Request) {
|
|||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
a.render(w, r, templateTaxonomy, &renderData{
|
||||
a.renderNew(w, r, a.renderTaxonomy, &renderData{
|
||||
Canonical: a.getFullAddress(r.URL.Path),
|
||||
Data: map[string]interface{}{
|
||||
"Taxonomy": tax,
|
||||
"ValueGroups": groupStrings(allValues),
|
||||
Data: &taxonomyRenderData{
|
||||
taxonomy: tax,
|
||||
valueGroups: groupStrings(allValues),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -144,7 +144,7 @@ details summary > *:first-child {
|
|||
border-bottom: 1px solid var(--primary, #000);
|
||||
}
|
||||
|
||||
.p, #post-actions, table {
|
||||
.p, #post-actions, #posteditactions, table {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
|
@ -214,12 +214,12 @@ details summary > *:first-child {
|
|||
height: 400px;
|
||||
}
|
||||
|
||||
#post-actions {
|
||||
#post-actions, #posteditactions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
#post-actions * {
|
||||
#post-actions *, #posteditactions * {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<nav>{{ range $i, $item := .Items }}{{ if ne $i 0 }} • {{ end }}<a href="{{ $item.Link }}">{{ mdtitle $item.Title }}</a>{{ end }}</nav>
|
||||
{{ end }}
|
||||
<p translate="no">© {{ dateformat now "2006" }} {{ with .User.Name }}{{ . }}{{ else }}{{ mdtitle .Blog.Title }}{{ end }}</p>
|
||||
{{ tor .Blog .TorUsed .TorAddress }}
|
||||
{{ tor . }}
|
||||
</footer>
|
||||
{{ if .EasterEgg }}<script defer src="{{ asset "js/easteregg.js" }}"></script>{{ end }}
|
||||
</html>
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ mdtitle .Data.Title }} - {{ mdtitle .Blog.Title }}</title>
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main>
|
||||
{{ with .Data.Title }}<h1>{{ mdtitle . }}</h1>{{ end }}
|
||||
{{ with .Data.Description }}{{ md . }}{{ end }}
|
||||
<p><a href="{{ .Blog.RelativePath .Data.Download }}" class="button" download>{{ string .Blog.Lang "download" }}</a></p>
|
||||
{{ $lang := .Blog.Lang }}
|
||||
{{ range .Data.Outlines }}
|
||||
{{ $title := .Title }}
|
||||
{{ if not $title }}{{ $title = .Text }}{{ end }}
|
||||
<h2 id="{{ urlize $title }}">{{ $title }} ({{ len .Outlines }})</h2>
|
||||
<ul>
|
||||
{{ range .Outlines }}
|
||||
{{ $ct := .Title }}
|
||||
{{ if not $ct }}{{ $ct = .Text }}{{ end }}
|
||||
<li><a href="{{ .HTMLURL }}" target="_blank">{{ $ct }}</a> (<a href="{{ .XMLURL }}" target="_blank">{{ string $lang "feed" }}</a>)</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
</main>
|
||||
{{ if .CommentsEnabled }}{{ interactions .Blog .Canonical }}{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "blogroll" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -1,17 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ with .Blog.BlogStats.Title }}{{ mdtitle . }} - {{ end }}{{ mdtitle .Blog.Title }}</title>
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main>
|
||||
{{ with .Blog.BlogStats.Title }}<h1>{{ mdtitle . }}</h1>{{ end }}
|
||||
{{ with .Blog.BlogStats.Description }}{{ md . }}{{ end }}
|
||||
<p id="loading" data-table="{{.Data.TableUrl}}">{{ string .Blog.Lang "loading" }}</p>
|
||||
<script defer src="{{ asset "js/blogstats.js" }}"></script>
|
||||
</main>
|
||||
{{ if .CommentsEnabled }}{{ interactions .Blog .Canonical }}{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "blogstats" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -1,48 +0,0 @@
|
|||
{{ define "blogstatstable" }}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="tal">{{ string .Blog.Lang "year" }}</th>
|
||||
<th class="tar">{{ string .Blog.Lang "posts" }}</th>
|
||||
<th class="tar">~{{ string .Blog.Lang "chars" }}</th>
|
||||
<th class="tar">~{{ string .Blog.Lang "words" }}</th>
|
||||
<th class="tar">~{{ string .Blog.Lang "wordsperpost" }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ $months := .Data.Months }}
|
||||
{{ range $year := .Data.Years }}
|
||||
<tr class="statsyear" data-year="{{ $year.Name }}">
|
||||
<td class="tal">{{ $year.Name }}</td>
|
||||
<td class="tar">{{ $year.Posts }}</td>
|
||||
<td class="tar">{{ $year.Chars }}</td>
|
||||
<td class="tar">{{ $year.Words }}</td>
|
||||
<td class="tar">{{ $year.WordsPerPost }}</td>
|
||||
</tr>
|
||||
{{ range $month := (index $months $year.Name) }}
|
||||
<tr class="statsmonth hide" data-year="{{ $year.Name }}">
|
||||
<td class="tal">{{ $year.Name }}-{{ $month.Name }}</td>
|
||||
<td class="tar">{{ $month.Posts }}</td>
|
||||
<td class="tar">{{ $month.Chars }}</td>
|
||||
<td class="tar">{{ $month.Words }}</td>
|
||||
<td class="tar">{{ $month.WordsPerPost }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<tr>
|
||||
<td class="tal">{{ string .Blog.Lang "withoutdate" }}</td>
|
||||
<td class="tar">{{ .Data.NoDate.Posts }}</td>
|
||||
<td class="tar">{{ .Data.NoDate.Chars }}</td>
|
||||
<td class="tar">{{ .Data.NoDate.Words }}</td>
|
||||
<td class="tar">{{ .Data.NoDate.WordsPerPost }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tal"><b>{{ string .Blog.Lang "total" }}</b></td>
|
||||
<td class="tar">{{ .Data.Total.Posts }}</td>
|
||||
<td class="tar">{{ .Data.Total.Chars }}</td>
|
||||
<td class="tar">{{ .Data.Total.Words }}</td>
|
||||
<td class="tar">{{ .Data.Total.WordsPerPost }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{{ end }}
|
|
@ -1,21 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ mdtitle .Blog.Title }}</title>
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main>
|
||||
<p><img src="/captcha/{{ .Data.captchaid }}.png" class="captchaimg"></p>
|
||||
<form class="fw p" method="post">
|
||||
<input type="hidden" name="captchaaction" value="captcha">
|
||||
<input type="hidden" name="captchamethod" value="{{ .Data.captchamethod }}">
|
||||
<input type="hidden" name="captchaheaders" value="{{ .Data.captchaheaders }}">
|
||||
<input type="hidden" name="captchabody" value="{{ .Data.captchabody }}">
|
||||
<input type="text" name="digits" placeholder="{{ string .Blog.Lang "captchainstructions" }}" required>
|
||||
<input type="submit" value="{{ string .Blog.Lang "submit" }}">
|
||||
</form>
|
||||
</main>
|
||||
{{ end }}
|
||||
|
||||
{{ define "captcha" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -1,21 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ string .Blog.Lang "acommentby" }} {{ .Data.Name }}</title>
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main class=h-entry>
|
||||
<p><a class="u-in-reply-to" href="{{ absolute .Data.Target }}">{{ absolute .Data.Target }}</a></p>
|
||||
<div class="p-author h-card p">
|
||||
{{ string .Blog.Lang "acommentby" }}
|
||||
{{ if .Data.Website }}<a href="{{ .Data.Website }}" class="p-name u-url" target="_blank" rel="nofollow noopener noreferrer ugc">{{ .Data.Name }}</a>{{ else }}<span class="p-name">{{ .Data.Name }}</span>{{ end }}:
|
||||
</div>
|
||||
<p class="e-content">
|
||||
{{ html .Data.Comment }}
|
||||
</p>
|
||||
</main>
|
||||
{{ if .CommentsEnabled }}{{ interactions .Blog .Canonical }}{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "comment" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -1,30 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ with .Data.title }}{{ mdtitle . }} - {{ end }}{{ mdtitle .Blog.Title }}</title>
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main>
|
||||
{{ if .Data.sent }}
|
||||
<p>{{ string .Blog.Lang "messagesent" }}</p>
|
||||
{{ else }}
|
||||
{{ with .Data.title }}<h1>{{ mdtitle . }}</h1>{{ end }}
|
||||
{{ with .Data.description }}{{ md . }}{{ end }}
|
||||
<form class="fw p" method="post">
|
||||
<input type="text" name="name" placeholder="{{ string .Blog.Lang "nameopt" }}">
|
||||
<input type="url" name="website" placeholder="{{ string .Blog.Lang "websiteopt" }}">
|
||||
<input type="email" name="email" placeholder="{{ string .Blog.Lang "emailopt" }}">
|
||||
<textarea name="message" required placeholder="{{ string .Blog.Lang "message" }}"></textarea>
|
||||
{{ if .Data.privacy }}
|
||||
{{ md .Data.privacy }}
|
||||
<input type="submit" value="{{ string .Blog.Lang "contactagreesend" }}">
|
||||
{{ else }}
|
||||
<input type="submit" value="{{ string .Blog.Lang "contactsend" }}">
|
||||
{{ end }}
|
||||
</form>
|
||||
{{ end }}
|
||||
</main>
|
||||
{{ end }}
|
||||
|
||||
{{ define "contact" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -1,14 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ with .Data.Title }}{{ . }}{{end}}</title>
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main>
|
||||
{{ with .Data.Title }}<h1>{{ . }}</h1>{{ end }}
|
||||
{{ with .Data.Message }}<p class="monospace">{{ . }}</p>{{ end }}
|
||||
</main>
|
||||
{{ end }}
|
||||
|
||||
{{ define "error" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -1,29 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ mdtitle .Blog.Title }}</title>
|
||||
{{ if not .Data.nolocations }}
|
||||
<link rel="stylesheet" href="/-/leaflet/leaflet.css"/>
|
||||
<script src="/-/leaflet/leaflet.js"></script>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main>
|
||||
{{ if .Data.nolocations }}
|
||||
<p>{{ string .Blog.Lang "nolocations" }}</p>
|
||||
{{ else }}
|
||||
<div class="p" id="map"
|
||||
data-locations="{{ .Data.locations }}"
|
||||
data-tracks="{{ .Data.tracks }}"
|
||||
data-minzoom={{ .Data.minzoom }}
|
||||
data-maxzoom={{ .Data.maxzoom }}
|
||||
data-attribution="{{ .Data.attribution }}"
|
||||
></div>
|
||||
<script defer src="{{ asset "js/geomap.js" }}"></script>
|
||||
{{ end }}
|
||||
</main>
|
||||
{{ if .CommentsEnabled }}{{ interactions .Blog .Canonical }}{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "geomap" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -1,36 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ with .Data.Title }}{{ mdtitle . }} - {{ end }}{{ mdtitle .Blog.Title }}</title>
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS{{ with .Data.Title }} ({{ mdtitle . }}){{ end }}" href="{{ .Data.First }}.rss"/>
|
||||
<link rel="alternate" type="application/atom+xml" title="Atom{{ with .Data.Title }} ({{ mdtitle . }}){{ end }}" href="{{ .Data.First }}.atom"/>
|
||||
<link rel="alternate" type="application/feed+json" title="JSON Feed{{ with .Data.Title }} ({{ mdtitle . }}){{ end }}" href="{{ .Data.First }}.json"/>
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main class="h-feed">
|
||||
{{ with .Data.Title }}<h1 class="p-name">{{ mdtitle . }}</h1>{{ end }}
|
||||
{{ with .Data.Description }}{{ md . }}{{ end }}
|
||||
{{ if (or .Data.Title .Data.Description) }}
|
||||
<hr>
|
||||
{{ end }}
|
||||
{{ if .Data.Posts }}
|
||||
{{ $summaryTemplate := .Data.SummaryTemplate }}
|
||||
{{ $blog := .Blog }}
|
||||
{{ range $i, $post := .Data.Posts }}
|
||||
{{ summary $blog $post $summaryTemplate }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<p>{{ string .Blog.Lang "noposts" }}</p>
|
||||
{{ end }}
|
||||
{{ if .Data.HasPrev }}
|
||||
<p><a href="{{ .Data.Prev }}">{{ string .Blog.Lang "prev" }}</a></p>
|
||||
{{ end }}
|
||||
{{ if .Data.HasNext }}
|
||||
<p><a href="{{ .Data.Next }}">{{ string .Blog.Lang "next" }}</a></p>
|
||||
{{ end }}
|
||||
{{ author }}
|
||||
</main>
|
||||
{{ end }}
|
||||
|
||||
{{ define "index" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -1,26 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ string .Blog.Lang "login" }} - {{ mdtitle .Blog.Title }}</title>
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main>
|
||||
<h1>{{ string .Blog.Lang "login" }}</h1>
|
||||
<form class="fw p" method="post">
|
||||
<input type="hidden" name="loginaction" value="login">
|
||||
<input type="hidden" name="loginmethod" value="{{ .Data.loginmethod }}">
|
||||
<input type="hidden" name="loginheaders" value="{{ .Data.loginheaders }}">
|
||||
<input type="hidden" name="loginbody" value="{{ .Data.loginbody }}">
|
||||
<input type="text" name="username" autocomplete="username" placeholder="{{ string .Blog.Lang "username" }}">
|
||||
<input type="password" name="password" autocomplete="current-password" placeholder="{{ string .Blog.Lang "password" }}">
|
||||
{{ if .Data.totp }}
|
||||
<input type="text" name="token" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" placeholder="{{ string .Blog.Lang "totp" }}">
|
||||
{{ end }}
|
||||
<input type="submit" value="{{ string .Blog.Lang "login" }}">
|
||||
</form>
|
||||
{{ author }}
|
||||
</main>
|
||||
{{ end }}
|
||||
|
||||
{{ define "login" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -1,77 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<link rel="stylesheet" href="{{ asset "css/chroma.css" }}">
|
||||
<title>{{ with .Data.RenderedTitle }}{{ . }} - {{end}}{{ mdtitle .Blog.Title }}</title>
|
||||
{{ postheadmeta .Data .Canonical }}
|
||||
{{ with shorturl .Data }}<link rel="shortlink" href="{{ . }}">{{ end }}
|
||||
{{ if .Data.HasTrack }}
|
||||
<link rel="stylesheet" href="/-/leaflet/leaflet.css"/>
|
||||
<script src="/-/leaflet/leaflet.js"></script>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main class=h-entry>
|
||||
<article>
|
||||
<data class="u-url hide" value="{{ absolute .Data.Path }}"></data>
|
||||
{{ with .Data.RenderedTitle }}<h1 class=p-name>{{ . }}</h1>{{ end }}
|
||||
{{ postmeta .Data .Blog "post" }}
|
||||
<div id="post-actions">
|
||||
<a href="https://www.addtoany.com/share#url={{ shorturl .Data }}{{ with .Data.RenderedTitle }}&title={{ . }}{{ end }}" target="_blank" rel="nofollow noopener noreferrer" class="button">{{ string .Blog.Lang "share" }}</a>
|
||||
<a id="translateBtn" href="https://translate.google.com/translate?u={{ absolute .Data.Path }}" target="_blank" rel="nofollow noopener noreferrer" class="button">{{ string .Blog.Lang "translate" }}</a>
|
||||
<script defer src="{{ asset "js/translate.js" }}"></script>
|
||||
<button id="speakBtn" class="hide" data-speak="{{ string .Blog.Lang "speak" }}" data-stopspeak="{{ string .Blog.Lang "stopspeak" }}"></button>
|
||||
<script defer src="{{ if .Data.TTS }}{{ asset "js/tts.js" }}{{ else }}{{ asset "js/speak.js" }}{{ end }}"></script>
|
||||
</div>
|
||||
{{ if .Data.TTS }}<div class="p hide" id="tts"><audio controls preload=none id="tts-audio"><source src="{{ .Data.TTS }}"/></audio></div>{{ end }}
|
||||
{{ if .Data.Content }}
|
||||
{{ oldcontentwarning .Data .Blog }}
|
||||
<div class=e-content>{{ content .Data false }}</div>
|
||||
{{ end }}
|
||||
{{ if .Data.HasTrack }}
|
||||
{{ $track := (gettrack .Data) }}
|
||||
{{ if $track }}{{ if $track.HasPoints }}
|
||||
{{ $lang := .Blog.Lang }}
|
||||
<p>{{ with $track.Name }}<b>{{ . }}</b> {{ end }}{{ with $track.Kilometers }}🏁 {{ . }} {{ string $lang "kilometers" }} {{ end }}{{ with $track.Hours }}⌛ {{ . }}{{ end }}</p>
|
||||
<div class="p" id="map" data-paths="{{ $track.PathsJSON }}" data-points="{{ $track.PointsJSON }}" data-minzoom={{ $track.MinZoom }} data-maxzoom={{ $track.MaxZoom }} data-attribution="{{ $track.MapAttribution }}"></div>
|
||||
<script defer src="{{ asset "js/geotrack.js" }}"></script>
|
||||
{{ end }}{{ end }}
|
||||
{{ end }}
|
||||
{{ posttax .Data .Blog }}
|
||||
</article>
|
||||
{{ author }}
|
||||
</main>
|
||||
{{ if .LoggedIn }}
|
||||
<div id="posteditactions" class="p">
|
||||
<form class="in" method="post" action="{{ .Blog.RelativePath "/editor" }}#update">
|
||||
<input type="hidden" name="editoraction" value="loadupdate">
|
||||
<input type="hidden" name="path" value="{{ .Data.Path }}">
|
||||
<input type="submit" value="{{ string .Blog.Lang "update" }}">
|
||||
</form>
|
||||
<form class="in" method="post" action="{{ .Blog.RelativePath "/editor" }}">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="url" value="{{ .Canonical }}">
|
||||
<input type="submit" value="{{ string .Blog.Lang "delete" }}" class="confirm" data-confirmmessage="{{ string .Blog.Lang "confirmdelete" }}">
|
||||
</form>
|
||||
{{ if .Data.Deleted }}
|
||||
<form class="in" method="post" action="{{ .Blog.RelativePath "/editor" }}">
|
||||
<input type="hidden" name="action" value="undelete">
|
||||
<input type="hidden" name="url" value="{{ .Canonical }}">
|
||||
<input type="submit" value="{{ string .Blog.Lang "undelete" }}">
|
||||
</form>
|
||||
{{ end }}
|
||||
{{ if ttsenabled }}
|
||||
<form class="in" method="post" action="{{ .Blog.RelativePath "/editor" }}">
|
||||
<input type="hidden" name="editoraction" value="tts">
|
||||
<input type="hidden" name="url" value="{{ .Canonical }}">
|
||||
<input type="submit" value="{{ string .Blog.Lang "gentts" }}">
|
||||
</form>
|
||||
{{ end }}
|
||||
<script defer src="{{ asset "js/formconfirm.js" }}"></script>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if .CommentsEnabled }}{{ interactions .Blog .Canonical }}{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "post" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -1,21 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ with .Blog.Search.Title }}{{ mdtitle . }} - {{ end }}{{ mdtitle .Blog.Title }}</title>
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main>
|
||||
{{ with .Blog.Search.Title }}<h1>{{ mdtitle . }}</h1>{{ end }}
|
||||
{{ with .Blog.Search.Description }}{{ md . }}{{ end }}
|
||||
{{ if (or .Blog.Search.Title .Blog.Search.Description) }}
|
||||
<hr>
|
||||
{{ end }}
|
||||
<form class="fw p" method="post">
|
||||
<input type="text" name="q" {{ with .Blog.Search.Placeholder }}placeholder="{{ mdtitle . }}"{{ end }}>
|
||||
<input type="submit" value="🔍 {{ string .Blog.Lang "search" }}">
|
||||
</form>
|
||||
</main>
|
||||
{{ end }}
|
||||
|
||||
{{ define "search" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -1,27 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ mdtitle .Blog.Title }}</title>
|
||||
{{ postheadmeta .Data .Canonical }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main class=h-entry>
|
||||
<article>
|
||||
<data class="u-url hide" value="{{ absolute .Data.Path }}"></data>
|
||||
{{ if .Data.Content }}<div class=e-content>{{ content .Data false }}</div>{{ end }}
|
||||
</article>
|
||||
{{ author }}
|
||||
</main>
|
||||
{{ if .LoggedIn }}
|
||||
<div id="posteditactions" class="p">
|
||||
<form class="in" method="post" action="{{ .Blog.RelativePath "/editor" }}#update">
|
||||
<input type="hidden" name="editoraction" value="loadupdate">
|
||||
<input type="hidden" name="path" value="{{ .Data.Path }}">
|
||||
<input type="submit" value="{{ string .Blog.Lang "update" }}">
|
||||
</form>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "statichome" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -1,24 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ mdtitle .Data.Taxonomy.Title }} - {{ mdtitle .Blog.Title }}</title>
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main>
|
||||
{{ with .Data.Taxonomy.Title }}<h1>{{ mdtitle . }}</h1>{{ end }}
|
||||
{{ with .Data.Taxonomy.Description }}{{ md . }}{{ end }}
|
||||
{{ $blog := .Blog }}
|
||||
{{ $taxonomy := .Data.Taxonomy.Name }}
|
||||
{{ range $i, $valueGroup := .Data.ValueGroups }}
|
||||
<h2>{{ $valueGroup.Identifier }}</h2>
|
||||
<p>
|
||||
{{ range $i, $value := $valueGroup.Strings }}
|
||||
{{ if ne $i 0 }} • {{ end }}<a href="{{ $blog.RelativePath ( printf "/%s/%s" $taxonomy (urlize .) ) }}">{{ . }}</a>
|
||||
{{ end }}
|
||||
</p>
|
||||
{{ end }}
|
||||
</main>
|
||||
{{ end }}
|
||||
|
||||
{{ define "taxonomy" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -0,0 +1,386 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type summaryTyp string
|
||||
|
||||
const (
|
||||
defaultSummary summaryTyp = "summary"
|
||||
photoSummary summaryTyp = "photosummary"
|
||||
)
|
||||
|
||||
// post summary on index pages
|
||||
func (a *goBlog) renderSummary(hb *htmlBuilder, bc *configBlog, p *post, typ summaryTyp) {
|
||||
if bc == nil || p == nil {
|
||||
return
|
||||
}
|
||||
if typ == "" {
|
||||
typ = defaultSummary
|
||||
}
|
||||
// Start article
|
||||
hb.writeElementOpen("article", "class", "h-entry border-bottom")
|
||||
if p.Priority > 0 {
|
||||
// Is pinned post
|
||||
hb.writeElementOpen("p")
|
||||
hb.writeEscaped("📌 ")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(bc.Lang, "pinned"))
|
||||
hb.writeElementClose("p")
|
||||
}
|
||||
if p.RenderedTitle != "" {
|
||||
// Has title
|
||||
hb.writeElementOpen("h2", "class", "p-name")
|
||||
hb.writeElementOpen("a", "class", "u-url", "href", p.Path)
|
||||
hb.writeEscaped(p.RenderedTitle)
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("h2")
|
||||
}
|
||||
// Show photos in photo summary
|
||||
photos := a.photoLinks(p)
|
||||
if typ == photoSummary && len(photos) > 0 {
|
||||
for _, photo := range photos {
|
||||
hb.write(string(a.safeRenderMarkdownAsHTML(fmt.Sprintf("![](%s)", photo))))
|
||||
}
|
||||
}
|
||||
// Post meta
|
||||
a.renderPostMeta(hb, p, bc, "summary")
|
||||
if typ != photoSummary && a.showFull(p) {
|
||||
// Show full content
|
||||
hb.writeElementOpen("div", "class", "e-content")
|
||||
hb.write(string(a.postHtml(p, false)))
|
||||
hb.writeElementClose("div")
|
||||
} else {
|
||||
// Show summary
|
||||
hb.writeElementOpen("p", "class", "p-summary")
|
||||
hb.writeEscaped(a.postSummary(p))
|
||||
hb.writeElementClose("p")
|
||||
}
|
||||
// Show link to full post
|
||||
hb.writeElementOpen("p")
|
||||
if len(photos) > 0 {
|
||||
// Contains photos
|
||||
hb.writeEscaped("🖼️ ")
|
||||
}
|
||||
hb.writeElementOpen("a", "class", "u-url", "href", p.Path)
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(bc.Lang, "view"))
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("p")
|
||||
// Finish article
|
||||
hb.writeElementClose("article")
|
||||
}
|
||||
|
||||
// list of post taxonomy values (tags, series, etc.)
|
||||
func (a *goBlog) renderPostTax(hb *htmlBuilder, p *post, b *configBlog) {
|
||||
if b == nil || p == nil {
|
||||
return
|
||||
}
|
||||
// Iterate over all taxonomies
|
||||
for _, tax := range b.Taxonomies {
|
||||
// Get all sorted taxonomy values for this post
|
||||
if taxValues := sortedStrings(p.Parameters[tax.Name]); len(taxValues) > 0 {
|
||||
// Start new paragraph
|
||||
hb.writeElementOpen("p")
|
||||
// Add taxonomy name
|
||||
hb.writeElementOpen("strong")
|
||||
hb.writeEscaped(a.renderMdTitle(tax.Title))
|
||||
hb.writeElementClose("strong")
|
||||
hb.write(": ")
|
||||
// Add taxonomy values
|
||||
for i, taxValue := range taxValues {
|
||||
if i > 0 {
|
||||
hb.write(", ")
|
||||
}
|
||||
hb.writeElementOpen(
|
||||
"a",
|
||||
"class", "p-category",
|
||||
"rel", "tag",
|
||||
"href", b.getRelativePath(fmt.Sprintf("/%s/%s", tax.Name, urlize(taxValue))),
|
||||
)
|
||||
hb.writeEscaped(a.renderMdTitle(taxValue))
|
||||
hb.writeElementClose("a")
|
||||
}
|
||||
// End paragraph
|
||||
hb.writeElementClose("p")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// post meta information.
|
||||
// typ can be "summary", "post" or "preview".
|
||||
func (a *goBlog) renderPostMeta(hb *htmlBuilder, p *post, b *configBlog, typ string) {
|
||||
if b == nil || p == nil || typ != "summary" && typ != "post" && typ != "preview" {
|
||||
return
|
||||
}
|
||||
if typ == "summary" || typ == "post" {
|
||||
hb.writeElementOpen("div", "class", "p")
|
||||
}
|
||||
// Published time
|
||||
if published := p.Published; published != "" {
|
||||
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.writeElementClose("time")
|
||||
// Section
|
||||
if p.Section != "" {
|
||||
if section := b.Sections[p.Section]; section != nil {
|
||||
hb.write(" in ") // TODO: Replace with a proper translation
|
||||
hb.writeElementOpen("a", "href", b.getRelativePath(section.Name))
|
||||
hb.writeEscaped(a.renderMdTitle(section.Title))
|
||||
hb.writeElementClose("a")
|
||||
}
|
||||
}
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
// Updated time
|
||||
if updated := p.Updated; updated != "" {
|
||||
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.writeElementClose("time")
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
// IndieWeb Meta
|
||||
// Reply ("u-in-reply-to")
|
||||
if replyLink := a.replyLink(p); replyLink != "" {
|
||||
hb.writeElementOpen("div")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "replyto"))
|
||||
hb.writeEscaped(": ")
|
||||
hb.writeElementOpen("a", "class", "u-in-reply-to", "rel", "noopener", "target", "_blank", "href", replyLink)
|
||||
if replyTitle := a.replyTitle(p); replyTitle != "" {
|
||||
hb.writeEscaped(replyTitle)
|
||||
} else {
|
||||
hb.writeEscaped(replyLink)
|
||||
}
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
// Like ("u-like-of")
|
||||
if likeLink := a.likeLink(p); likeLink != "" {
|
||||
hb.writeElementOpen("div")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "likeof"))
|
||||
hb.writeEscaped(": ")
|
||||
hb.writeElementOpen("a", "class", "u-like-of", "rel", "noopener", "target", "_blank", "href", likeLink)
|
||||
if likeTitle := a.likeTitle(p); likeTitle != "" {
|
||||
hb.writeEscaped(likeTitle)
|
||||
} else {
|
||||
hb.writeEscaped(likeLink)
|
||||
}
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
// Geo
|
||||
if geoURI := a.geoURI(p); geoURI != nil {
|
||||
hb.writeElementOpen("div")
|
||||
hb.writeEscaped("📍 ")
|
||||
hb.writeElementOpen("a", "class", "p-location h-geo", "target", "_blank", "rel", "nofollow noopener noreferrer", "href", geoOSMLink(geoURI))
|
||||
hb.writeElementOpen("span", "class", "p-name")
|
||||
hb.writeEscaped(a.geoTitle(geoURI, b.Lang))
|
||||
hb.writeElementClose("span")
|
||||
hb.writeElementOpen("data", "class", "p-longitude", "value", fmt.Sprintf("%f", geoURI.Longitude))
|
||||
hb.writeElementClose("data")
|
||||
hb.writeElementOpen("data", "class", "p-latitude", "value", fmt.Sprintf("%f", geoURI.Latitude))
|
||||
hb.writeElementClose("data")
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
// Post specific elements
|
||||
if typ == "post" {
|
||||
// Translations
|
||||
if translations := a.postTranslations(p); len(translations) > 0 {
|
||||
hb.writeElementOpen("div")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "translations"))
|
||||
hb.writeEscaped(": ")
|
||||
for i, translation := range translations {
|
||||
if i > 0 {
|
||||
hb.writeEscaped(", ")
|
||||
}
|
||||
hb.writeElementOpen("a", "translate", "no", "href", translation.Path)
|
||||
hb.writeEscaped(translation.RenderedTitle)
|
||||
hb.writeElementClose("a")
|
||||
}
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
// Short link
|
||||
if shortLink := a.shortPostURL(p); shortLink != "" {
|
||||
hb.writeElementOpen("div")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "shorturl"))
|
||||
hb.writeEscaped(" ")
|
||||
hb.writeElementOpen("a", "rel", "shortlink", "href", shortLink)
|
||||
hb.writeEscaped(shortLink)
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
// Status
|
||||
if p.Status != statusPublished {
|
||||
hb.writeElementOpen("div")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "status"))
|
||||
hb.writeEscaped(": ")
|
||||
hb.writeEscaped(string(p.Status))
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
}
|
||||
if typ == "summary" || typ == "post" {
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
}
|
||||
|
||||
// warning for old posts
|
||||
func (a *goBlog) renderOldContentWarning(hb *htmlBuilder, p *post, b *configBlog) {
|
||||
if b == nil || p == nil || !p.Old() {
|
||||
return
|
||||
}
|
||||
hb.writeElementOpen("strong", "class", "p border-top border-bottom")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "oldcontent"))
|
||||
hb.writeElementClose("strong")
|
||||
}
|
||||
|
||||
func (a *goBlog) renderInteractions(hb *htmlBuilder, b *configBlog, canonical string) {
|
||||
if b == nil || canonical == "" {
|
||||
return
|
||||
}
|
||||
// Start accordion
|
||||
hb.writeElementOpen("details", "class", "p", "id", "interactions")
|
||||
hb.writeElementOpen("summary")
|
||||
hb.writeElementOpen("strong")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "interactions"))
|
||||
hb.writeElementClose("strong")
|
||||
hb.writeElementClose("summary")
|
||||
// Render mentions
|
||||
var renderMentions func(m []*mention)
|
||||
renderMentions = func(m []*mention) {
|
||||
if len(m) == 0 {
|
||||
return
|
||||
}
|
||||
hb.writeElementOpen("ul")
|
||||
for _, mention := range m {
|
||||
hb.writeElementOpen("li")
|
||||
hb.writeElementOpen("a", "href", mention.Url, "target", "_blank", "rel", "nofollow noopener noreferrer ugc")
|
||||
hb.writeEscaped(defaultIfEmpty(mention.Author, mention.Url))
|
||||
hb.writeElementClose("a")
|
||||
if mention.Title != "" {
|
||||
hb.write(" ")
|
||||
hb.writeElementOpen("strong")
|
||||
hb.writeEscaped(mention.Title)
|
||||
hb.writeElementClose("strong")
|
||||
}
|
||||
if mention.Content != "" {
|
||||
hb.write(" ")
|
||||
hb.writeElementOpen("i")
|
||||
hb.writeEscaped(mention.Content)
|
||||
hb.writeElementClose("i")
|
||||
}
|
||||
if len(mention.Submentions) > 0 {
|
||||
renderMentions(mention.Submentions)
|
||||
}
|
||||
hb.writeElementClose("li")
|
||||
}
|
||||
hb.writeElementClose("ul")
|
||||
}
|
||||
renderMentions(a.db.getWebmentionsByAddress(canonical))
|
||||
// Show form to send a webmention
|
||||
hb.writeElementOpen("form", "class", "fw p", "method", "post", "action", "/webmention")
|
||||
hb.writeElementOpen("label", "for", "wm-source", "class", "p")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "interactionslabel"))
|
||||
hb.writeElementClose("label")
|
||||
hb.writeElementOpen("input", "id", "wm-source", "type", "url", "name", "source", "placeholder", "URL", "required", "")
|
||||
hb.writeElementOpen("input", "type", "hidden", "name", "target", "value", canonical)
|
||||
hb.writeElementOpen("input", "type", "submit", "value", a.ts.GetTemplateStringVariant(b.Lang, "send"))
|
||||
hb.writeElementClose("form")
|
||||
// Show form to create a new comment
|
||||
hb.writeElementOpen("form", "class", "fw p", "method", "post", "action", "/comment")
|
||||
hb.writeElementOpen("input", "type", "hidden", "name", "target", "value", canonical)
|
||||
hb.writeElementOpen("input", "type", "text", "name", "name", "placeholder", a.ts.GetTemplateStringVariant(b.Lang, "nameopt"))
|
||||
hb.writeElementOpen("input", "type", "url", "name", "website", "placeholder", a.ts.GetTemplateStringVariant(b.Lang, "websiteopt"))
|
||||
hb.writeElementOpen("textarea", "name", "comment", "required", "", "placeholder", a.ts.GetTemplateStringVariant(b.Lang, "comment"))
|
||||
hb.writeElementClose("textarea")
|
||||
hb.writeElementOpen("input", "type", "submit", "value", a.ts.GetTemplateStringVariant(b.Lang, "docomment"))
|
||||
hb.writeElementClose("form")
|
||||
// Finish accordion
|
||||
hb.writeElementClose("details")
|
||||
}
|
||||
|
||||
// author h-card
|
||||
func (a *goBlog) renderAuthor(hb *htmlBuilder) {
|
||||
user := a.cfg.User
|
||||
if user == nil {
|
||||
return
|
||||
}
|
||||
hb.writeElementOpen("div", "class", "p-author h-card hide")
|
||||
if user.Picture != "" {
|
||||
hb.writeElementOpen("data", "class", "u-photo", "value", user.Picture)
|
||||
hb.writeElementClose("data")
|
||||
}
|
||||
if user.Name != "" {
|
||||
hb.writeElementOpen("a", "class", "p-name u-url", "rel", "me", "href", defaultIfEmpty(user.Link, "/"))
|
||||
hb.writeEscaped(user.Name)
|
||||
hb.writeElementClose("a")
|
||||
}
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
|
||||
// head meta tags for a post
|
||||
func (a *goBlog) renderPostHeadMeta(hb *htmlBuilder, p *post, canonical string) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
if canonical != "" {
|
||||
hb.writeElementOpen("meta", "property", "og:url", "content", canonical)
|
||||
hb.writeElementOpen("meta", "property", "twitter:url", "content", canonical)
|
||||
}
|
||||
if p.RenderedTitle != "" {
|
||||
hb.writeElementOpen("meta", "property", "og:title", "content", p.RenderedTitle)
|
||||
hb.writeElementOpen("meta", "property", "twitter:title", "content", p.RenderedTitle)
|
||||
}
|
||||
if summary := a.postSummary(p); summary != "" {
|
||||
hb.writeElementOpen("meta", "name", "description", "content", summary)
|
||||
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 p.Updated != "" {
|
||||
hb.writeElementOpen("meta", "itemprop", "dateModified", "content", dateFormat(p.Updated, "2006-01-02T15:04:05-07:00"))
|
||||
}
|
||||
for _, img := range a.photoLinks(p) {
|
||||
hb.writeElementOpen("meta", "itemprop", "image", "content", img)
|
||||
hb.writeElementOpen("meta", "property", "og:image", "content", img)
|
||||
hb.writeElementOpen("meta", "property", "twitter:image", "content", img)
|
||||
}
|
||||
}
|
||||
|
||||
// TOR notice in the footer
|
||||
func (a *goBlog) renderTorNotice(hb *htmlBuilder, rd *renderData) {
|
||||
if !a.cfg.Server.Tor || (!rd.TorUsed && rd.TorAddress == "") {
|
||||
return
|
||||
}
|
||||
if rd.TorUsed {
|
||||
hb.writeElementOpen("p", "id", "tor")
|
||||
hb.writeEscaped("🔐 ")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "connectedviator"))
|
||||
hb.writeElementClose("p")
|
||||
} else if rd.TorAddress != "" {
|
||||
hb.writeElementOpen("p", "id", "tor")
|
||||
hb.writeEscaped("🔓 ")
|
||||
hb.writeElementOpen("a", "href", rd.TorAddress)
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "connectviator"))
|
||||
hb.writeElementClose("a")
|
||||
hb.writeEscaped(" ")
|
||||
hb.writeElementOpen("a", "href", "https://www.torproject.org/", "target", "_blank", "rel", "nofollow noopener noreferrer")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "whatistor"))
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("p")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *goBlog) renderTitleTag(hb *htmlBuilder, blog *configBlog, optionalTitle string) {
|
||||
hb.writeElementOpen("title")
|
||||
if optionalTitle != "" {
|
||||
hb.writeEscaped(optionalTitle)
|
||||
hb.writeEscaped(" - ")
|
||||
}
|
||||
hb.writeEscaped(a.renderMdTitle(blog.Title))
|
||||
hb.writeElementClose("title")
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
textTemplate "text/template"
|
||||
)
|
||||
|
||||
type htmlBuilder struct {
|
||||
w io.Writer
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func newHtmlBuilder(w io.Writer) *htmlBuilder {
|
||||
return &htmlBuilder{
|
||||
w: w,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *htmlBuilder) getWriter() io.Writer {
|
||||
if h.w != nil {
|
||||
return h.w
|
||||
}
|
||||
return &h.buf
|
||||
}
|
||||
|
||||
func (h *htmlBuilder) Write(p []byte) (int, error) {
|
||||
return h.getWriter().Write(p)
|
||||
}
|
||||
|
||||
func (h *htmlBuilder) WriteString(s string) (int, error) {
|
||||
return io.WriteString(h.getWriter(), s)
|
||||
}
|
||||
|
||||
func (h *htmlBuilder) Read(p []byte) (int, error) {
|
||||
return h.buf.Read(p)
|
||||
}
|
||||
|
||||
func (h *htmlBuilder) String() string {
|
||||
return h.buf.String()
|
||||
}
|
||||
|
||||
func (h *htmlBuilder) Bytes() []byte {
|
||||
return h.buf.Bytes()
|
||||
}
|
||||
|
||||
func (h *htmlBuilder) html() template.HTML {
|
||||
return template.HTML(h.String())
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
func (h *htmlBuilder) writeAttribute(attr string, val interface{}) {
|
||||
h.write(` `)
|
||||
h.write(attr)
|
||||
h.write(`=`)
|
||||
if valStr, ok := val.(string); ok {
|
||||
h.write(`"`)
|
||||
h.writeEscaped(valStr)
|
||||
h.write(`"`)
|
||||
} else {
|
||||
h.writeEscaped(fmt.Sprint(val))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *htmlBuilder) writeElementOpen(tag string, attrs ...interface{}) {
|
||||
h.write(`<`)
|
||||
h.write(tag)
|
||||
for i := 0; i < len(attrs); i += 2 {
|
||||
if i+2 > len(attrs) {
|
||||
break
|
||||
}
|
||||
attrStr, ok := attrs[i].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
h.writeAttribute(attrStr, attrs[i+1])
|
||||
}
|
||||
h.write(`>`)
|
||||
}
|
||||
|
||||
func (h *htmlBuilder) writeElementClose(tag string) {
|
||||
h.write(`</`)
|
||||
h.write(tag)
|
||||
h.write(`>`)
|
||||
}
|
32
ui_test.go
32
ui_test.go
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"html/template"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -11,6 +12,10 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var _ io.Writer = &htmlBuilder{}
|
||||
var _ io.StringWriter = &htmlBuilder{}
|
||||
var _ io.Reader = &htmlBuilder{}
|
||||
|
||||
func Test_renderPostTax(t *testing.T) {
|
||||
app := &goBlog{
|
||||
cfg: createDefaultTestConfig(t),
|
||||
|
@ -137,30 +142,3 @@ func Test_renderAuthor(t *testing.T) {
|
|||
|
||||
assert.Equal(t, template.HTML("<div class=\"p-author h-card hide\"><data class=\"u-photo\" value=\"https://example.com/picture.jpg\"></data><a class=\"p-name u-url\" rel=\"me\" href=\"/\">John Doe</a></div>"), res)
|
||||
}
|
||||
|
||||
func Test_renderTorNotice(t *testing.T) {
|
||||
app := &goBlog{
|
||||
cfg: createDefaultTestConfig(t),
|
||||
}
|
||||
_ = app.initConfig()
|
||||
_ = app.initDatabase(false)
|
||||
app.initComponents(false)
|
||||
|
||||
app.cfg.Server.Tor = true
|
||||
|
||||
var hb htmlBuilder
|
||||
app.renderTorNotice(&hb, app.cfg.Blogs["default"], true, "http://abc.onion:80/test")
|
||||
res := hb.html()
|
||||
_, err := goquery.NewDocumentFromReader(strings.NewReader(string(res)))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, template.HTML("<p id=\"tor\">🔐 Connected via Tor.</p>"), res)
|
||||
|
||||
hb.Reset()
|
||||
app.renderTorNotice(&hb, app.cfg.Blogs["default"], false, "http://abc.onion:80/test")
|
||||
res = hb.html()
|
||||
_, err = goquery.NewDocumentFromReader(strings.NewReader(string(res)))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, template.HTML("<p id=\"tor\">🔓 <a href=\"http://abc.onion:80/test\">Connect via Tor.</a> <a href=\"https://www.torproject.org/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">What is Tor?</a></p>"), res)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue