Browse Source

Various refactorings

master
Jan-Lukas Else 5 months ago
parent
commit
6bc70f0a0e
  1. 59
      activityPub.go
  2. 4
      captcha_test.go
  3. 10
      editor.go
  4. 2
      httpClient_test.go
  5. 4
      mediaCompression_test.go
  6. 9
      mediaStorage.go
  7. 30
      mediaStorage_test.go
  8. 238
      micropub.go
  9. 32
      postsFuncs.go
  10. 52
      render.go
  11. 6
      templates/editor.gohtml

59
activityPub.go

@ -82,22 +82,23 @@ func (a *goBlog) apHandleWebfinger(w http.ResponseWriter, r *http.Request) {
a.serveError(w, r, "Resource not found", http.StatusNotFound)
return
}
apIri := a.apIri(blog)
b, _ := json.Marshal(map[string]interface{}{
"subject": a.webfingerAccts[a.apIri(blog)],
"subject": a.webfingerAccts[apIri],
"aliases": []string{
a.webfingerAccts[a.apIri(blog)],
a.apIri(blog),
a.webfingerAccts[apIri],
apIri,
},
"links": []map[string]string{
{
"rel": "self",
"type": contenttype.AS,
"href": a.apIri(blog),
"href": apIri,
},
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": a.apIri(blog),
"href": apIri,
},
},
})
@ -107,8 +108,8 @@ func (a *goBlog) apHandleWebfinger(w http.ResponseWriter, r *http.Request) {
func (a *goBlog) apHandleInbox(w http.ResponseWriter, r *http.Request) {
blogName := chi.URLParam(r, "blog")
blog := a.cfg.Blogs[blogName]
if blog == nil {
blog, ok := a.cfg.Blogs[blogName]
if !ok || blog == nil {
a.serveError(w, r, "Inbox not found", http.StatusNotFound)
return
}
@ -159,31 +160,28 @@ func (a *goBlog) apHandleInbox(w http.ResponseWriter, r *http.Request) {
case "Undo":
{
if object, ok := activity["object"].(map[string]interface{}); ok {
if objectType, ok := object["type"].(string); ok && objectType == "Follow" {
if iri, ok := object["actor"].(string); ok && iri == activityActor {
_ = a.db.apRemoveFollower(blogName, activityActor)
}
ot := cast.ToString(object["type"])
actor := cast.ToString(object["actor"])
if ot == "Follow" && actor == activityActor {
_ = a.db.apRemoveFollower(blogName, activityActor)
}
}
}
case "Create":
{
if object, ok := activity["object"].(map[string]interface{}); ok {
inReplyTo := cast.ToString(object["inReplyTo"])
objectId := cast.ToString(object["id"])
objectUrl := cast.ToString(object["url"])
baseUrl := objectId
if objectUrl != "" {
baseUrl = objectUrl
baseUrl := cast.ToString(object["id"])
if ou := cast.ToString(object["url"]); ou != "" {
baseUrl = ou
}
if inReplyTo != "" && objectId != "" && strings.Contains(inReplyTo, blogIri) {
if r := cast.ToString(object["inReplyTo"]); r != "" && baseUrl != "" && strings.HasPrefix(r, blogIri) {
// It's an ActivityPub reply; save reply as webmention
_ = a.createWebmention(baseUrl, inReplyTo)
} else if content, hasContent := object["content"].(string); hasContent && objectId != "" {
_ = a.createWebmention(baseUrl, r)
} else if content := cast.ToString(object["content"]); content != "" && baseUrl != "" {
// May be a mention; find links to blog and save them as webmentions
if links, err := allLinksFromHTMLString(content, baseUrl); err == nil {
for _, link := range links {
if strings.Contains(link, blogIri) {
if strings.HasPrefix(link, blogIri) {
_ = a.createWebmention(baseUrl, link)
}
}
@ -191,25 +189,22 @@ func (a *goBlog) apHandleInbox(w http.ResponseWriter, r *http.Request) {
}
}
}
case "Delete":
case "Block":
case "Delete", "Block":
{
if object, ok := activity["object"].(string); ok && len(object) > 0 && object == activityActor {
if o := cast.ToString(activity["object"]); o == activityActor {
_ = a.db.apRemoveFollower(blogName, activityActor)
}
}
case "Like":
{
likeObject, likeObjectOk := activity["object"].(string)
if likeObjectOk && len(likeObject) > 0 && strings.Contains(likeObject, blogIri) {
a.sendNotification(fmt.Sprintf("%s liked %s", activityActor, likeObject))
if o := cast.ToString(activity["object"]); o != "" && strings.HasPrefix(o, blogIri) {
a.sendNotification(fmt.Sprintf("%s liked %s", activityActor, o))
}
}
case "Announce":
{
announceObject, announceObjectOk := activity["object"].(string)
if announceObjectOk && len(announceObject) > 0 && strings.Contains(announceObject, blogIri) {
a.sendNotification(fmt.Sprintf("%s announced %s", activityActor, announceObject))
if o := cast.ToString(activity["object"]); o != "" && strings.HasPrefix(o, blogIri) {
a.sendNotification(fmt.Sprintf("%s announced %s", activityActor, o))
}
}
}
@ -230,11 +225,11 @@ func (a *goBlog) apVerifySignature(r *http.Request) (*asPerson, string, int, err
return nil, keyID, statusCode, err
}
if actor.PublicKey == nil || actor.PublicKey.PublicKeyPem == "" {
return nil, keyID, 0, errors.New("Actor has no public key")
return nil, keyID, 0, errors.New("actor has no public key")
}
block, _ := pem.Decode([]byte(actor.PublicKey.PublicKeyPem))
if block == nil {
return nil, keyID, 0, errors.New("Public key invalid")
return nil, keyID, 0, errors.New("public key invalid")
}
pubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {

4
captcha_test.go

@ -32,7 +32,7 @@ func Test_captchaMiddleware(t *testing.T) {
_ = app.initRendering()
h := app.captchaMiddleware(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Write([]byte("ABC Test"))
_, _ = rw.Write([]byte("ABC Test"))
}))
t.Run("Default", func(t *testing.T) {
@ -58,7 +58,7 @@ func Test_captchaMiddleware(t *testing.T) {
session, _ := app.captchaSessions.Get(req, "c")
session.Values["captcha"] = true
session.Save(req, rec1)
_ = session.Save(req, rec1)
for _, cookie := range rec1.Result().Cookies() {
req.AddCookie(cookie)

10
editor.go

@ -50,7 +50,7 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
BlogString: blog,
Data: map[string]interface{}{
"UpdatePostURL": parsedURL.String(),
"UpdatePostContent": a.toMfItem(post).Properties.Content[0],
"UpdatePostContent": a.postToMfItem(post).Properties.Content[0],
"Drafts": a.db.getDrafts(blog),
},
})
@ -77,6 +77,14 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
a.editorMicropubPost(w, req, false)
case "upload":
a.editorMicropubPost(w, r, true)
case "viewdraft":
parsedURL, err := url.Parse(r.FormValue("url"))
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
http.Redirect(w, r, parsedURL.Path, http.StatusFound)
return
default:
a.serveError(w, r, "Unknown editoraction", http.StatusBadRequest)
}

2
httpClient_test.go

@ -36,7 +36,7 @@ func (c *fakeHttpClient) setHandler(handler http.Handler) {
func (c *fakeHttpClient) setFakeResponse(statusCode int, body string) {
c.setHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(statusCode)
rw.Write([]byte(body))
_, _ = rw.Write([]byte(body))
}))
}

4
mediaCompression_test.go

@ -32,7 +32,7 @@ func Test_compress(t *testing.T) {
assert.Equal(t, "https://www.cloudflare.com/cdn-cgi/image/f=jpeg,q=75,metadata=none,fit=scale-down,w=2000,h=3000/https://example.com/original.jpg", r.URL.String())
rw.WriteHeader(http.StatusOK)
rw.Write([]byte(fakeFileContent))
_, _ = rw.Write([]byte(fakeFileContent))
}))
cf := &cloudflare{}
@ -59,7 +59,7 @@ func Test_compress(t *testing.T) {
assert.Equal(t, "https://example.com/original.jpg", requestJson["url"])
rw.WriteHeader(http.StatusOK)
rw.Write([]byte(fakeFileContent))
_, _ = rw.Write([]byte(fakeFileContent))
}))
cf := &shortpixel{"testkey"}

9
mediaStorage.go

@ -40,10 +40,13 @@ type mediaStorage interface {
type localMediaStorage struct {
mediaURL string // optional
path string // required
}
func (a *goBlog) initLocalMediaStorage() mediaStorage {
ms := &localMediaStorage{}
ms := &localMediaStorage{
path: mediaFilePath,
}
if config := a.cfg.Micropub.MediaStorage; config != nil && config.MediaURL != "" {
ms.mediaURL = config.MediaURL
}
@ -51,10 +54,10 @@ func (a *goBlog) initLocalMediaStorage() mediaStorage {
}
func (l *localMediaStorage) save(filename string, file io.Reader) (location string, err error) {
if err = os.MkdirAll(mediaFilePath, 0644); err != nil {
if err = os.MkdirAll(l.path, 0644); err != nil {
return "", err
}
newFile, err := os.Create(filepath.Join(mediaFilePath, filename))
newFile, err := os.Create(filepath.Join(l.path, filename))
if err != nil {
return "", err
}

30
mediaStorage_test.go

@ -0,0 +1,30 @@
package main
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_localMediaStorage(t *testing.T) {
testDir := t.TempDir()
l := &localMediaStorage{
mediaURL: "https://example.com",
path: testDir,
}
testFileContent := "This is a test"
loc, err := l.save("test.txt", strings.NewReader(testFileContent))
require.Nil(t, err)
assert.Equal(t, "https://example.com/test.txt", loc)
file, err := os.ReadFile(filepath.Join(testDir, "test.txt"))
require.Nil(t, err)
assert.Equal(t, testFileContent, string(file))
}

238
micropub.go

@ -3,7 +3,8 @@ package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"mime"
"net/http"
"net/url"
"reflect"
@ -45,7 +46,7 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
mf = a.toMfItem(p)
mf = a.postToMfItem(p)
} else {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
@ -59,7 +60,7 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
}
list := map[string][]*microformatItem{}
for _, p := range posts {
list["items"] = append(list["items"], a.toMfItem(p))
list["items"] = append(list["items"], a.postToMfItem(p))
}
mf = list
}
@ -88,138 +89,73 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
}
}
func (a *goBlog) toMfItem(p *post) *microformatItem {
params := p.Parameters
params["path"] = []string{p.Path}
params["section"] = []string{p.Section}
params["blog"] = []string{p.Blog}
params["published"] = []string{p.Published}
params["updated"] = []string{p.Updated}
params["status"] = []string{string(p.Status)}
pb, _ := yaml.Marshal(p.Parameters)
content := fmt.Sprintf("---\n%s---\n%s", string(pb), p.Content)
return &microformatItem{
Type: []string{"h-entry"},
Properties: &microformatProperties{
Name: p.Parameters["title"],
Published: []string{p.Published},
Updated: []string{p.Updated},
PostStatus: []string{string(p.Status)},
Category: p.Parameters[a.cfg.Micropub.CategoryParam],
Content: []string{content},
URL: []string{a.fullPostURL(p)},
InReplyTo: p.Parameters[a.cfg.Micropub.ReplyParam],
LikeOf: p.Parameters[a.cfg.Micropub.LikeParam],
BookmarkOf: p.Parameters[a.cfg.Micropub.BookmarkParam],
MpSlug: []string{p.Slug},
Audio: p.Parameters[a.cfg.Micropub.AudioParam],
// TODO: Photos
},
}
}
func (a *goBlog) serveMicropubPost(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
var p *post
if ct := r.Header.Get(contentType); strings.Contains(ct, contenttype.WWWForm) || strings.Contains(ct, contenttype.MultipartForm) {
var err error
if strings.Contains(ct, contenttype.MultipartForm) {
err = r.ParseMultipartForm(0)
} else {
err = r.ParseForm()
}
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
switch mt, _, _ := mime.ParseMediaType(r.Header.Get(contentType)); mt {
case contenttype.WWWForm, contenttype.MultipartForm:
_ = r.ParseForm()
_ = r.ParseMultipartForm(0)
if r.Form == nil {
a.serveError(w, r, "Failed to parse form", http.StatusBadRequest)
return
}
if action := micropubAction(r.Form.Get("action")); action != "" {
u, err := url.Parse(r.Form.Get("url"))
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
switch action {
case actionDelete:
a.micropubDelete(w, r, r.Form.Get("url"))
default:
a.serveError(w, r, "Action not supported", http.StatusNotImplemented)
}
if action == actionDelete {
a.micropubDelete(w, r, u)
return
}
a.serveError(w, r, "Action not supported", http.StatusNotImplemented)
return
}
p, err = a.convertMPValueMapToPost(r.Form)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
} else if strings.Contains(ct, contenttype.JSON) {
a.micropubCreatePostFromForm(w, r)
case contenttype.JSON:
parsedMfItem := &microformatItem{}
err := json.NewDecoder(r.Body).Decode(parsedMfItem)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
b, _ := io.ReadAll(io.LimitReader(r.Body, 10000000)) // 10 MB
if err := json.Unmarshal(b, parsedMfItem); err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
if parsedMfItem.Action != "" {
u, err := url.Parse(parsedMfItem.URL)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
if parsedMfItem.Action == actionDelete {
a.micropubDelete(w, r, u)
return
}
if parsedMfItem.Action == actionUpdate {
a.micropubUpdate(w, r, u, parsedMfItem)
return
switch parsedMfItem.Action {
case actionDelete:
a.micropubDelete(w, r, parsedMfItem.URL)
case actionUpdate:
a.micropubUpdate(w, r, parsedMfItem.URL, parsedMfItem)
default:
a.serveError(w, r, "Action not supported", http.StatusNotImplemented)
}
a.serveError(w, r, "Action not supported", http.StatusNotImplemented)
return
}
p, err = a.convertMPMfToPost(parsedMfItem)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
} else {
a.micropubCreatePostFromJson(w, r, parsedMfItem)
default:
a.serveError(w, r, "wrong content type", http.StatusBadRequest)
return
}
if !strings.Contains(r.Context().Value(indieAuthScope).(string), "create") {
a.serveError(w, r, "create scope missing", http.StatusForbidden)
return
}
err := a.createPost(p)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, a.fullPostURL(p), http.StatusAccepted)
}
func (a *goBlog) convertMPValueMapToPost(values map[string][]string) (*post, error) {
func (a *goBlog) micropubParseValuePostParamsValueMap(entry *post, values map[string][]string) error {
if h, ok := values["h"]; ok && (len(h) != 1 || h[0] != "entry") {
return nil, errors.New("only entry type is supported so far")
return errors.New("only entry type is supported so far")
}
delete(values, "h")
entry := &post{
Parameters: map[string][]string{},
}
if content, ok := values["content"]; ok {
entry.Parameters = map[string][]string{}
if content, ok := values["content"]; ok && len(content) > 0 {
entry.Content = content[0]
delete(values, "content")
}
if published, ok := values["published"]; ok {
if published, ok := values["published"]; ok && len(published) > 0 {
entry.Published = published[0]
delete(values, "published")
}
if updated, ok := values["updated"]; ok {
if updated, ok := values["updated"]; ok && len(updated) > 0 {
entry.Updated = updated[0]
delete(values, "updated")
}
if status, ok := values["post-status"]; ok {
if status, ok := values["post-status"]; ok && len(status) > 0 {
entry.Status = postStatus(status[0])
delete(values, "post-status")
}
if slug, ok := values["mp-slug"]; ok {
if slug, ok := values["mp-slug"]; ok && len(slug) > 0 {
entry.Slug = slug[0]
delete(values, "mp-slug")
}
@ -275,11 +211,7 @@ func (a *goBlog) convertMPValueMapToPost(values map[string][]string) (*post, err
for n, p := range values {
entry.Parameters[n] = append(entry.Parameters[n], p...)
}
err := a.computeExtraPostParameters(entry)
if err != nil {
return nil, err
}
return entry, nil
return nil
}
type micropubAction string
@ -315,40 +247,44 @@ type microformatProperties struct {
Audio []string `json:"audio,omitempty"`
}
func (a *goBlog) convertMPMfToPost(mf *microformatItem) (*post, error) {
func (a *goBlog) micropubParsePostParamsMfItem(entry *post, mf *microformatItem) error {
if len(mf.Type) != 1 || mf.Type[0] != "h-entry" {
return nil, errors.New("only entry type is supported so far")
return errors.New("only entry type is supported so far")
}
entry := &post{
Parameters: map[string][]string{},
entry.Parameters = map[string][]string{}
if mf.Properties == nil {
return nil
}
// Content
if mf.Properties != nil && len(mf.Properties.Content) == 1 && len(mf.Properties.Content[0]) > 0 {
if len(mf.Properties.Content) > 0 && mf.Properties.Content[0] != "" {
entry.Content = mf.Properties.Content[0]
}
if len(mf.Properties.Published) == 1 {
if len(mf.Properties.Published) > 0 {
entry.Published = mf.Properties.Published[0]
}
if len(mf.Properties.Updated) == 1 {
if len(mf.Properties.Updated) > 0 {
entry.Updated = mf.Properties.Updated[0]
}
if len(mf.Properties.PostStatus) == 1 {
if len(mf.Properties.PostStatus) > 0 {
entry.Status = postStatus(mf.Properties.PostStatus[0])
}
if len(mf.Properties.MpSlug) > 0 {
entry.Slug = mf.Properties.MpSlug[0]
}
// Parameter
if len(mf.Properties.Name) == 1 {
if len(mf.Properties.Name) > 0 {
entry.Parameters["title"] = mf.Properties.Name
}
if len(mf.Properties.Category) > 0 {
entry.Parameters[a.cfg.Micropub.CategoryParam] = mf.Properties.Category
}
if len(mf.Properties.InReplyTo) == 1 {
if len(mf.Properties.InReplyTo) > 0 {
entry.Parameters[a.cfg.Micropub.ReplyParam] = mf.Properties.InReplyTo
}
if len(mf.Properties.LikeOf) == 1 {
if len(mf.Properties.LikeOf) > 0 {
entry.Parameters[a.cfg.Micropub.LikeParam] = mf.Properties.LikeOf
}
if len(mf.Properties.BookmarkOf) == 1 {
if len(mf.Properties.BookmarkOf) > 0 {
entry.Parameters[a.cfg.Micropub.BookmarkParam] = mf.Properties.BookmarkOf
}
if len(mf.Properties.Audio) > 0 {
@ -365,15 +301,7 @@ func (a *goBlog) convertMPMfToPost(mf *microformatItem) (*post, error) {
}
}
}
if len(mf.Properties.MpSlug) == 1 {
entry.Slug = mf.Properties.MpSlug[0]
}
err := a.computeExtraPostParameters(entry)
if err != nil {
return nil, err
}
return entry, nil
return nil
}
func (a *goBlog) computeExtraPostParameters(p *post) error {
@ -456,24 +384,70 @@ func (a *goBlog) computeExtraPostParameters(p *post) error {
return nil
}
func (a *goBlog) micropubDelete(w http.ResponseWriter, r *http.Request, u *url.URL) {
func (a *goBlog) micropubCreatePostFromForm(w http.ResponseWriter, r *http.Request) {
p := &post{}
err := a.micropubParseValuePostParamsValueMap(p, r.Form)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
a.micropubCreate(w, r, p)
}
func (a *goBlog) micropubCreatePostFromJson(w http.ResponseWriter, r *http.Request, parsedMfItem *microformatItem) {
p := &post{}
err := a.micropubParsePostParamsMfItem(p, parsedMfItem)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
a.micropubCreate(w, r, p)
}
func (a *goBlog) micropubCreate(w http.ResponseWriter, r *http.Request, p *post) {
if !strings.Contains(r.Context().Value(indieAuthScope).(string), "create") {
a.serveError(w, r, "create scope missing", http.StatusForbidden)
return
}
if err := a.computeExtraPostParameters(p); err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
if err := a.createPost(p); err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
http.Redirect(w, r, a.fullPostURL(p), http.StatusAccepted)
}
func (a *goBlog) micropubDelete(w http.ResponseWriter, r *http.Request, u string) {
if !strings.Contains(r.Context().Value(indieAuthScope).(string), "delete") {
a.serveError(w, r, "delete scope missing", http.StatusForbidden)
return
}
if err := a.deletePost(u.Path); err != nil {
uu, err := url.Parse(u)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
if err := a.deletePost(uu.Path); err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
http.Redirect(w, r, u.String(), http.StatusNoContent)
http.Redirect(w, r, uu.String(), http.StatusNoContent)
}
func (a *goBlog) micropubUpdate(w http.ResponseWriter, r *http.Request, u *url.URL, mf *microformatItem) {
func (a *goBlog) micropubUpdate(w http.ResponseWriter, r *http.Request, u string, mf *microformatItem) {
if !strings.Contains(r.Context().Value(indieAuthScope).(string), "update") {
a.serveError(w, r, "update scope missing", http.StatusForbidden)
return
}
p, err := a.db.getPost(u.Path)
uu, err := url.Parse(u)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
p, err := a.db.getPost(uu.Path)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return

32
postsFuncs.go

@ -1,6 +1,7 @@
package main
import (
"fmt"
"html/template"
"log"
"strings"
@ -9,6 +10,7 @@ import (
gogeouri "git.jlel.se/jlelse/go-geouri"
"github.com/PuerkitoBio/goquery"
"github.com/araddon/dateparse"
"gopkg.in/yaml.v3"
)
func (a *goBlog) fullPostURL(p *post) string {
@ -117,6 +119,36 @@ func (p *post) isPublishedSectionPost() bool {
return p.Published != "" && p.Section != "" && p.Status == statusPublished
}
func (a *goBlog) postToMfItem(p *post) *microformatItem {
params := p.Parameters
params["path"] = []string{p.Path}
params["section"] = []string{p.Section}
params["blog"] = []string{p.Blog}
params["published"] = []string{p.Published}
params["updated"] = []string{p.Updated}
params["status"] = []string{string(p.Status)}
pb, _ := yaml.Marshal(p.Parameters)
content := fmt.Sprintf("---\n%s---\n%s", string(pb), p.Content)
return &microformatItem{
Type: []string{"h-entry"},
Properties: &microformatProperties{
Name: p.Parameters["title"],
Published: []string{p.Published},
Updated: []string{p.Updated},
PostStatus: []string{string(p.Status)},
Category: p.Parameters[a.cfg.Micropub.CategoryParam],
Content: []string{content},
URL: []string{a.fullPostURL(p)},
InReplyTo: p.Parameters[a.cfg.Micropub.ReplyParam],
LikeOf: p.Parameters[a.cfg.Micropub.LikeParam],
BookmarkOf: p.Parameters[a.cfg.Micropub.BookmarkParam],
MpSlug: []string{p.Slug},
Audio: p.Parameters[a.cfg.Micropub.AudioParam],
// TODO: Photos
},
}
}
// Public because of rendering
func (p *post) Title() string {

52
render.go

@ -112,12 +112,34 @@ func (a *goBlog) render(w http.ResponseWriter, r *http.Request, template string,
func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, statusCode int, template string, data *renderData) {
// Server timing
t := servertiming.FromContext(r.Context()).NewMetric("r").Start()
defer t.Stop()
// Check render data
a.checkRenderData(r, data)
// Set content type
w.Header().Set(contentType, contenttype.HTMLUTF8)
// Minify and write response
var tw bytes.Buffer
err := a.templates[template].ExecuteTemplate(&tw, template, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(statusCode)
_, err = a.min.Write(w, contenttype.HTML, tw.Bytes())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (a *goBlog) checkRenderData(r *http.Request, data *renderData) {
// User
if data.User == nil {
data.User = a.cfg.User
}
// Blog
if data.Blog == nil {
if len(data.BlogString) == 0 {
if data.BlogString == "" {
data.BlogString = a.cfg.DefaultBlog
}
data.Blog = a.cfg.Blogs[data.BlogString]
@ -130,11 +152,12 @@ func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, st
}
}
}
// Tor
if a.cfg.Server.Tor && a.torAddress != "" {
data.TorAddress = fmt.Sprintf("http://%v%v", a.torAddress, r.RequestURI)
}
if data.Data == nil {
data.Data = map[string]interface{}{}
if torUsed, ok := r.Context().Value(torUsedKey).(bool); ok && torUsed {
data.TorUsed = true
}
// Check login
if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok && loggedIn {
@ -144,27 +167,10 @@ func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, st
data.CommentsEnabled = data.Blog.Comments != nil && data.Blog.Comments.Enabled
// Check if able to receive webmentions
data.WebmentionReceivingEnabled = a.cfg.Webmention == nil || !a.cfg.Webmention.DisableReceiving
// Check if Tor request
if torUsed, ok := r.Context().Value(torUsedKey).(bool); ok && torUsed {
data.TorUsed = true
}
// Set content type
w.Header().Set(contentType, contenttype.HTMLUTF8)
// Minify and write response
var tw bytes.Buffer
err := a.templates[template].ExecuteTemplate(&tw, template, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(statusCode)
_, err = a.min.Write(w, contenttype.HTML, tw.Bytes())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
// Data
if data.Data == nil {
data.Data = map[string]interface{}{}
}
// Server timing
t.Stop()
}
func (a *goBlog) includeRenderedTemplate(templateName string, data ...interface{}) (template.HTML, error) {

6
templates/editor.gohtml

@ -50,16 +50,18 @@ tags:
<input class="fw" type="file" name="file">
<input type="submit" value="{{ string .Blog.Lang "upload" }}">
</form>
{{ if .Data.Drafts }}
<h2>{{ string .Blog.Lang "drafts" }}</h2>
<form class="fw-form p" method="post">
<input type="hidden" name="editoraction" value="loadupdate">
<input type="hidden" name="editoraction" value="viewdraft">
<select name="url" class="fw">
{{ range $i, $draft := .Data.Drafts }}
<option value="{{ absolute $draft.Path }}">{{ with ($draft.Title) }}{{ . }}{{ else }}{{ $draft.Path }}{{ end }}</option>
{{ end }}
</select>
<input type="submit" value="{{ string .Blog.Lang "update" }}">
<input type="submit" value="{{ string .Blog.Lang "view" }}">
</form>
{{ end }}
<h2>{{ string .Blog.Lang "location" }}</h2>
<form class="fw-form p">
<input id="geobtn" type="button" class="fw" value="{{ string .Blog.Lang "locationget" }}" data-failed="{{ string .Blog.Lang "locationfailed" }}" data-notsupported="{{ string .Blog.Lang "locationnotsupported" }}">

Loading…
Cancel
Save