Replace more templates

This commit is contained in:
Jan-Lukas Else 2022-01-27 18:17:44 +01:00
parent bf812d9650
commit e02ec1cd71
14 changed files with 221 additions and 137 deletions

View File

@ -1,6 +1,6 @@
# How to use GoBlog
This section of the documentation is a work in progress!
This section of the documentation is a **work in progress**!
## Posting

View File

@ -16,8 +16,8 @@ func (a *goBlog) serveEditorFiles(w http.ResponseWriter, r *http.Request) {
}
// Check if files at all
if len(files) == 0 {
a.render(w, r, templateEditorFiles, &renderData{
Data: map[string]interface{}{},
a.renderNew(w, r, a.renderEditorFiles, &renderData{
Data: &editorFilesRenderData{},
})
return
}
@ -39,10 +39,10 @@ func (a *goBlog) serveEditorFiles(w http.ResponseWriter, r *http.Request) {
return
}
// Serve HTML
a.render(w, r, templateEditorFiles, &renderData{
Data: map[string]interface{}{
"Files": files,
"Uses": uses,
a.renderNew(w, r, a.renderEditorFiles, &renderData{
Data: &editorFilesRenderData{
files: files,
uses: uses,
},
})
}

2
go.mod
View File

@ -55,7 +55,7 @@ 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-20220112180741-5e0467b6c7ce
golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/text v0.3.7

4
go.sum
View File

@ -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-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI=
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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/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=

View File

@ -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.render(w, r, templateIndieAuth, &renderData{
a.renderNew(w, r, a.renderIndieAuth, &renderData{
Data: iareq,
})
}

View File

@ -154,13 +154,13 @@ func (a *goBlog) notificationsAdmin(w http.ResponseWriter, r *http.Request) {
}
nextPath = fmt.Sprintf("%s/page/%d", notificationsPath, nextPage)
// Render
a.render(w, r, templateNotificationsAdmin, &renderData{
Data: map[string]interface{}{
"Notifications": notifications,
"HasPrev": hasPrev,
"HasNext": hasNext,
"Prev": prevPath,
"Next": nextPath,
a.renderNew(w, r, a.renderNotificationsAdmin, &renderData{
Data: &notificationsRenderData{
notifications: notifications,
hasPrev: hasPrev,
hasNext: hasNext,
prev: prevPath,
next: nextPath,
},
})
}

View File

@ -628,7 +628,7 @@ select name, count(path) as count from (
group by name;
`
func (db *database) usesOfMediaFile(names ...string) (counts map[string]int, err error) {
func (db *database) usesOfMediaFile(names ...string) (counts []int, err error) {
sqlArgs := []interface{}{dbNoCache}
var nameValues strings.Builder
for i, n := range names {
@ -645,7 +645,7 @@ func (db *database) usesOfMediaFile(names ...string) (counts map[string]int, err
if err != nil {
return nil, err
}
counts = map[string]int{}
counts = make([]int, len(names))
var name string
var count int
for rows.Next() {
@ -653,7 +653,12 @@ func (db *database) usesOfMediaFile(names ...string) (counts map[string]int, err
if err != nil {
return nil, err
}
counts[name] = count
for i, n := range names {
if n == name {
counts[i] = count
break
}
}
}
return counts, nil
}

View File

@ -358,7 +358,7 @@ func Test_usesOfMediaFile(t *testing.T) {
counts, err := app.db.usesOfMediaFile("test.jpg")
require.NoError(t, err)
assert.Len(t, counts, 1)
if assert.NotNil(t, counts["test.jpg"]) {
assert.Equal(t, 2, counts["test.jpg"])
if assert.NotEmpty(t, counts) {
assert.Equal(t, 2, counts[0])
}
}

View File

@ -16,13 +16,10 @@ const (
templatesDir = "templates"
templatesExt = ".gohtml"
templateBase = "base"
templateEditor = "editor"
templateEditorFiles = "editorfiles"
templateCommentsAdmin = "commentsadmin"
templateNotificationsAdmin = "notificationsadmin"
templateWebmentionAdmin = "webmentionadmin"
templateIndieAuth = "indieauth"
templateBase = "base"
templateEditor = "editor"
templateCommentsAdmin = "commentsadmin"
templateWebmentionAdmin = "webmentionadmin"
)
func (a *goBlog) initRendering() error {
@ -42,14 +39,12 @@ func (a *goBlog) initRendering() error {
},
// Others
"dateformat": dateFormat,
"isodate": isoDateFormat,
"unixtodate": unixToLocalDateString,
"now": localNowString,
"asset": a.assetFileName,
"string": a.ts.GetTemplateStringVariantFunc(),
"absolute": a.getFullAddress,
"opensearch": openSearchUrl,
"mbytes": mBytesString,
"editortemplate": a.editorPostTemplate,
"editorpostdesc": a.editorPostDesc,
}

View File

@ -1,29 +0,0 @@
{{ define "title" }}
<title>{{ string .Blog.Lang "mediafiles" }} - {{ mdtitle .Blog.Title }}</title>
{{ end }}
{{ define "main" }}
<main>
{{ $blog := .Blog }}
<h2>{{ string .Blog.Lang "mediafiles" }}</h2>
{{ if .Data.Files }}
<form class="fw p" method="post">
<select name="filename">
{{ $uses := .Data.Uses }}
{{ range $i, $file := .Data.Files }}
<option value="{{ $file.Name }}">{{ $file.Name }} ({{ isodate $file.Time.String }}, {{ mbytes $file.Size }}, ~{{ index $uses $file.Name }} {{ string $blog.Lang "fileuses" }})</option>
{{ end }}
</select>
<input type="submit" formaction="{{ .Blog.RelativePath "/editor/files/view" }}" value="{{ string .Blog.Lang "view" }}">
<input type="submit" formaction="{{ .Blog.RelativePath "/editor/files/delete" }}" value="{{ string .Blog.Lang "delete" }}" class="confirm" data-confirmmessage="{{ string .Blog.Lang "confirmdelete" }}">
</form>
<script defer src="{{ asset "js/formconfirm.js" }}"></script>
{{ else }}
<p>{{ string .Blog.Lang "nofiles" }}</p>
{{ end }}
</main>
{{ end }}
{{ define "editorfiles" }}
{{ template "base" . }}
{{ end }}

View File

@ -1,33 +0,0 @@
{{ define "title" }}
<title>{{ string .Blog.Lang "indieauth" }} - {{ .Blog.Title }}</title>
{{ end }}
{{ define "main" }}
<main>
<h1>{{ string .Blog.Lang "indieauth" }}</h1>
<div class="p">
<form method="post" action="/indieauth/accept">
{{ if .Data.Scopes }}
<h3>{{ string .Blog.Lang "scopes" }}</h3>
<ul>
{{ range $i, $scope := .Data.Scopes }}
<li><input type="checkbox" name="scopes" value="{{ $scope }}" id="scope-{{ $scope }}" checked><label for="scope-{{ $scope }}">{{ $scope }}</label></li>
{{ end }}
</ul>
{{ end }}
<p><strong>client_id:</strong> {{ .Data.ClientID }}</p>
<p><strong>redirect_uri:</strong> {{ .Data.RedirectURI }}</p>
<input type="hidden" name="redirect_uri" value="{{ .Data.RedirectURI }}">
<input type="hidden" name="state" value="{{ .Data.State }}">
<input type="hidden" name="client_id" value="{{ .Data.ClientID }}">
<input type="hidden" name="code_challenge" value="{{ .Data.CodeChallenge }}">
<input type="hidden" name="code_challenge_method" value="{{ .Data.CodeChallengeMethod }}">
<input type="submit" value="{{ string .Blog.Lang "authenticate" }}">
</form>
</div>
</main>
{{ end }}
{{ define "indieauth" }}
{{ template "base" . }}
{{ end }}

View File

@ -1,29 +0,0 @@
{{ define "title" }}
<title>{{ string "notifications" }} - {{ mdtitle .Blog.Title }}</title>
{{ end }}
{{ define "main" }}
<main>
<h1>{{ string "notifications" }}</h1>
{{ range $i, $notification := .Data.Notifications }}
<div class="p">
<p><i>{{ unixtodate $notification.Time }}</i></p>
{{ md $notification.Text }}
<form method="post">
<input type="hidden" name="notificationid" value="{{ $notification.ID }}">
<input type="submit" formaction="/notifications/delete" value="{{ string "delete" }}">
</form>
</div>
{{ end }}
{{ if .Data.HasPrev }}
<p><a href="{{ .Data.Prev }}">{{ string "prev" }}</a></p>
{{ end }}
{{ if .Data.HasNext }}
<p><a href="{{ .Data.Next }}">{{ string "next" }}</a></p>
{{ end }}
</main>
{{ end }}
{{ define "notificationsadmin" }}
{{ template "base" . }}
{{ end }}

185
ui.go
View File

@ -5,6 +5,7 @@ import (
"strings"
"time"
"github.com/hacdias/indieauth"
"github.com/kaorimatz/go-opml"
"github.com/thoas/go-funk"
)
@ -398,20 +399,7 @@ func (a *goBlog) renderIndex(hb *htmlBuilder, rd *renderData) {
hb.writeElementClose("p")
}
// Navigation
if id.hasPrev {
hb.writeElementOpen("p")
hb.writeElementOpen("a", "href", id.prev) // TODO: rel=prev?
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "prev"))
hb.writeElementClose("a")
hb.writeElementClose("p")
}
if id.hasNext {
hb.writeElementOpen("p")
hb.writeElementOpen("a", "href", id.next) // TODO: rel=next?
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "next"))
hb.writeElementClose("a")
hb.writeElementClose("p")
}
a.renderPagination(hb, rd.Blog, id.hasPrev, id.hasNext, id.prev, id.next)
// Author
a.renderAuthor(hb)
hb.writeElementClose("main")
@ -1054,3 +1042,172 @@ func (a *goBlog) renderStaticHome(hb *htmlBuilder, rd *renderData) {
},
)
}
func (a *goBlog) renderIndieAuth(hb *htmlBuilder, rd *renderData) {
indieAuthRequest, ok := rd.Data.(*indieauth.AuthenticationRequest)
if !ok {
return
}
a.renderBase(
hb, rd,
func(hb *htmlBuilder) {
a.renderTitleTag(hb, rd.Blog, a.ts.GetTemplateStringVariant(rd.Blog.Lang, "indieauth"))
},
func(hb *htmlBuilder) {
hb.writeElementOpen("main")
// Title
hb.writeElementOpen("h1")
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "indieauth"))
hb.writeElementClose("h1")
hb.writeElementClose("main")
// Form
hb.writeElementOpen("form", "method", "post", "action", "/indieauth/accept", "class", "p")
// Scopes
if scopes := indieAuthRequest.Scopes; len(scopes) > 0 {
hb.writeElementOpen("h3")
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "scopes"))
hb.writeElementClose("h3")
hb.writeElementOpen("ul")
for _, scope := range scopes {
hb.writeElementOpen("li")
hb.writeElementOpen("input", "type", "checkbox", "name", "scopes", "value", scope, "id", "scope-"+scope, "checked", "")
hb.writeElementOpen("label", "for", "scope-"+scope)
hb.writeEscaped(scope)
hb.writeElementClose("label")
hb.writeElementClose("li")
}
hb.writeElementClose("ul")
}
// Client ID
hb.writeElementOpen("p")
hb.writeElementOpen("strong")
hb.writeEscaped("client_id:")
hb.writeElementClose("strong")
hb.write(" ")
hb.writeEscaped(indieAuthRequest.ClientID)
hb.writeElementClose("p")
// Redirect URI
hb.writeElementOpen("p")
hb.writeElementOpen("strong")
hb.writeEscaped("redirect_uri:")
hb.writeElementClose("strong")
hb.write(" ")
hb.writeEscaped(indieAuthRequest.RedirectURI)
hb.writeElementClose("p")
// Hidden form fields
hb.writeElementOpen("input", "type", "hidden", "name", "client_id", "value", indieAuthRequest.ClientID)
hb.writeElementOpen("input", "type", "hidden", "name", "redirect_uri", "value", indieAuthRequest.RedirectURI)
hb.writeElementOpen("input", "type", "hidden", "name", "state", "value", indieAuthRequest.State)
hb.writeElementOpen("input", "type", "hidden", "name", "code_challenge", "value", indieAuthRequest.CodeChallenge)
hb.writeElementOpen("input", "type", "hidden", "name", "code_challenge_method", "value", indieAuthRequest.CodeChallengeMethod)
// Submit button
hb.writeElementOpen("input", "type", "submit", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "authenticate"))
hb.writeElementClose("form")
},
)
}
type editorFilesRenderData struct {
files []*mediaFile
uses []int
}
func (a *goBlog) renderEditorFiles(hb *htmlBuilder, rd *renderData) {
ef, ok := rd.Data.(*editorFilesRenderData)
if !ok {
return
}
a.renderBase(
hb, rd,
func(hb *htmlBuilder) {
a.renderTitleTag(hb, rd.Blog, a.ts.GetTemplateStringVariant(rd.Blog.Lang, "mediafiles"))
},
func(hb *htmlBuilder) {
hb.writeElementOpen("main")
// Title
hb.writeElementOpen("h1")
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "mediafiles"))
hb.writeElementClose("h1")
// Files
if len(ef.files) > 0 {
// Form
hb.writeElementOpen("form", "method", "post", "class", "fw p")
// Select with number of uses
hb.writeElementOpen("select", "name", "filename")
usesString := a.ts.GetTemplateStringVariant(rd.Blog.Lang, "fileuses")
for i, f := range ef.files {
hb.writeElementOpen("option", "value", f.Name)
hb.writeEscaped(fmt.Sprintf("%s (%s), %s, ~%d %s", f.Name, isoDateFormat(f.Time.String()), mBytesString(f.Size), ef.uses[i], usesString))
hb.writeElementClose("option")
}
hb.writeElementClose("select")
// View button
hb.writeElementOpen(
"input", "type", "submit", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "view"),
"formaction", rd.Blog.getRelativePath("/editor/files/view"),
)
// Delete button
hb.writeElementOpen(
"input", "type", "submit", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "delete"),
"formaction", rd.Blog.getRelativePath("/editor/files/delete"),
"class", "confirm", "data-confirmmessage", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "confirmdelete"),
)
hb.writeElementOpen("script", "src", a.assetFileName("js/formconfirm.js"), "defer", "")
hb.writeElementClose("script")
hb.writeElementClose("form")
} else {
hb.writeElementOpen("p")
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "nofiles"))
hb.writeElementClose("p")
}
hb.writeElementClose("main")
},
)
}
type notificationsRenderData struct {
notifications []*notification
hasPrev, hasNext bool
prev, next string
}
func (a *goBlog) renderNotificationsAdmin(hb *htmlBuilder, rd *renderData) {
nrd, ok := rd.Data.(*notificationsRenderData)
if !ok {
return
}
a.renderBase(
hb, rd,
func(hb *htmlBuilder) {
a.renderTitleTag(hb, rd.Blog, a.ts.GetTemplateStringVariant(rd.Blog.Lang, "notifications"))
},
func(hb *htmlBuilder) {
hb.writeElementOpen("main")
// Title
hb.writeElementOpen("h1")
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "notifications"))
hb.writeElementClose("h1")
// Notifications
for _, n := range nrd.notifications {
hb.writeElementOpen("div", "class", "p")
// Date
hb.writeElementOpen("p")
hb.writeElementOpen("i")
hb.writeEscaped(unixToLocalDateString(n.Time))
hb.writeElementClose("i")
hb.writeElementClose("p")
// Message
a.renderMarkdownToWriter(hb, n.Text, false)
// Delete form
hb.writeElementOpen("form", "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")
hb.writeElementClose("div")
}
// Pagination
a.renderPagination(hb, rd.Blog, nrd.hasPrev, nrd.hasNext, nrd.prev, nrd.next)
hb.writeElementClose("main")
},
)
}

View File

@ -384,3 +384,21 @@ func (a *goBlog) renderTitleTag(hb *htmlBuilder, blog *configBlog, optionalTitle
hb.writeEscaped(a.renderMdTitle(blog.Title))
hb.writeElementClose("title")
}
func (a *goBlog) renderPagination(hb *htmlBuilder, blog *configBlog, hasPrev, hasNext bool, prev, next string) {
// Navigation
if hasPrev {
hb.writeElementOpen("p")
hb.writeElementOpen("a", "href", prev) // TODO: rel=prev?
hb.writeEscaped(a.ts.GetTemplateStringVariant(blog.Lang, "prev"))
hb.writeElementClose("a")
hb.writeElementClose("p")
}
if hasNext {
hb.writeElementOpen("p")
hb.writeElementOpen("a", "href", next) // TODO: rel=next?
hb.writeEscaped(a.ts.GetTemplateStringVariant(blog.Lang, "next"))
hb.writeElementClose("a")
hb.writeElementClose("p")
}
}