mirror of https://github.com/jlelse/GoBlog
Breaking! Render everything using go functions, remove custom pages support and now unused template rendering
parent
6dc36e697f
commit
1c7195a135
3
app.go
3
app.go
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
|
@ -70,8 +69,6 @@ type goBlog struct {
|
|||
min minify.Minifier
|
||||
// Regex Redirects
|
||||
regexRedirects []*regexRedirect
|
||||
// Rendering
|
||||
templates map[string]*template.Template
|
||||
// Sessions
|
||||
loginSessions, captchaSessions *dbSessionStore
|
||||
// Shutdown
|
||||
|
|
|
@ -62,7 +62,7 @@ func (a *goBlog) authMiddleware(next http.Handler) http.Handler {
|
|||
_ = r.ParseForm()
|
||||
b = []byte(r.PostForm.Encode())
|
||||
}
|
||||
a.renderNew(w, r, a.renderLogin, &renderData{
|
||||
a.render(w, r, a.renderLogin, &renderData{
|
||||
Data: &loginRenderData{
|
||||
loginMethod: r.Method,
|
||||
loginHeaders: base64.StdEncoding.EncodeToString(h),
|
||||
|
|
|
@ -29,7 +29,7 @@ func (a *goBlog) serveBlogroll(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
c := bc.Blogroll
|
||||
can := bc.getRelativePath(defaultIfEmpty(c.Path, defaultBlogrollPath))
|
||||
a.renderNew(w, r, a.renderBlogroll, &renderData{
|
||||
a.render(w, r, a.renderBlogroll, &renderData{
|
||||
Canonical: a.getFullAddress(can),
|
||||
Data: &blogrollRenderData{
|
||||
title: c.Title,
|
||||
|
|
|
@ -25,7 +25,7 @@ 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.renderNew(w, r, a.renderBlogStats, &renderData{
|
||||
a.render(w, r, a.renderBlogStats, &renderData{
|
||||
Canonical: a.getFullAddress(canonical),
|
||||
Data: &blogStatsRenderData{
|
||||
tableUrl: canonical + blogStatsTablePath,
|
||||
|
@ -43,7 +43,7 @@ func (a *goBlog) serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
// Render
|
||||
a.renderNew(w, r, a.renderBlogStatsTable, &renderData{
|
||||
a.render(w, r, a.renderBlogStatsTable, &renderData{
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ 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.renderNewWithStatusCode(w, r, http.StatusUnauthorized, a.renderCaptcha, &renderData{
|
||||
a.renderWithStatusCode(w, r, http.StatusUnauthorized, a.renderCaptcha, &renderData{
|
||||
Data: &captchaRenderData{
|
||||
captchaMethod: r.Method,
|
||||
captchaHeaders: base64.StdEncoding.EncodeToString(h),
|
||||
|
|
|
@ -41,7 +41,7 @@ func (a *goBlog) serveComment(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
_, bc := a.getBlog(r)
|
||||
a.renderNew(w, r, a.renderComment, &renderData{
|
||||
a.render(w, r, a.renderComment, &renderData{
|
||||
Canonical: a.getFullAddress(bc.getRelativePath(path.Join(commentPath, strconv.Itoa(id)))),
|
||||
Data: comment,
|
||||
})
|
||||
|
|
|
@ -70,13 +70,13 @@ func (a *goBlog) commentsAdmin(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
nextPath = fmt.Sprintf("%s/page/%d", commentsPath, nextPage)
|
||||
// Render
|
||||
a.render(w, r, templateCommentsAdmin, &renderData{
|
||||
Data: map[string]interface{}{
|
||||
"Comments": comments,
|
||||
"HasPrev": hasPrev,
|
||||
"HasNext": hasNext,
|
||||
"Prev": prevPath,
|
||||
"Next": nextPath,
|
||||
a.render(w, r, a.renderCommentsAdmin, &renderData{
|
||||
Data: &commentsRenderData{
|
||||
comments: comments,
|
||||
hasPrev: hasPrev,
|
||||
hasNext: hasNext,
|
||||
prev: prevPath,
|
||||
next: nextPath,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -81,7 +81,6 @@ type configBlog struct {
|
|||
Search *configSearch `mapstructure:"search"`
|
||||
BlogStats *configBlogStats `mapstructure:"blogStats"`
|
||||
Blogroll *configBlogroll `mapstructure:"blogroll"`
|
||||
CustomPages []*configCustomPage `mapstructure:"custompages"`
|
||||
Telegram *configTelegram `mapstructure:"telegram"`
|
||||
PostAsHome bool `mapstructure:"postAsHome"`
|
||||
RandomPost *configRandomPost `mapstructure:"randomPost"`
|
||||
|
@ -148,14 +147,6 @@ type configBlogroll struct {
|
|||
Description string `mapstructure:"description"`
|
||||
}
|
||||
|
||||
type configCustomPage struct {
|
||||
Path string `mapstructure:"path"`
|
||||
Template string `mapstructure:"template"`
|
||||
Cache bool `mapstructure:"cache"`
|
||||
CacheExpiration int `mapstructure:"cacheExpiration"`
|
||||
Data *interface{} `mapstructure:"data"`
|
||||
}
|
||||
|
||||
type configRandomPost struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Path string `mapstructure:"path"`
|
||||
|
|
|
@ -17,7 +17,7 @@ const defaultContactPath = "/contact"
|
|||
func (a *goBlog) serveContactForm(w http.ResponseWriter, r *http.Request) {
|
||||
_, bc := a.getBlog(r)
|
||||
cc := bc.Contact
|
||||
a.renderNew(w, r, a.renderContact, &renderData{
|
||||
a.render(w, r, a.renderContact, &renderData{
|
||||
Data: &contactRenderData{
|
||||
title: cc.Title,
|
||||
description: cc.Description,
|
||||
|
@ -63,7 +63,7 @@ func (a *goBlog) sendContactSubmission(w http.ResponseWriter, r *http.Request) {
|
|||
// Send notification
|
||||
a.sendNotification(message.String())
|
||||
// Give feedback
|
||||
a.renderNew(w, r, a.renderContact, &renderData{
|
||||
a.render(w, r, a.renderContact, &renderData{
|
||||
Data: &contactRenderData{
|
||||
sent: true,
|
||||
},
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
package main
|
||||
|
||||
import "net/http"
|
||||
|
||||
const customPageContextKey = "custompage"
|
||||
|
||||
func (a *goBlog) serveCustomPage(w http.ResponseWriter, r *http.Request) {
|
||||
page := r.Context().Value(customPageContextKey).(*configCustomPage)
|
||||
a.render(w, r, page.Template, &renderData{
|
||||
Canonical: a.getFullAddress(page.Path),
|
||||
Data: page.Data,
|
||||
})
|
||||
}
|
18
editor.go
18
editor.go
|
@ -21,8 +21,8 @@ import (
|
|||
const editorPath = "/editor"
|
||||
|
||||
func (a *goBlog) serveEditor(w http.ResponseWriter, r *http.Request) {
|
||||
a.render(w, r, templateEditor, &renderData{
|
||||
Data: map[string]interface{}{},
|
||||
a.render(w, r, a.renderEditor, &renderData{
|
||||
Data: &editorRenderData{},
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -89,10 +89,10 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
|
|||
a.serveError(w, r, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
a.render(w, r, templateEditor, &renderData{
|
||||
Data: map[string]interface{}{
|
||||
"UpdatePostURL": a.fullPostURL(post),
|
||||
"UpdatePostContent": a.postToMfItem(post).Properties.Content[0],
|
||||
a.render(w, r, a.renderEditor, &renderData{
|
||||
Data: &editorRenderData{
|
||||
updatePostUrl: a.fullPostURL(post),
|
||||
updatePostContent: a.postToMfItem(post).Properties.Content[0],
|
||||
},
|
||||
})
|
||||
case "updatepost":
|
||||
|
@ -188,14 +188,13 @@ func (a *goBlog) editorMicropubPost(w http.ResponseWriter, r *http.Request, medi
|
|||
_ = result.Body.Close()
|
||||
}
|
||||
|
||||
func (a *goBlog) editorPostTemplate(blog string) string {
|
||||
func (a *goBlog) editorPostTemplate(blog string, bc *configBlog) string {
|
||||
var builder strings.Builder
|
||||
marsh := func(param string, i interface{}) {
|
||||
_ = yaml.NewEncoder(&builder).Encode(map[string]interface{}{
|
||||
param: i,
|
||||
})
|
||||
}
|
||||
bc := a.cfg.Blogs[blog]
|
||||
builder.WriteString("---\n")
|
||||
marsh("blog", blog)
|
||||
marsh("section", bc.DefaultSection)
|
||||
|
@ -210,8 +209,7 @@ func (a *goBlog) editorPostTemplate(blog string) string {
|
|||
return builder.String()
|
||||
}
|
||||
|
||||
func (a *goBlog) editorPostDesc(blog string) string {
|
||||
bc := a.cfg.Blogs[blog]
|
||||
func (a *goBlog) editorPostDesc(bc *configBlog) string {
|
||||
t := a.ts.GetTemplateStringVariant(bc.Lang, "editorpostdesc")
|
||||
var paramBuilder, statusBuilder strings.Builder
|
||||
for i, param := range []string{
|
||||
|
|
|
@ -16,7 +16,7 @@ func (a *goBlog) serveEditorFiles(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
// Check if files at all
|
||||
if len(files) == 0 {
|
||||
a.renderNew(w, r, a.renderEditorFiles, &renderData{
|
||||
a.render(w, r, a.renderEditorFiles, &renderData{
|
||||
Data: &editorFilesRenderData{},
|
||||
})
|
||||
return
|
||||
|
@ -39,7 +39,7 @@ func (a *goBlog) serveEditorFiles(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
// Serve HTML
|
||||
a.renderNew(w, r, a.renderEditorFiles, &renderData{
|
||||
a.render(w, r, a.renderEditorFiles, &renderData{
|
||||
Data: &editorFilesRenderData{
|
||||
files: files,
|
||||
uses: uses,
|
||||
|
|
|
@ -35,7 +35,7 @@ func (a *goBlog) serveError(w http.ResponseWriter, r *http.Request, message stri
|
|||
http.Error(w, message, status)
|
||||
return
|
||||
}
|
||||
a.renderNewWithStatusCode(w, r, status, a.renderError, &renderData{
|
||||
a.renderWithStatusCode(w, r, status, a.renderError, &renderData{
|
||||
Data: &errorRenderData{
|
||||
Title: fmt.Sprintf("%d %s", status, http.StatusText(status)),
|
||||
Message: message,
|
||||
|
|
|
@ -222,15 +222,6 @@ blogs:
|
|||
authValue: abc # Authentication value for OPML
|
||||
categories: # Optional, allow only these categories
|
||||
- Blogs
|
||||
# Custom pages
|
||||
custompages:
|
||||
- path: /blogroll # Path
|
||||
template: blogroll # Template
|
||||
cache: true # Enable caching
|
||||
cacheExpiration: 600 # Cache expiration (default uses blog cache TTL)
|
||||
data: # Data to provide to template
|
||||
Title: Blogroll
|
||||
Description: "This are alphabetically sorted lists of blogs and sites I subscribe to."
|
||||
# Redirect to random post
|
||||
randomPost:
|
||||
enabled: true # Enable
|
||||
|
|
|
@ -25,7 +25,7 @@ func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
if len(allPostsWithLocation) == 0 {
|
||||
a.renderNew(w, r, a.renderGeoMap, &renderData{
|
||||
a.render(w, r, a.renderGeoMap, &renderData{
|
||||
Canonical: canonical,
|
||||
Data: &geoMapRenderData{
|
||||
noLocations: true,
|
||||
|
@ -85,7 +85,7 @@ func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
|
|||
tracksJson = string(tracksJsonBytes)
|
||||
}
|
||||
|
||||
a.renderNew(w, r, a.renderGeoMap, &renderData{
|
||||
a.render(w, r, a.renderGeoMap, &renderData{
|
||||
Canonical: canonical,
|
||||
Data: &geoMapRenderData{
|
||||
locations: locationsJson,
|
||||
|
|
8
go.mod
8
go.mod
|
@ -35,7 +35,7 @@ require (
|
|||
github.com/kaorimatz/go-opml v0.0.0-20210201121027-bc8e2852d7f9
|
||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
|
||||
github.com/lopezator/migrator v0.3.0
|
||||
github.com/mattn/go-sqlite3 v1.14.10
|
||||
github.com/mattn/go-sqlite3 v1.14.11
|
||||
github.com/microcosm-cc/bluemonday v1.0.17
|
||||
github.com/mmcdole/gofeed v1.1.3
|
||||
github.com/paulmach/go.geojson v1.4.0
|
||||
|
@ -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.29
|
||||
github.com/tdewolff/minify/v2 v2.10.0
|
||||
github.com/thoas/go-funk v0.9.1
|
||||
github.com/tkrajina/gpxgo v1.2.0
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
||||
|
@ -55,8 +55,8 @@ require (
|
|||
// master
|
||||
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20210516132338-9216f9c5aa01
|
||||
golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8
|
||||
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba
|
||||
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/text v0.3.7
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
|
|
16
go.sum
16
go.sum
|
@ -314,8 +314,8 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9
|
|||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk=
|
||||
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.11 h1:gt+cp9c0XGqe9S/wAHTL3n/7MqY+siPWgWJgqdsFrzQ=
|
||||
github.com/mattn/go-sqlite3 v1.14.11/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60 h1:tHdB+hQRHU10CfcK0furo6rSNgZ38JT8uPh70c/pFD8=
|
||||
|
@ -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.29 h1:QMVJaCJzWL0mXS33cX792YD074xz4lOhkyBS8hAzYAY=
|
||||
github.com/tdewolff/minify/v2 v2.9.29/go.mod h1:6XAjcHM46pFcRE0eztigFPm0Q+Cxsw8YhEWT+rDkcZM=
|
||||
github.com/tdewolff/minify/v2 v2.10.0 h1:ovVAHUcjfGrBDf1EIvsodRUVJiZK/28mMose08B7k14=
|
||||
github.com/tdewolff/minify/v2 v2.10.0/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=
|
||||
|
@ -472,8 +472,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8 h1:kACShD3qhmr/3rLmg1yXyt+N4HcwutKyPRB93s54TIU=
|
||||
golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA=
|
||||
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -553,8 +553,8 @@ golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba h1:6u6sik+bn/y7vILcYkK3iwTBWN7WtBvB0+SZswQnbf8=
|
||||
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
|
|
@ -128,9 +128,6 @@ func (a *goBlog) blogRouter(blog string, conf *configBlog) func(r chi.Router) {
|
|||
// Search
|
||||
r.Group(a.blogSearchRouter(conf))
|
||||
|
||||
// Custom pages
|
||||
r.Group(a.blogCustomPagesRouter(conf))
|
||||
|
||||
// Random post
|
||||
r.Group(a.blogRandomRouter(conf))
|
||||
|
||||
|
@ -295,29 +292,6 @@ func (a *goBlog) blogSearchRouter(conf *configBlog) func(r chi.Router) {
|
|||
}
|
||||
}
|
||||
|
||||
// Blog - Custom pages
|
||||
func (a *goBlog) blogCustomPagesRouter(conf *configBlog) func(r chi.Router) {
|
||||
return func(r chi.Router) {
|
||||
r.Use(a.privateModeHandler)
|
||||
for _, cp := range conf.CustomPages {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middleware.WithValue(customPageContextKey, cp))
|
||||
if cp.Cache {
|
||||
ce := cp.CacheExpiration
|
||||
if ce == 0 {
|
||||
ce = a.defaultCacheExpiration()
|
||||
}
|
||||
r.Use(
|
||||
a.cacheMiddleware,
|
||||
middleware.WithValue(cacheExpirationKey, ce),
|
||||
)
|
||||
}
|
||||
r.Get(cp.Path, a.serveCustomPage)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Blog - Random
|
||||
func (a *goBlog) blogRandomRouter(conf *configBlog) func(r chi.Router) {
|
||||
return func(r chi.Router) {
|
||||
|
|
|
@ -31,7 +31,7 @@ func (a *goBlog) indieAuthRequest(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
// Render page that let's the user authorize the app
|
||||
a.renderNew(w, r, a.renderIndieAuth, &renderData{
|
||||
a.render(w, r, a.renderIndieAuth, &renderData{
|
||||
Data: iareq,
|
||||
})
|
||||
}
|
||||
|
|
4
main.go
4
main.go
|
@ -154,10 +154,6 @@ func (app *goBlog) initComponents(logging bool) {
|
|||
app.logErrAndQuit("Failed to init template translations:", err.Error())
|
||||
return
|
||||
}
|
||||
if err = app.initRendering(); err != nil { // Needs assets and minify
|
||||
app.logErrAndQuit("Failed to init HTML rendering:", err.Error())
|
||||
return
|
||||
}
|
||||
if err = app.initCache(); err != nil {
|
||||
app.logErrAndQuit("Failed to init HTTP cache:", err.Error())
|
||||
return
|
||||
|
|
|
@ -154,7 +154,7 @@ func (a *goBlog) notificationsAdmin(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
nextPath = fmt.Sprintf("%s/page/%d", notificationsPath, nextPage)
|
||||
// Render
|
||||
a.renderNew(w, r, a.renderNotificationsAdmin, &renderData{
|
||||
a.render(w, r, a.renderNotificationsAdmin, &renderData{
|
||||
Data: ¬ificationsRenderData{
|
||||
notifications: notifications,
|
||||
hasPrev: hasPrev,
|
||||
|
|
|
@ -250,11 +250,7 @@ details summary {
|
|||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
#post-actions, #posteditactions {
|
||||
.actions {
|
||||
@extend .p;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
@ -265,6 +261,10 @@ details summary {
|
|||
}
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
#announcement {
|
||||
@extend .invert;
|
||||
padding: 5px;
|
||||
|
|
4
posts.go
4
posts.go
|
@ -83,7 +83,7 @@ func (a *goBlog) servePost(w http.ResponseWriter, r *http.Request) {
|
|||
if strings.HasSuffix(string(p.Status), statusDeletedSuffix) {
|
||||
status = http.StatusGone
|
||||
}
|
||||
a.renderNewWithStatusCode(w, r, status, renderMethod, &renderData{
|
||||
a.renderWithStatusCode(w, r, status, renderMethod, &renderData{
|
||||
BlogString: p.Blog,
|
||||
Canonical: canonical,
|
||||
Data: p,
|
||||
|
@ -356,7 +356,7 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
|
|||
if summaryTemplate == "" {
|
||||
summaryTemplate = defaultSummary
|
||||
}
|
||||
a.renderNew(w, r, a.renderIndex, &renderData{
|
||||
a.render(w, r, a.renderIndex, &renderData{
|
||||
Canonical: a.getFullAddress(path),
|
||||
Data: &indexRenderData{
|
||||
title: title,
|
||||
|
|
90
render.go
90
render.go
|
@ -1,76 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"go.goblog.app/app/pkgs/bufferpool"
|
||||
"go.goblog.app/app/pkgs/contenttype"
|
||||
)
|
||||
|
||||
const (
|
||||
templatesDir = "templates"
|
||||
templatesExt = ".gohtml"
|
||||
|
||||
templateBase = "base"
|
||||
templateEditor = "editor"
|
||||
templateCommentsAdmin = "commentsadmin"
|
||||
templateWebmentionAdmin = "webmentionadmin"
|
||||
)
|
||||
|
||||
func (a *goBlog) initRendering() error {
|
||||
a.templates = map[string]*template.Template{}
|
||||
templateFunctions := template.FuncMap{
|
||||
"md": a.safeRenderMarkdownAsHTML,
|
||||
"mdtitle": a.renderMdTitle,
|
||||
"html": wrapStringAsHTML,
|
||||
// Code based rendering
|
||||
"tor": func(rd *renderData) template.HTML {
|
||||
buf := bufferpool.Get()
|
||||
hb := newHtmlBuilder(buf)
|
||||
a.renderTorNotice(hb, rd)
|
||||
res := template.HTML(buf.String())
|
||||
bufferpool.Put(buf)
|
||||
return res
|
||||
},
|
||||
// Others
|
||||
"dateformat": dateFormat,
|
||||
"unixtodate": unixToLocalDateString,
|
||||
"now": localNowString,
|
||||
"asset": a.assetFileName,
|
||||
"string": a.ts.GetTemplateStringVariantFunc(),
|
||||
"absolute": a.getFullAddress,
|
||||
"opensearch": openSearchUrl,
|
||||
"editortemplate": a.editorPostTemplate,
|
||||
"editorpostdesc": a.editorPostDesc,
|
||||
}
|
||||
baseTemplate, err := template.New("base").Funcs(templateFunctions).ParseFiles(path.Join(templatesDir, templateBase+templatesExt))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = filepath.Walk(templatesDir, func(p string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Mode().IsRegular() && path.Ext(p) == templatesExt {
|
||||
if name := strings.TrimSuffix(path.Base(p), templatesExt); name != templateBase {
|
||||
if a.templates[name], err = template.Must(baseTemplate.Clone()).New(name).ParseFiles(p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type renderData struct {
|
||||
BlogString string
|
||||
Canonical string
|
||||
|
@ -91,31 +27,11 @@ func (d *renderData) LoggedIn() bool {
|
|||
return d.app.isLoggedIn(d.req)
|
||||
}
|
||||
|
||||
func (a *goBlog) render(w http.ResponseWriter, r *http.Request, template string, data *renderData) {
|
||||
a.renderWithStatusCode(w, r, http.StatusOK, template, data)
|
||||
func (a *goBlog) render(w http.ResponseWriter, r *http.Request, f func(*htmlBuilder, *renderData), data *renderData) {
|
||||
a.renderWithStatusCode(w, r, http.StatusOK, f, data)
|
||||
}
|
||||
|
||||
func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, statusCode int, template string, data *renderData) {
|
||||
// Check render data
|
||||
a.checkRenderData(r, data)
|
||||
// Set content type
|
||||
w.Header().Set(contentType, contenttype.HTMLUTF8)
|
||||
// Render template and write minified HTML
|
||||
buf := bufferpool.Get()
|
||||
defer bufferpool.Put(buf)
|
||||
if err := a.templates[template].ExecuteTemplate(buf, template, data); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(statusCode)
|
||||
_ = a.min.Minify(contenttype.HTML, w, buf)
|
||||
}
|
||||
|
||||
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) {
|
||||
func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, statusCode int, f func(*htmlBuilder, *renderData), data *renderData) {
|
||||
// Check render data
|
||||
a.checkRenderData(r, data)
|
||||
// Set content type
|
||||
|
|
|
@ -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.renderNew(w, r, a.renderSearch, &renderData{
|
||||
a.render(w, r, a.renderSearch, &renderData{
|
||||
Canonical: a.getFullAddress(servePath),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -102,12 +102,6 @@ func (a *goBlog) serveSitemapBlogFeatures(w http.ResponseWriter, r *http.Request
|
|||
Loc: a.getFullAddress(bc.getRelativePath(defaultIfEmpty(cc.Path, defaultContactPath))),
|
||||
})
|
||||
}
|
||||
// Custom pages
|
||||
for _, cp := range bc.CustomPages {
|
||||
sm.Add(&sitemap.URL{
|
||||
Loc: a.getFullAddress(cp.Path),
|
||||
})
|
||||
}
|
||||
// Write sitemap
|
||||
a.writeSitemapXML(w, sm)
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ func (a *goBlog) serveTaxonomy(w http.ResponseWriter, r *http.Request) {
|
|||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
a.renderNew(w, r, a.renderTaxonomy, &renderData{
|
||||
a.render(w, r, a.renderTaxonomy, &renderData{
|
||||
Canonical: a.getFullAddress(r.URL.Path),
|
||||
Data: &taxonomyRenderData{
|
||||
taxonomy: tax,
|
||||
|
|
|
@ -144,7 +144,7 @@ details summary > *:first-child {
|
|||
border-bottom: 1px solid var(--primary, #000);
|
||||
}
|
||||
|
||||
.p, #post-actions, #posteditactions, table {
|
||||
.p, .actions, table {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
|
@ -210,19 +210,19 @@ details summary > *:first-child {
|
|||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
#post-actions, #posteditactions {
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
#post-actions *, #posteditactions * {
|
||||
.actions * {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
#announcement {
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
{{ define "base" }}
|
||||
<!doctype html>
|
||||
<html lang="{{ .Blog.Lang }}">
|
||||
<meta charset="utf-8">
|
||||
<meta name=viewport content="width=device-width,initial-scale=1">
|
||||
<link rel="stylesheet" href="{{ asset "css/styles.css" }}">
|
||||
{{ with .Canonical }}<link rel="canonical" href="{{ . }}">{{ end }}
|
||||
{{ block "title" . }}<title>{{ mdtitle .Blog.Title }}</title>{{ end }}
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS ({{ mdtitle .Blog.Title }})" href="{{ .Blog.Path }}.rss"/>
|
||||
<link rel="alternate" type="application/atom+xml" title="Atom ({{ mdtitle .Blog.Title }})" href="{{ .Blog.Path }}.atom"/>
|
||||
<link rel="alternate" type="application/feed+json" title="JSON Feed ({{ mdtitle .Blog.Title }})" href="{{ .Blog.Path }}.json"/>
|
||||
<link rel="webmention" href="{{ absolute "/webmention" }}" />
|
||||
<link rel="micropub" href="/micropub" />
|
||||
<link rel="authorization_endpoint" href="/indieauth" />
|
||||
<link rel="token_endpoint" href="/indieauth/token" />
|
||||
{{ with .User }}{{ range .Identities }}<link rel="me" href="{{ . }}" />{{ end }}{{ end }}
|
||||
{{ $os := opensearch .Blog }}
|
||||
{{ if $os }}<link rel="search" type="application/opensearchdescription+xml" href="{{ $os }}" title="{{ mdtitle .Blog.Title }}" />{{ end }}
|
||||
{{ with .Blog.Announcement }}{{ with .Text }}<div id="announcement" data-nosnippet>{{ md . }}</div>{{ end }}{{ end }}
|
||||
<header>
|
||||
<h1><a href="{{ .Blog.RelativePath "/" }}" rel="home" title="{{ mdtitle .Blog.Title }}" translate="no">{{ mdtitle .Blog.Title }}</a></h1>
|
||||
{{ with .Blog.Description }}<p><i>{{ . }}</i></p>{{ end }}
|
||||
{{ with index .Blog.Menus "main" }}
|
||||
<nav>{{ range $i, $item := .Items }}{{ if ne $i 0 }} • {{ end }}<a href="{{ $item.Link }}">{{ mdtitle $item.Title }}</a>{{ end }}</nav>
|
||||
{{ end }}
|
||||
{{ if .LoggedIn }}
|
||||
<nav>
|
||||
<a href="{{ .Blog.RelativePath "/editor" }}">{{ string .Blog.Lang "editor" }}</a>
|
||||
• <a href="/notifications">{{ string .Blog.Lang "notifications" }}</a>
|
||||
{{ if .WebmentionReceivingEnabled }}• <a href="/webmention">{{ string .Blog.Lang "webmentions" }}</a>{{ end }}
|
||||
{{ if .CommentsEnabled }}• <a href="{{ .Blog.RelativePath "/comment" }}">{{ string .Blog.Lang "comments" }}</a>{{ end }}
|
||||
• <a href="/logout">{{ string .Blog.Lang "logout" }}</a>
|
||||
</nav>
|
||||
{{ end }}
|
||||
</header>
|
||||
{{ block "main" . }}{{ end }}
|
||||
<footer>
|
||||
{{ with index .Blog.Menus "footer" }}
|
||||
<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 . }}
|
||||
</footer>
|
||||
{{ if .EasterEgg }}<script defer src="{{ asset "js/easteregg.js" }}"></script>{{ end }}
|
||||
</html>
|
||||
{{ end }}
|
|
@ -1,36 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ string .Blog.Lang "comments" }} - {{ mdtitle .Blog.Title }}</title>
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main>
|
||||
<h1>{{ string .Blog.Lang "comments" }}</h1>
|
||||
{{ $blog := .Blog }}
|
||||
{{ range $i, $comment := .Data.Comments }}
|
||||
<div class="p">
|
||||
<p>
|
||||
ID: {{ $comment.ID }}<br/>
|
||||
Target: <a href="{{ $comment.Target }}" target="_blank">{{ $comment.Target }}</a><br/>
|
||||
Name: {{ if $comment.Website }}<a href="{{ $comment.Website }}" target="_blank" rel="nofollow noopener noreferrer ugc">{{ $comment.Name }}</a>{{ else }}{{ $comment.Name }}{{ end }}
|
||||
</p>
|
||||
<p>
|
||||
{{ html $comment.Comment }}
|
||||
</p>
|
||||
<form method="post">
|
||||
<input type="hidden" name="commentid" value="{{ $comment.ID }}">
|
||||
<input type="submit" formaction="comment/delete" value="{{ string $blog.Lang "delete" }}">
|
||||
</form>
|
||||
</div>
|
||||
{{ 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 }}
|
||||
</main>
|
||||
{{ end }}
|
||||
|
||||
{{ define "commentsadmin" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -1,66 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<link rel="stylesheet" href="{{ asset "css/chroma.css" }}">
|
||||
<title>{{ string .Blog.Lang "editor" }} - {{ mdtitle .Blog.Title }}</title>
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main>
|
||||
<h1>{{ string .Blog.Lang "editor" }}</h1>
|
||||
<h2>{{ string .Blog.Lang "create" }}</h2>
|
||||
{{ md (editorpostdesc .BlogString) }}
|
||||
<form class="fw p" method="post">
|
||||
<input type="hidden" name="h" value="entry">
|
||||
<textarea name="content" class="monospace h400p formcache mdpreview" id="create-input" data-preview="post-preview" data-previewws="{{ .Blog.RelativePath "/editor/preview" }}">{{ editortemplate .BlogString }}</textarea>
|
||||
<div id="post-preview" class="hide"></div>
|
||||
<input type="submit" value="{{ string .Blog.Lang "create" }}">
|
||||
</form>
|
||||
|
||||
{{ if .Data.UpdatePostURL }}
|
||||
<h2 id="update">{{ string .Blog.Lang "update" }}</h2>
|
||||
<form class="fw p" method="post" action="#update">
|
||||
<input type="hidden" name="editoraction" value="updatepost">
|
||||
<input type="hidden" name="url" value="{{ .Data.UpdatePostURL }}">
|
||||
<textarea name="content" class="monospace h400p mdpreview" data-preview="update-preview" data-previewws="{{ .Blog.RelativePath "/editor/preview" }}">{{ .Data.UpdatePostContent }}</textarea>
|
||||
<div id="update-preview" class="hide"></div>
|
||||
<input type="submit" value="{{ string .Blog.Lang "update" }}">
|
||||
</form>
|
||||
{{ end }}
|
||||
|
||||
<h2>{{ string .Blog.Lang "posts" }}</h2>
|
||||
<p><a href="{{ .Blog.RelativePath "/editor/drafts" }}">{{ string .Blog.Lang "drafts" }}</a></p>
|
||||
<p><a href="{{ .Blog.RelativePath "/editor/private" }}">{{ string .Blog.Lang "privateposts" }}</a></p>
|
||||
<p><a href="{{ .Blog.RelativePath "/editor/unlisted" }}">{{ string .Blog.Lang "unlistedposts" }}</a></p>
|
||||
<p><a href="{{ .Blog.RelativePath "/editor/scheduled" }}">{{ string .Blog.Lang "scheduledposts" }}</a></p>
|
||||
<p><a href="{{ .Blog.RelativePath "/editor/deleted" }}">{{ string .Blog.Lang "deletedposts" }}</a></p>
|
||||
|
||||
<h2>{{ string .Blog.Lang "upload" }}</h2>
|
||||
<form class="fw p" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="editoraction" value="upload">
|
||||
<input type="file" name="file">
|
||||
<input type="submit" value="{{ string .Blog.Lang "upload" }}">
|
||||
</form>
|
||||
<p><a href="{{ .Blog.RelativePath "/editor/files" }}">{{ string .Blog.Lang "mediafiles" }}</a></p>
|
||||
|
||||
<h2>{{ string .Blog.Lang "location" }}</h2>
|
||||
<form class="fw p">
|
||||
<input id="geobtn" type="button" value="{{ string .Blog.Lang "locationget" }}" data-failed="{{ string .Blog.Lang "locationfailed" }}" data-notsupported="{{ string .Blog.Lang "locationnotsupported" }}">
|
||||
<input id="geostatus" type="text" class="hide" readonly>
|
||||
</form>
|
||||
|
||||
<h2>{{ string .Blog.Lang "gpxhelper" }}</h2>
|
||||
<p>{{ string .Blog.Lang "gpxhelperdesc" }}</p>
|
||||
<form class="fw p" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="editoraction" value="helpgpx">
|
||||
<input type="file" name="file">
|
||||
<input type="submit" value="{{ string .Blog.Lang "upload" }}">
|
||||
</form>
|
||||
|
||||
<script defer src="{{ asset "js/mdpreview.js" }}"></script>
|
||||
<script defer src="{{ asset "js/geohelper.js" }}"></script>
|
||||
<script defer src="{{ asset "js/formcache.js" }}"></script>
|
||||
</main>
|
||||
{{ end }}
|
||||
|
||||
{{ define "editor" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
|
@ -1,43 +0,0 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ string .Blog.Lang "webmentions" }} - {{ mdtitle .Blog.Title }}</title>
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main>
|
||||
<h1>{{ string .Blog.Lang "webmentions" }}</h1>
|
||||
{{ $blog := .Blog }}
|
||||
{{ $current := .Data.Current }}
|
||||
{{ range $i, $mention := .Data.Mentions }}
|
||||
<div id="mention-{{ $mention.ID }}" class="p">
|
||||
<p>
|
||||
From: <a href="{{ $mention.Source }}" target="_blank" rel="noopener noreferrer">{{ $mention.Source }}</a><br/>
|
||||
{{ if not (eq $mention.Source $mention.Url ) }}u-url: <a href="{{ $mention.Url }}" target="_blank" rel="noopener noreferrer">{{ $mention.Url }}</a><br/>{{ end }}
|
||||
To: <a href="{{ $mention.Target }}" target="_blank">{{ $mention.Target }}</a><br/>
|
||||
Created: {{ unixtodate $mention.Created }}<br/><br/>
|
||||
{{ if $mention.Author }}{{ $mention.Author }}<br/>{{ end }}
|
||||
{{ with $mention.Title }}<b>{{.}}</b><br/>{{ end }}
|
||||
{{ with $mention.Content }}<i>{{.}}</i>{{ end }}
|
||||
</p>
|
||||
<form method="post">
|
||||
<input type="hidden" name="mentionid" value="{{ $mention.ID }}">
|
||||
<input type="hidden" name="redir" value="{{ $current }}#mention-{{ $mention.ID }}">
|
||||
{{ if eq $mention.Status "verified" }}
|
||||
<input type="submit" formaction="/webmention/approve" value="{{ string $blog.Lang "approve" }}">
|
||||
{{ end }}
|
||||
<input type="submit" formaction="/webmention/delete" value="{{ string $blog.Lang "delete" }}">
|
||||
<input type="submit" formaction="/webmention/reverify" value="{{ string $blog.Lang "reverify" }}">
|
||||
</form>
|
||||
</div>
|
||||
{{ 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 }}
|
||||
</main>
|
||||
{{ end }}
|
||||
|
||||
{{ define "webmentionadmin" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
335
ui.go
335
ui.go
|
@ -336,7 +336,7 @@ func (a *goBlog) renderComment(h *htmlBuilder, rd *renderData) {
|
|||
hb.writeElementClose("main")
|
||||
// Interactions
|
||||
if rd.CommentsEnabled {
|
||||
a.renderInteractions(hb, rd.Blog, rd.Canonical)
|
||||
a.renderInteractions(hb, rd)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -448,7 +448,7 @@ func (a *goBlog) renderBlogStats(hb *htmlBuilder, rd *renderData) {
|
|||
hb.writeElementClose("main")
|
||||
// Interactions
|
||||
if rd.CommentsEnabled {
|
||||
a.renderInteractions(hb, rd.Blog, rd.Canonical)
|
||||
a.renderInteractions(hb, rd)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -620,7 +620,7 @@ func (a *goBlog) renderGeoMap(hb *htmlBuilder, rd *renderData) {
|
|||
}
|
||||
hb.writeElementClose("main")
|
||||
if rd.CommentsEnabled {
|
||||
a.renderInteractions(hb, rd.Blog, rd.Canonical)
|
||||
a.renderInteractions(hb, rd)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -695,7 +695,7 @@ func (a *goBlog) renderBlogroll(hb *htmlBuilder, rd *renderData) {
|
|||
hb.writeElementClose("main")
|
||||
// Interactions
|
||||
if rd.CommentsEnabled {
|
||||
a.renderInteractions(hb, rd.Blog, rd.Canonical)
|
||||
a.renderInteractions(hb, rd)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -891,7 +891,7 @@ func (a *goBlog) renderPost(hb *htmlBuilder, rd *renderData) {
|
|||
// Post meta
|
||||
a.renderPostMeta(hb, p, rd.Blog, "post")
|
||||
// Post actions
|
||||
hb.writeElementOpen("div", "id", "post-actions")
|
||||
hb.writeElementOpen("div", "class", "actions")
|
||||
// Share button
|
||||
hb.writeElementOpen("a", "class", "button", "href", fmt.Sprintf("https://www.addtoany.com/share#url=%s%s", a.shortPostURL(p), funk.ShortIf(p.RenderedTitle != "", "&title="+p.RenderedTitle, "")), "target", "_blank", "rel", "nofollow noopener noreferrer")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "share"))
|
||||
|
@ -964,7 +964,7 @@ func (a *goBlog) renderPost(hb *htmlBuilder, rd *renderData) {
|
|||
hb.writeElementClose("main")
|
||||
// Post edit actions
|
||||
if rd.LoggedIn() {
|
||||
hb.writeElementOpen("div", "id", "posteditactions")
|
||||
hb.writeElementOpen("div", "class", "actions")
|
||||
// Update
|
||||
hb.writeElementOpen("form", "method", "post", "action", rd.Blog.RelativePath("/editor")+"#update")
|
||||
hb.writeElementOpen("input", "type", "hidden", "name", "editoraction", "value", "loadupdate")
|
||||
|
@ -999,7 +999,7 @@ func (a *goBlog) renderPost(hb *htmlBuilder, rd *renderData) {
|
|||
}
|
||||
// Comments
|
||||
if rd.CommentsEnabled {
|
||||
a.renderInteractions(hb, rd.Blog, rd.Canonical)
|
||||
a.renderInteractions(hb, rd)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -1201,9 +1201,9 @@ func (a *goBlog) renderNotificationsAdmin(hb *htmlBuilder, rd *renderData) {
|
|||
hb.writeElementClose("i")
|
||||
hb.writeElementClose("p")
|
||||
// Message
|
||||
a.renderMarkdownToWriter(hb, n.Text, false)
|
||||
_ = a.renderMarkdownToWriter(hb, n.Text, false)
|
||||
// Delete form
|
||||
hb.writeElementOpen("form", "method", "post", "action", "/notifications/delete")
|
||||
hb.writeElementOpen("form", "class", "actions", "method", "post", "action", "/notifications/delete")
|
||||
hb.writeElementOpen("input", "type", "hidden", "name", "notificationid", "value", n.ID)
|
||||
hb.writeElementOpen("input", "type", "submit", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "delete"))
|
||||
hb.writeElementClose("form")
|
||||
|
@ -1215,3 +1215,320 @@ func (a *goBlog) renderNotificationsAdmin(hb *htmlBuilder, rd *renderData) {
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
type commentsRenderData struct {
|
||||
comments []*comment
|
||||
hasPrev, hasNext bool
|
||||
prev, next string
|
||||
}
|
||||
|
||||
func (a *goBlog) renderCommentsAdmin(hb *htmlBuilder, rd *renderData) {
|
||||
crd, ok := rd.Data.(*commentsRenderData)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
a.renderBase(
|
||||
hb, rd,
|
||||
func(hb *htmlBuilder) {
|
||||
a.renderTitleTag(hb, rd.Blog, a.ts.GetTemplateStringVariant(rd.Blog.Lang, "comments"))
|
||||
},
|
||||
func(hb *htmlBuilder) {
|
||||
hb.writeElementOpen("main")
|
||||
// Title
|
||||
hb.writeElementOpen("h1")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "comments"))
|
||||
hb.writeElementClose("h1")
|
||||
// Notifications
|
||||
for _, c := range crd.comments {
|
||||
hb.writeElementOpen("div", "class", "p")
|
||||
// ID, Target, Name
|
||||
hb.writeElementOpen("p")
|
||||
hb.writeEscaped("ID: ")
|
||||
hb.writeEscaped(fmt.Sprintf("%d", c.ID))
|
||||
hb.writeElementOpen("br")
|
||||
hb.writeEscaped("Target: ")
|
||||
hb.writeElementOpen("a", "href", c.Target, "target", "_blank")
|
||||
hb.writeEscaped(c.Target)
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementOpen("br")
|
||||
hb.writeEscaped("Name: ")
|
||||
if c.Website != "" {
|
||||
hb.writeElementOpen("a", "href", c.Website, "target", "_blank", "rel", "nofollow noopener noreferrer ugc")
|
||||
}
|
||||
hb.writeEscaped(c.Name)
|
||||
if c.Website != "" {
|
||||
hb.writeElementClose("a")
|
||||
}
|
||||
hb.writeElementClose("p")
|
||||
// Comment
|
||||
hb.writeElementOpen("p")
|
||||
hb.write(c.Comment)
|
||||
hb.writeElementClose("p")
|
||||
// Delete form
|
||||
hb.writeElementOpen("form", "class", "actions", "method", "post", "action", rd.Blog.getRelativePath("/comment/delete"))
|
||||
hb.writeElementOpen("input", "type", "hidden", "name", "commentid", "value", c.ID)
|
||||
hb.writeElementOpen("input", "type", "submit", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "delete"))
|
||||
hb.writeElementClose("form")
|
||||
hb.writeElementClose("div")
|
||||
}
|
||||
// Pagination
|
||||
a.renderPagination(hb, rd.Blog, crd.hasPrev, crd.hasNext, crd.prev, crd.next)
|
||||
hb.writeElementClose("main")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
type webmentionRenderData struct {
|
||||
mentions []*mention
|
||||
hasPrev, hasNext bool
|
||||
prev, current, next string
|
||||
}
|
||||
|
||||
func (a *goBlog) renderWebmentionAdmin(hb *htmlBuilder, rd *renderData) {
|
||||
wrd, ok := rd.Data.(*webmentionRenderData)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
a.renderBase(
|
||||
hb, rd,
|
||||
func(hb *htmlBuilder) {
|
||||
a.renderTitleTag(hb, rd.Blog, a.ts.GetTemplateStringVariant(rd.Blog.Lang, "webmentions"))
|
||||
},
|
||||
func(hb *htmlBuilder) {
|
||||
hb.writeElementOpen("main")
|
||||
// Title
|
||||
hb.writeElementOpen("h1")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "webmentions"))
|
||||
hb.writeElementClose("h1")
|
||||
// Notifications
|
||||
for _, m := range wrd.mentions {
|
||||
hb.writeElementOpen("div", "id", fmt.Sprintf("mention-%d", m.ID), "class", "p")
|
||||
hb.writeElementOpen("p")
|
||||
// Source
|
||||
hb.writeEscaped("From: ")
|
||||
hb.writeElementOpen("a", "href", m.Source, "target", "_blank", "rel", "noopener noreferrer")
|
||||
hb.writeEscaped(m.Source)
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementOpen("br")
|
||||
// u-url
|
||||
if m.Source != m.Url {
|
||||
hb.writeEscaped("u-url: ")
|
||||
hb.writeElementOpen("a", "href", m.Url, "target", "_blank", "rel", "noopener noreferrer")
|
||||
hb.writeEscaped(m.Url)
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementOpen("br")
|
||||
}
|
||||
// Target
|
||||
hb.writeEscaped("To: ")
|
||||
hb.writeElementOpen("a", "href", m.Target, "target", "_blank")
|
||||
hb.writeEscaped(m.Target)
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementOpen("br")
|
||||
// Date
|
||||
hb.writeEscaped("Created: ")
|
||||
hb.writeEscaped(unixToLocalDateString(m.Created))
|
||||
hb.writeElementOpen("br")
|
||||
hb.writeElementOpen("br")
|
||||
// Author
|
||||
if m.Author != "" {
|
||||
hb.writeEscaped(m.Author)
|
||||
hb.writeElementOpen("br")
|
||||
}
|
||||
// Title
|
||||
if m.Title != "" {
|
||||
hb.writeElementOpen("strong")
|
||||
hb.writeEscaped(m.Title)
|
||||
hb.writeElementClose("strong")
|
||||
hb.writeElementOpen("br")
|
||||
}
|
||||
// Content
|
||||
if m.Content != "" {
|
||||
hb.writeElementOpen("i")
|
||||
hb.writeEscaped(m.Content)
|
||||
hb.writeElementClose("i")
|
||||
hb.writeElementOpen("br")
|
||||
}
|
||||
hb.writeElementClose("p")
|
||||
// Actions
|
||||
hb.writeElementOpen("form", "method", "post", "class", "actions")
|
||||
hb.writeElementOpen("input", "type", "hidden", "name", "mentionid", "value", m.ID)
|
||||
hb.writeElementOpen("input", "type", "hidden", "name", "redir", "value", fmt.Sprintf("%s#mention-%d", wrd.current, m.ID))
|
||||
if m.Status == webmentionStatusVerified {
|
||||
// Approve verified mention
|
||||
hb.writeElementOpen("input", "type", "submit", "formaction", "/webmention/approve", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "approve"))
|
||||
}
|
||||
// Delete mention
|
||||
hb.writeElementOpen("input", "type", "submit", "formaction", "/webmention/delete", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "delete"))
|
||||
// Reverify mention
|
||||
hb.writeElementOpen("input", "type", "submit", "formaction", "/webmention/reverify", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "reverify"))
|
||||
hb.writeElementClose("form")
|
||||
}
|
||||
// Pagination
|
||||
a.renderPagination(hb, rd.Blog, wrd.hasPrev, wrd.hasNext, wrd.prev, wrd.next)
|
||||
hb.writeElementClose("main")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
type editorRenderData struct {
|
||||
updatePostUrl string
|
||||
updatePostContent string
|
||||
}
|
||||
|
||||
func (a *goBlog) renderEditor(hb *htmlBuilder, rd *renderData) {
|
||||
edrd, ok := rd.Data.(*editorRenderData)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
a.renderBase(
|
||||
hb, rd,
|
||||
func(hb *htmlBuilder) {
|
||||
a.renderTitleTag(hb, rd.Blog, a.ts.GetTemplateStringVariant(rd.Blog.Lang, "editor"))
|
||||
// Chroma CSS
|
||||
hb.writeElementOpen("link", "rel", "stylesheet", "href", a.assetFileName("css/chroma.css"))
|
||||
},
|
||||
func(hb *htmlBuilder) {
|
||||
hb.writeElementOpen("main")
|
||||
// Title
|
||||
hb.writeElementOpen("h1")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "editor"))
|
||||
hb.writeElementClose("h1")
|
||||
|
||||
// Create
|
||||
hb.writeElementOpen("h2")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "create"))
|
||||
hb.writeElementClose("h2")
|
||||
_ = a.renderMarkdownToWriter(hb, a.editorPostDesc(rd.Blog), false)
|
||||
hb.writeElementOpen("form", "method", "post", "class", "fw p")
|
||||
hb.writeElementOpen("input", "type", "hidden", "name", "h", "value", "entry")
|
||||
hb.writeElementOpen(
|
||||
"textarea",
|
||||
"name", "content",
|
||||
"class", "monospace h400p formcache mdpreview",
|
||||
"id", "create-input",
|
||||
"data-preview", "post-preview",
|
||||
"data-previewws", rd.Blog.getRelativePath("/editor/preview"),
|
||||
)
|
||||
hb.writeEscaped(a.editorPostTemplate(rd.BlogString, rd.Blog))
|
||||
hb.writeElementClose("textarea")
|
||||
hb.writeElementOpen("div", "id", "post-preview", "class", "hide")
|
||||
hb.writeElementClose("div")
|
||||
hb.writeElementOpen("input", "type", "submit", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "create"))
|
||||
hb.writeElementClose("form")
|
||||
|
||||
// Update
|
||||
if edrd.updatePostUrl != "" {
|
||||
hb.writeElementOpen("h2", "id", "#update")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "update"))
|
||||
hb.writeElementClose("h2")
|
||||
hb.writeElementOpen("form", "method", "post", "class", "fw p", "action", "#update")
|
||||
hb.writeElementOpen("input", "type", "hidden", "name", "editoraction", "value", "updatepost")
|
||||
hb.writeElementOpen("input", "type", "hidden", "name", "url", "value", edrd.updatePostUrl)
|
||||
hb.writeElementOpen(
|
||||
"textarea",
|
||||
"name", "content",
|
||||
"class", "monospace h400p mdpreview",
|
||||
"data-preview", "update-preview",
|
||||
"data-previewws", rd.Blog.getRelativePath("/editor/preview"),
|
||||
)
|
||||
hb.writeEscaped(edrd.updatePostContent)
|
||||
hb.writeElementClose("textarea")
|
||||
hb.writeElementOpen("div", "id", "update-preview", "class", "hide")
|
||||
hb.writeElementClose("div")
|
||||
hb.writeElementOpen("input", "type", "submit", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "update"))
|
||||
hb.writeElementClose("form")
|
||||
}
|
||||
|
||||
// Posts
|
||||
hb.writeElementOpen("h2")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "posts"))
|
||||
hb.writeElementClose("h2")
|
||||
// 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")
|
||||
// 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")
|
||||
// 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")
|
||||
// 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")
|
||||
// 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")
|
||||
|
||||
// Upload
|
||||
hb.writeElementOpen("h2")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "upload"))
|
||||
hb.writeElementClose("h2")
|
||||
hb.writeElementOpen("form", "class", "fw p", "method", "post", "enctype", "multipart/form-data")
|
||||
hb.writeElementOpen("input", "type", "hidden", "name", "editoraction", "value", "upload")
|
||||
hb.writeElementOpen("input", "type", "file", "name", "file")
|
||||
hb.writeElementOpen("input", "type", "submit", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "upload"))
|
||||
hb.writeElementClose("form")
|
||||
// Media files
|
||||
hb.writeElementOpen("p")
|
||||
hb.writeElementOpen("a", "href", rd.Blog.getRelativePath("/editor/files"))
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "mediafiles"))
|
||||
hb.writeElementClose("a")
|
||||
hb.writeElementClose("p")
|
||||
|
||||
// Location-Helper
|
||||
hb.writeElementOpen("h2")
|
||||
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "location"))
|
||||
hb.writeElementClose("h2")
|
||||
hb.writeElementOpen("form", "class", "fw p")
|
||||
hb.writeElementOpen(
|
||||
"input", "id", "geobtn", "type", "button",
|
||||
"value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "locationget"),
|
||||
"data-failed", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "locationfailed"),
|
||||
"data-notsupported", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "locationnotsupported"),
|
||||
)
|
||||
hb.writeElementOpen("input", "id", "geostatus", "type", "text", "class", "hide", "readonly", "")
|
||||
hb.writeElementClose("form")
|
||||
|
||||