Vanity import path and listing of media files

This commit is contained in:
Jan-Lukas Else 2021-06-28 22:17:18 +02:00
parent 1e0b97b4d4
commit 2437ed70d7
37 changed files with 303 additions and 57 deletions

2
.vscode/tasks.json vendored
View File

@ -4,7 +4,7 @@
{
"label": "Build",
"type": "shell",
"command": "go build",
"command": "go build -o GoBlog",
"options": {
"env": {
"GOFLAGS": "-tags=linux,libsqlite3,sqlite_fts5"

View File

@ -15,10 +15,10 @@ import (
"strings"
"time"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/go-chi/chi/v5"
"github.com/go-fed/httpsig"
"github.com/spf13/cast"
"go.goblog.app/app/pkgs/contenttype"
)
func (a *goBlog) initActivityPub() error {

View File

@ -12,7 +12,7 @@ import (
"net/url"
"time"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"go.goblog.app/app/pkgs/contenttype"
)
type apRequest struct {

View File

@ -8,9 +8,9 @@ import (
"fmt"
"net/http"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/araddon/dateparse"
ct "github.com/elnormous/contenttype"
"go.goblog.app/app/pkgs/contenttype"
)
const asContext = "https://www.w3.org/ns/activitystreams"

2
app.go
View File

@ -6,7 +6,6 @@ import (
"net/http"
"sync"
"git.jlel.se/jlelse/GoBlog/pkgs/minify"
shutdowner "git.jlel.se/jlelse/go-shutdowner"
ts "git.jlel.se/jlelse/template-strings"
ct "github.com/elnormous/contenttype"
@ -14,6 +13,7 @@ import (
"github.com/go-fed/httpsig"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/yuin/goldmark"
"go.goblog.app/app/pkgs/minify"
"golang.org/x/sync/singleflight"
)

View File

@ -8,8 +8,8 @@ import (
"io"
"net/http"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/pquerna/otp/totp"
"go.goblog.app/app/pkgs/contenttype"
)
func (a *goBlog) checkCredentials(username, password, totpPasscode string) bool {

View File

@ -10,10 +10,10 @@ import (
"strings"
"time"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/kaorimatz/go-opml"
servertiming "github.com/mitchellh/go-server-timing"
"github.com/thoas/go-funk"
"go.goblog.app/app/pkgs/contenttype"
)
func (a *goBlog) serveBlogroll(w http.ResponseWriter, r *http.Request) {

View File

@ -7,8 +7,8 @@ import (
"io"
"net/http"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/dchest/captcha"
"go.goblog.app/app/pkgs/contenttype"
)
func (a *goBlog) captchaMiddleware(next http.Handler) http.Handler {

View File

@ -6,8 +6,8 @@ import (
"net/http/httptest"
"testing"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/stretchr/testify/assert"
"go.goblog.app/app/pkgs/contenttype"
)
func Test_captchaMiddleware(t *testing.T) {

View File

@ -8,7 +8,7 @@ import (
"net/http/httptest"
"net/url"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"go.goblog.app/app/pkgs/contenttype"
)
const editorPath = "/editor"

48
editorFiles.go Normal file
View File

@ -0,0 +1,48 @@
package main
import (
"net/http"
"sort"
)
func (a *goBlog) serveEditorFiles(w http.ResponseWriter, r *http.Request) {
files, err := a.mediaFiles()
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
// Sort files time desc
sort.Slice(files, func(i, j int) bool {
return files[i].Time.After(files[j].Time)
})
// Serve HTML
blog := r.Context().Value(blogContextKey).(string)
a.render(w, r, templateEditorFiles, &renderData{
BlogString: blog,
Data: map[string]interface{}{
"Files": files,
},
})
}
func (a *goBlog) serveEditorFilesView(w http.ResponseWriter, r *http.Request) {
filename := r.FormValue("filename")
if filename == "" {
a.serveError(w, r, "No file selected", http.StatusBadRequest)
return
}
http.Redirect(w, r, a.mediaFileLocation(filename), http.StatusFound)
}
func (a *goBlog) serveEditorFilesDelete(w http.ResponseWriter, r *http.Request) {
filename := r.FormValue("filename")
if filename == "" {
a.serveError(w, r, "No file selected", http.StatusBadRequest)
return
}
if err := a.deleteMediaFile(filename); err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, a.getRelativePath(r.Context().Value(blogContextKey).(string), "/editor/files"), http.StatusFound)
}

View File

@ -4,8 +4,8 @@ import (
"fmt"
"net/http"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
ct "github.com/elnormous/contenttype"
"go.goblog.app/app/pkgs/contenttype"
)
type errorData struct {

View File

@ -6,8 +6,8 @@ import (
"net/http/httptest"
"testing"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/stretchr/testify/assert"
"go.goblog.app/app/pkgs/contenttype"
)
func Test_errors(t *testing.T) {

View File

@ -5,9 +5,9 @@ import (
"strings"
"time"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/araddon/dateparse"
"github.com/gorilla/feeds"
"go.goblog.app/app/pkgs/contenttype"
)
type feedType string

10
go.mod
View File

@ -1,4 +1,4 @@
module git.jlel.se/jlelse/GoBlog
module go.goblog.app/app
go 1.16
@ -11,6 +11,7 @@ require (
github.com/andybalholm/cascadia v1.2.0 // indirect
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/boombuler/barcode v1.0.1 // indirect
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2
github.com/caddyserver/certmagic v0.14.0
github.com/cretz/bine v0.2.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
@ -45,17 +46,18 @@ require (
github.com/smartystreets/assertions v1.2.0 // indirect
github.com/snabb/sitemap v1.0.0
github.com/spf13/cast v1.3.1
github.com/spf13/viper v1.8.0
github.com/spf13/viper v1.8.1
github.com/stretchr/testify v1.7.0
github.com/tdewolff/minify/v2 v2.9.17
github.com/tdewolff/minify/v2 v2.9.18
github.com/thoas/go-funk v0.8.0
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2
github.com/yuin/goldmark v1.3.8
github.com/yuin/goldmark v1.3.9
// master
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
go.uber.org/atomic v1.8.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.18.1 // indirect
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c

26
go.sum
View File

@ -63,12 +63,16 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2 h1:t8KYCwSKsOEZBFELI4Pn/phbp38iJ1RRAkDFNin1aak=
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/caddyserver/certmagic v0.14.0 h1:XW1o32s7smIYEJSc6g+N8YXljpjRo5ZE2zi3CIYTs74=
github.com/caddyserver/certmagic v0.14.0/go.mod h1:oRQOZmUVKwlpgNidslysHt05osM9uMrJ4YMk+Ot4P4Q=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -363,8 +367,8 @@ github.com/spf13/pflag v1.0.1-0.20170901120850-7aff26db30c1/go.mod h1:DYY7MBk1bd
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/spf13/viper v1.8.0 h1:QRwDgoG8xX+kp69di68D+YYTCWfYEckbZRfUlEIAal0=
github.com/spf13/viper v1.8.0/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@ -375,8 +379,10 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tdewolff/minify/v2 v2.9.17 h1:0RPKCBSz5plIKZkmkm/zeQdqPYf/9NJVwG63NHtViHQ=
github.com/tdewolff/minify/v2 v2.9.17/go.mod h1:OLHZpngMfp36EyqxkGGta1l3hB1c+sHhXNHk8WTrsQo=
github.com/tdewolff/minify/v2 v2.9.18 h1:j5Is0sOGp4cxm0o3HgvHCWCvTtmKnfB0qv0FCRbmgZY=
github.com/tdewolff/minify/v2 v2.9.18/go.mod h1:0y0mXZnisZm8HcgQvAV0btxa1IgecGam90zMuHqEZuc=
github.com/tdewolff/parse/v2 v2.5.18 h1:d67Ql/Pe36JcJZ7J2MY8upx6iTxbxGS9lzwyFGtMmd0=
github.com/tdewolff/parse/v2 v2.5.18/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/thoas/go-funk v0.8.0 h1:JP9tKSvnpFVclYgDM0Is7FD9M4fhPvqA0s0BsXmzSRQ=
@ -391,8 +397,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.3.8 h1:Nw158Q8QN+CPgTmVRByhVwapp8Mm1e2blinhmx4wx5E=
github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.3.9 h1:XsVHmzm4P6g84IBbAj+WYMF/IEZ3J9+3I1wlqCNa/SQ=
github.com/yuin/goldmark v1.3.9/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38 h1:XZjLcLoTPNZuxppY3gwhRqo/T2XF6JMGFFdkAjX3w1w=
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
@ -409,14 +415,17 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.8.0 h1:CUhrE4N1rqSE6FM9ecihEjRkLQu8cDfgDyoOs83mEY4=
go.uber.org/atomic v1.8.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -450,6 +459,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@ -610,6 +620,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -644,6 +655,7 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -194,6 +194,9 @@ func (a *goBlog) buildStaticHandlersRouters() error {
a.editorRouter.Use(a.authMiddleware)
a.editorRouter.Get("/", a.serveEditor)
a.editorRouter.Post("/", a.serveEditorPost)
a.editorRouter.Get("/files", a.serveEditorFiles)
a.editorRouter.Post("/files/view", a.serveEditorFilesView)
a.editorRouter.Post("/files/delete", a.serveEditorFilesDelete)
a.commentsRouter = chi.NewRouter()
a.commentsRouter.Use(a.privateModeHandler...)

View File

@ -11,8 +11,8 @@ import (
"strings"
"time"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/spf13/cast"
"go.goblog.app/app/pkgs/contenttype"
)
// https://www.w3.org/TR/indieauth/

View File

@ -9,7 +9,7 @@ import (
"net/http"
"os"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"go.goblog.app/app/pkgs/contenttype"
)
const defaultCompressionWidth = 2000

View File

@ -12,9 +12,7 @@ import (
"github.com/jlaffaye/ftp"
)
type mediaStorageSaveFunc func(filename string, file io.Reader) (location string, err error)
func (a *goBlog) saveMediaFile(filename string, f io.Reader) (string, error) {
func (a *goBlog) initMediaStorage() {
a.mediaStorageInit.Do(func() {
type initFunc func() mediaStorage
for _, fc := range []initFunc{a.initBunnyCdnMediaStorage, a.initFtpMediaStorage, a.initLocalMediaStorage} {
@ -24,6 +22,12 @@ func (a *goBlog) saveMediaFile(filename string, f io.Reader) (string, error) {
}
}
})
}
type mediaStorageSaveFunc func(filename string, file io.Reader) (location string, err error)
func (a *goBlog) saveMediaFile(filename string, f io.Reader) (string, error) {
a.initMediaStorage()
if a.mediaStorage == nil {
return "", errors.New("no media storage configured")
}
@ -34,8 +38,42 @@ func (a *goBlog) saveMediaFile(filename string, f io.Reader) (string, error) {
return a.getFullAddress(loc), nil
}
func (a *goBlog) deleteMediaFile(filename string) error {
a.initMediaStorage()
if a.mediaStorage == nil {
return errors.New("no media storage configured")
}
return a.mediaStorage.delete(filepath.Base(filename))
}
type mediaFile struct {
Name string
Location string
Time time.Time
Size int64
}
func (a *goBlog) mediaFiles() ([]*mediaFile, error) {
a.initMediaStorage()
if a.mediaStorage == nil {
return nil, errors.New("no media storage configured")
}
return a.mediaStorage.files()
}
func (a *goBlog) mediaFileLocation(name string) string {
a.initMediaStorage()
if a.mediaStorage == nil {
return ""
}
return a.mediaStorage.location(name)
}
type mediaStorage interface {
save(filename string, file io.Reader) (location string, err error)
delete(filename string) (err error)
files() (files []*mediaFile, err error)
location(filename string) (location string)
}
type localMediaStorage struct {
@ -64,10 +102,46 @@ func (l *localMediaStorage) save(filename string, file io.Reader) (location stri
if _, err = io.Copy(newFile, file); err != nil {
return "", err
}
if l.mediaURL != "" {
return fmt.Sprintf("%s/%s", l.mediaURL, filename), nil
return l.location(filename), nil
}
return fmt.Sprintf("/m/%s", filename), nil
func (l *localMediaStorage) delete(filename string) (err error) {
if err = os.MkdirAll(l.path, 0644); err != nil {
return err
}
return os.Remove(filepath.Join(l.path, filename))
}
func (l *localMediaStorage) files() (files []*mediaFile, err error) {
if err = os.MkdirAll(l.path, 0644); err != nil {
return nil, err
}
entries, err := os.ReadDir(l.path)
if err != nil {
return nil, err
}
for _, e := range entries {
fi, er := e.Info()
if er != nil {
continue
}
if fi.Mode().IsRegular() {
files = append(files, &mediaFile{
Name: fi.Name(),
Location: l.location(fi.Name()),
Time: fi.ModTime(),
Size: int64(fi.Size()),
})
}
}
return files, nil
}
func (l *localMediaStorage) location(name string) string {
if l.mediaURL != "" {
return fmt.Sprintf("%s/%s", l.mediaURL, name)
}
return fmt.Sprintf("/m/%s", name)
}
func (a *goBlog) initBunnyCdnMediaStorage() mediaStorage {
@ -108,21 +182,72 @@ func (a *goBlog) initFtpMediaStorage() mediaStorage {
}
func (f *ftpMediaStorage) save(filename string, file io.Reader) (location string, err error) {
if f.address == "" || f.user == "" || f.password == "" {
return "", errors.New("missing FTP config")
}
c, err := ftp.Dial(f.address, ftp.DialWithTimeout(5*time.Second))
if err != nil {
return "", err
}
c, err := f.connection()
defer func() {
if c != nil {
_ = c.Quit()
}
}()
if err = c.Login(f.user, f.password); err != nil {
if err != nil {
return "", err
}
if err = c.Stor(filename, file); err != nil {
return "", err
}
return fmt.Sprintf("%s/%s", f.mediaURL, filename), nil
return f.location(filename), nil
}
func (f *ftpMediaStorage) delete(filename string) (err error) {
c, err := f.connection()
defer func() {
if c != nil {
_ = c.Quit()
}
}()
if err != nil {
return err
}
return c.Delete(filename)
}
func (f *ftpMediaStorage) files() (files []*mediaFile, err error) {
c, err := f.connection()
defer func() {
if c != nil {
_ = c.Quit()
}
}()
if err != nil {
return nil, err
}
w := c.Walk("")
for w.Next() {
if s := w.Stat(); s.Type == ftp.EntryTypeFile {
files = append(files, &mediaFile{
Name: s.Name,
Location: f.location(s.Name),
Time: s.Time,
Size: int64(s.Size),
})
}
}
return files, nil
}
func (f *ftpMediaStorage) location(name string) string {
return fmt.Sprintf("%s/%s", f.mediaURL, name)
}
func (f *ftpMediaStorage) connection() (*ftp.ServerConn, error) {
if f.address == "" || f.user == "" || f.password == "" {
return nil, errors.New("missing FTP config")
}
c, err := ftp.Dial(f.address, ftp.DialWithTimeout(5*time.Second))
if err != nil {
return nil, err
}
if err = c.Login(f.user, f.password); err != nil {
return nil, err
}
return c, nil
}

View File

@ -13,8 +13,8 @@ import (
"strings"
"time"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/spf13/cast"
"go.goblog.app/app/pkgs/contenttype"
"gopkg.in/yaml.v3"
)

View File

@ -6,7 +6,7 @@ import (
"path/filepath"
"strings"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"go.goblog.app/app/pkgs/contenttype"
)
const micropubMediaSubPath = "/media"

View File

@ -4,7 +4,7 @@ import (
"encoding/json"
"net/http"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"go.goblog.app/app/pkgs/contenttype"
)
func (a *goBlog) serveNodeInfoDiscover(w http.ResponseWriter, r *http.Request) {
@ -28,7 +28,7 @@ func (a *goBlog) serveNodeInfo(w http.ResponseWriter, r *http.Request) {
"version": "2.1",
"software": map[string]interface{}{
"name": "goblog",
"repository": "https://git.jlel.se/jlelse/GoBlog",
"repository": "https://go.goblog.app/app",
},
"usage": map[string]interface{}{
"users": map[string]interface{}{

View File

@ -4,7 +4,7 @@ import (
"fmt"
"net/http"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"go.goblog.app/app/pkgs/contenttype"
)
func (a *goBlog) serveOpenSearch(w http.ResponseWriter, r *http.Request) {

View File

@ -118,7 +118,7 @@ form {
.fw-form {
@extend .fw;
input:not([type]), input[type="submit"], input[type="button"], input[type="text"], input[type="email"], input[type="url"], input[type="password"], textarea {
input:not([type]), input[type="submit"], input[type="button"], input[type="text"], input[type="email"], input[type="url"], input[type="password"], input[type="file"], textarea, select {
@extend .fw;
}
}

View File

@ -4,13 +4,13 @@ import (
"io"
"sync"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/tdewolff/minify/v2"
mCss "github.com/tdewolff/minify/v2/css"
mHtml "github.com/tdewolff/minify/v2/html"
mJs "github.com/tdewolff/minify/v2/js"
mJson "github.com/tdewolff/minify/v2/json"
mXml "github.com/tdewolff/minify/v2/xml"
"go.goblog.app/app/pkgs/contenttype"
)
type Minifier struct {

View File

@ -11,8 +11,8 @@ import (
"path/filepath"
"strings"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
servertiming "github.com/mitchellh/go-server-timing"
"go.goblog.app/app/pkgs/contenttype"
)
const (
@ -28,6 +28,7 @@ const (
templateSummary = "summary"
templatePhotosSummary = "photosummary"
templateEditor = "editor"
templateEditorFiles = "editorfiles"
templateLogin = "login"
templateStaticHome = "statichome"
templateBlogStats = "blogstats"
@ -68,6 +69,7 @@ func (a *goBlog) initRendering() error {
"geotitle": a.geoTitle,
"geolink": geoOSMLink,
"opensearch": openSearchUrl,
"mbytes": mBytesString,
}
baseTemplate, err := template.New("base").Funcs(templateFunctions).ParseFiles(path.Join(templatesDir, templateBase+templatesExt))
if err != nil {

View File

@ -10,7 +10,7 @@ import (
"path/filepath"
"strings"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"go.goblog.app/app/pkgs/contenttype"
)
const assetsFolder = "templates/assets"

View File

@ -163,7 +163,7 @@ footer * {
display: inline;
}
.fw, .fw-form, .fw-form input:not([type]), .fw-form input[type=submit], .fw-form input[type=button], .fw-form input[type=text], .fw-form input[type=email], .fw-form input[type=url], .fw-form input[type=password], .fw-form textarea {
.fw, .fw-form, .fw-form input:not([type]), .fw-form input[type=submit], .fw-form input[type=button], .fw-form input[type=text], .fw-form input[type=email], .fw-form input[type=url], .fw-form input[type=password], .fw-form input[type=file], .fw-form textarea, .fw-form select {
width: 100%;
}

View File

@ -0,0 +1,16 @@
(function () {
Array.from(document.querySelectorAll('form input.confirm')).forEach(element => {
let showed = false
element.form.addEventListener('submit', event => {
if (!showed) {
event.preventDefault()
element.value = '…'
setTimeout(() => {
element.value = element.dataset.confirmmessage
showed = true
}, 1000)
return false
}
})
})
})()

View File

@ -47,14 +47,15 @@ tags:
<h2>{{ string .Blog.Lang "upload" }}</h2>
<form class="fw-form p" method="post" enctype="multipart/form-data">
<input type="hidden" name="editoraction" value="upload">
<input class="fw" type="file" name="file">
<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>
{{ if .Data.Drafts }}
<h2>{{ string .Blog.Lang "drafts" }}</h2>
<form class="fw-form p" method="post">
<input type="hidden" name="editoraction" value="viewdraft">
<select name="url" class="fw">
<select name="url">
{{ range $i, $draft := .Data.Drafts }}
<option value="{{ absolute $draft.Path }}">{{ with ($draft.Title) }}{{ . }}{{ else }}{{ $draft.Path }}{{ end }}</option>
{{ end }}
@ -64,7 +65,7 @@ tags:
{{ 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" }}">
<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>
<script defer src="{{ asset "js/geohelper.js" }}"></script>

View File

@ -0,0 +1,28 @@
{{ define "title" }}
<title>{{ string .Blog.Lang "mediafiles" }} - {{ .Blog.Title }}</title>
{{ end }}
{{ define "main" }}
<main>
{{ $blog := .Blog }}
<h2>{{ string .Blog.Lang "mediafiles" }}</h2>
{{ if .Data.Files }}
<form class="fw-form p" method="post">
<select name="filename">
{{ range $i, $file := .Data.Files }}
<option value="{{ $file.Name }}">{{ $file.Name }} ({{ isodate $file.Time.String }}, {{ mbytes $file.Size }})</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

@ -19,7 +19,9 @@ location: "Standort"
locationfailed: "Abfragen des Standorts fehlgeschlagen"
locationget: "Standort abfragen"
locationnotsupported: "Die Standort-API wird von diesem Browser nicht unterstützt"
mediafiles: "Medien-Dateien"
next: "Weiter"
nofiles: "Keine Dateien"
noposts: "Hier sind keine Posts."
oldcontent: "⚠️ Dieser Eintrag ist bereits über ein Jahr alt. Er ist möglicherweise nicht mehr aktuell. Meinungen können sich geändert haben."
posts: "Posts"

View File

@ -28,8 +28,10 @@ locationget: "Request location"
locationnotsupported: "The location API is not supported by this browser"
login: "Login"
logout: "Logout"
mediafiles: "Media files"
nameopt: "Name (optional)"
next: "Next"
nofiles: "No files"
noposts: "There are no posts here."
notifications: "Notifications"
oldcontent: "⚠️ This entry is already over one year old. It may no longer be up to date. Opinions may have changed."

View File

@ -15,6 +15,7 @@ import (
"github.com/PuerkitoBio/goquery"
"github.com/araddon/dateparse"
"github.com/c2h5oh/datasize"
"github.com/thoas/go-funk"
)
@ -226,3 +227,7 @@ func getSHA256(file io.ReadSeeker) (filename string, err error) {
}
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
func mBytesString(size int64) string {
return fmt.Sprintf("%.2f MB", datasize.ByteSize(size).MBytes())
}

View File

@ -9,7 +9,7 @@ import (
"strings"
"time"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"go.goblog.app/app/pkgs/contenttype"
)
type webmentionStatus string

View File

@ -8,10 +8,10 @@ import (
"net/url"
"strings"
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/PuerkitoBio/goquery"
"github.com/thoas/go-funk"
"github.com/tomnomnom/linkheader"
"go.goblog.app/app/pkgs/contenttype"
)
func (a *goBlog) sendWebmentions(p *post) error {