mirror of https://github.com/jlelse/GoBlog
Basic trips/gpx support
This commit is contained in:
parent
95233038f4
commit
1a6b1e6776
22
editor.go
22
editor.go
|
@ -143,7 +143,29 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
http.Redirect(w, r, post.Path, http.StatusFound)
|
||||
case "helpgpx":
|
||||
file, _, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
a.serveError(w, r, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
gpx, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
a.serveError(w, r, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var gpxBuffer bytes.Buffer
|
||||
_, _ = a.min.Write(&gpxBuffer, contenttype.XML, gpx)
|
||||
resultMap := map[string]string{
|
||||
"gpx": gpxBuffer.String(),
|
||||
}
|
||||
resultBytes, err := yaml.Marshal(resultMap)
|
||||
if err != nil {
|
||||
a.serveError(w, r, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.Header().Set(contentType, contenttype.TextUTF8)
|
||||
_, _ = w.Write(resultBytes)
|
||||
default:
|
||||
a.serveError(w, r, "Unknown editoraction", http.StatusBadRequest)
|
||||
}
|
||||
|
|
46
geo.go
46
geo.go
|
@ -1,9 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
|
@ -70,3 +73,46 @@ func (a *goBlog) photonReverse(lat, lon float64, lang string) ([]byte, error) {
|
|||
func geoOSMLink(g *gogeouri.Geo) string {
|
||||
return fmt.Sprintf("https://www.openstreetmap.org/?mlat=%v&mlon=%v", g.Latitude, g.Longitude)
|
||||
}
|
||||
|
||||
//go:embed leaflet/*
|
||||
var leafletFiles embed.FS
|
||||
|
||||
func (a *goBlog) proxyTiles(basePath string) http.HandlerFunc {
|
||||
osmUrl, _ := url.Parse("https://tile.openstreetmap.org/")
|
||||
tileProxy := http.StripPrefix(basePath, httputil.NewSingleHostReverseProxy(osmUrl))
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
proxyTarget := "https://tile.openstreetmap.org" + r.URL.Path
|
||||
proxyRequest, _ := http.NewRequest(http.MethodGet, proxyTarget, nil)
|
||||
// Copy request headers
|
||||
for _, k := range []string{
|
||||
"Accept-Encoding",
|
||||
"Accept-Language",
|
||||
"Accept",
|
||||
"Cache-Control",
|
||||
"If-Modified-Since",
|
||||
"If-None-Match",
|
||||
"User-Agent",
|
||||
} {
|
||||
proxyRequest.Header.Set(k, r.Header.Get(k))
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
tileProxy.ServeHTTP(rec, proxyRequest)
|
||||
res := rec.Result()
|
||||
// Copy result headers
|
||||
for _, k := range []string{
|
||||
"Accept-Ranges",
|
||||
"Access-Control-Allow-Origin",
|
||||
"Age",
|
||||
"Cache-Control",
|
||||
"Content-Length",
|
||||
"Content-Type",
|
||||
"Etag",
|
||||
"Expires",
|
||||
} {
|
||||
w.Header().Set(k, res.Header.Get(k))
|
||||
}
|
||||
w.WriteHeader(res.StatusCode)
|
||||
_, _ = io.Copy(w, res.Body)
|
||||
_ = res.Body.Close()
|
||||
}
|
||||
}
|
||||
|
|
49
geoMap.go
49
geoMap.go
|
@ -1,18 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
//go:embed leaflet/*
|
||||
var leafletFiles embed.FS
|
||||
|
||||
const defaultGeoMapPath = "/map"
|
||||
|
||||
func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -68,48 +60,7 @@ func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
|
|||
BlogString: blog,
|
||||
Canonical: a.getFullAddress(mapPath),
|
||||
Data: map[string]interface{}{
|
||||
"mappath": mapPath,
|
||||
"locations": string(jb),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (a *goBlog) proxyTiles(basePath string) http.HandlerFunc {
|
||||
osmUrl, _ := url.Parse("https://tile.openstreetmap.org/")
|
||||
tileProxy := http.StripPrefix(basePath, httputil.NewSingleHostReverseProxy(osmUrl))
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
proxyTarget := "https://tile.openstreetmap.org" + r.URL.Path
|
||||
proxyRequest, _ := http.NewRequest(http.MethodGet, proxyTarget, nil)
|
||||
// Copy request headers
|
||||
for _, k := range []string{
|
||||
"Accept-Encoding",
|
||||
"Accept-Language",
|
||||
"Accept",
|
||||
"Cache-Control",
|
||||
"If-Modified-Since",
|
||||
"If-None-Match",
|
||||
"User-Agent",
|
||||
} {
|
||||
proxyRequest.Header.Set(k, r.Header.Get(k))
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
tileProxy.ServeHTTP(rec, proxyRequest)
|
||||
res := rec.Result()
|
||||
// Copy result headers
|
||||
for _, k := range []string{
|
||||
"Accept-Ranges",
|
||||
"Access-Control-Allow-Origin",
|
||||
"Age",
|
||||
"Cache-Control",
|
||||
"Content-Length",
|
||||
"Content-Type",
|
||||
"Etag",
|
||||
"Expires",
|
||||
} {
|
||||
w.Header().Set(k, res.Header.Get(k))
|
||||
}
|
||||
w.WriteHeader(res.StatusCode)
|
||||
_, _ = io.Copy(w, res.Body)
|
||||
_ = res.Body.Close()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"math"
|
||||
|
||||
"github.com/tkrajina/gpxgo/gpx"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
)
|
||||
|
||||
func (p *post) HasTrip() bool {
|
||||
return p.firstParameter("gpx") != ""
|
||||
}
|
||||
|
||||
type tripRenderResult struct {
|
||||
HasPoints bool
|
||||
Paths [][]*tripPoint
|
||||
PathsJSON string
|
||||
Kilometers string
|
||||
Hours string
|
||||
Name string
|
||||
}
|
||||
|
||||
func (a *goBlog) renderTrip(p *post) (result *tripRenderResult, err error) {
|
||||
gpxString := p.firstParameter("gpx")
|
||||
if gpxString == "" {
|
||||
return nil, errors.New("no gpx parameter in post")
|
||||
}
|
||||
|
||||
// Parse GPX
|
||||
parseResult, err := tripParseGPX(gpxString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l, _ := language.Parse(a.cfg.Blogs[p.Blog].Lang)
|
||||
lp := message.NewPrinter(l)
|
||||
|
||||
pathsJSON, err := json.Marshal(parseResult.paths)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = &tripRenderResult{
|
||||
HasPoints: len(parseResult.paths) > 0 && len(parseResult.paths[0]) > 0,
|
||||
Paths: parseResult.paths,
|
||||
PathsJSON: string(pathsJSON),
|
||||
Name: parseResult.gpxData.Name,
|
||||
Kilometers: lp.Sprintf("%.2f", parseResult.md.MovingDistance/1000),
|
||||
Hours: lp.Sprintf(
|
||||
"%.0f:%2.0f:%2.0f",
|
||||
math.Floor(parseResult.md.MovingTime/3600), // Hours
|
||||
math.Floor(math.Mod(parseResult.md.MovingTime, 3600)/60), // Minutes
|
||||
math.Floor(math.Mod(parseResult.md.MovingTime, 60)), // Seconds
|
||||
),
|
||||
}
|
||||
|
||||
log.Println(string(pathsJSON))
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type tripPoint struct {
|
||||
Lat, Lon float64
|
||||
}
|
||||
|
||||
type tripParseResult struct {
|
||||
paths [][]*tripPoint
|
||||
gpxData *gpx.GPX
|
||||
md *gpx.MovingData
|
||||
}
|
||||
|
||||
func tripParseGPX(gpxString string) (result *tripParseResult, err error) {
|
||||
result = &tripParseResult{}
|
||||
|
||||
type tripPath struct {
|
||||
gpxMovingData *gpx.MovingData
|
||||
points []*tripPoint
|
||||
}
|
||||
|
||||
var paths []*tripPath
|
||||
|
||||
result.gpxData, err = gpx.ParseString(gpxString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, track := range result.gpxData.Tracks {
|
||||
for _, segment := range track.Segments {
|
||||
md := segment.MovingData()
|
||||
path := &tripPath{
|
||||
gpxMovingData: &md,
|
||||
}
|
||||
for _, point := range segment.Points {
|
||||
path.points = append(path.points, &tripPoint{
|
||||
Lat: point.GetLatitude(), Lon: point.GetLongitude(),
|
||||
})
|
||||
}
|
||||
paths = append(paths, path)
|
||||
}
|
||||
}
|
||||
|
||||
result.md = &gpx.MovingData{}
|
||||
for _, path := range paths {
|
||||
// Combine moving data
|
||||
result.md.MaxSpeed = math.Max(result.md.MaxSpeed, path.gpxMovingData.MaxSpeed)
|
||||
result.md.MovingDistance = result.md.MovingDistance + path.gpxMovingData.MovingDistance
|
||||
result.md.MovingTime = result.md.MovingTime + path.gpxMovingData.MovingTime
|
||||
result.md.StoppedDistance = result.md.StoppedDistance + path.gpxMovingData.StoppedDistance
|
||||
result.md.StoppedTime = result.md.StoppedTime + path.gpxMovingData.StoppedTime
|
||||
// Add points
|
||||
result.paths = append(result.paths, path.points)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_renderTrip(t *testing.T) {
|
||||
app := &goBlog{
|
||||
cfg: &config{
|
||||
Db: &configDb{
|
||||
File: filepath.Join(t.TempDir(), "test.db"),
|
||||
},
|
||||
Server: &configServer{},
|
||||
Blogs: map[string]*configBlog{
|
||||
"en": {
|
||||
Lang: "en",
|
||||
},
|
||||
"de": {
|
||||
Lang: "de",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_ = app.initDatabase(false)
|
||||
app.initComponents(false)
|
||||
|
||||
gpxBytes, _ := os.ReadFile("testdata/test.gpx")
|
||||
|
||||
post := &post{
|
||||
Blog: "en",
|
||||
Parameters: map[string][]string{
|
||||
"gpx": {
|
||||
string(gpxBytes),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resEn, err := app.renderTrip(post)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, resEn.HasPoints)
|
||||
assert.Equal(t, "2.70", resEn.Kilometers)
|
||||
assert.Equal(t, "0:42:53", resEn.Hours)
|
||||
|
||||
post.Blog = "de"
|
||||
|
||||
resDe, err := app.renderTrip(post)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, resDe.HasPoints)
|
||||
assert.Equal(t, "2,70", resDe.Kilometers)
|
||||
assert.Equal(t, "0:42:53", resDe.Hours)
|
||||
}
|
7
go.mod
7
go.mod
|
@ -41,14 +41,16 @@ require (
|
|||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tdewolff/minify/v2 v2.9.22
|
||||
github.com/thoas/go-funk v0.9.1
|
||||
github.com/tkrajina/gpxgo v1.1.2
|
||||
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.4.3
|
||||
// master
|
||||
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa
|
||||
golang.org/x/net v0.0.0-20211108170745-6635138e15ea
|
||||
golang.org/x/net v0.0.0-20211109214657-ef0fda0de508
|
||||
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
|
||||
tailscale.com v1.16.2
|
||||
// main
|
||||
|
@ -102,8 +104,7 @@ require (
|
|||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
|
||||
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
|
||||
golang.org/x/sys v0.0.0-20211109065445-02f5c0300f6e // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3 // indirect
|
||||
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210905140043-2ef39d47540c // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.4.10 // indirect
|
||||
|
|
12
go.sum
12
go.sum
|
@ -280,6 +280,7 @@ github.com/jlaffaye/ftp v0.0.0-20211029032751-b1140299f4df h1:nsRFf9ZkcalB12ZJZY
|
|||
github.com/jlaffaye/ftp v0.0.0-20211029032751-b1140299f4df/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
|
||||
github.com/jlelse/feeds v1.2.1-0.20210704161900-189f94254ad4 h1:d2oKwfgLl3ef0PyYDkzjsVyYlBZzNpOpXitDraOnVXc=
|
||||
github.com/jlelse/feeds v1.2.1-0.20210704161900-189f94254ad4/go.mod h1:vt0iOV52/wq97Ql/jp7mUkqyrlEiGQuhHic4bVoHy0c=
|
||||
github.com/joeshaw/gengen v0.0.0-20190604015154-c77d87825f5a/go.mod h1:v2qvRL8Xwk4OlARK6gPlf2JreZXzv0dYp/8+kUJ0y7Q=
|
||||
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
||||
|
@ -452,6 +453,8 @@ 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.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
|
||||
github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
|
||||
github.com/tkrajina/gpxgo v1.1.2 h1:il6rjS6IGm3yqa/yr7+fKBlF3ufWDEPZrYi/kxI1Jv0=
|
||||
github.com/tkrajina/gpxgo v1.1.2/go.mod h1:795sjVRFo5wWyN6oOZp0RYienGGBJjpAlgOz2nCngA0=
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
|
||||
|
@ -587,8 +590,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211108170745-6635138e15ea h1:FosBMXtOc8Tp9Hbo4ltl1WJSrTVewZU8MPnTPY2HdH8=
|
||||
golang.org/x/net v0.0.0-20211108170745-6635138e15ea/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211109214657-ef0fda0de508 h1:v3NKo+t/Kc3EASxaKZ82lwK6mCf4ZeObQBduYFZHo7c=
|
||||
golang.org/x/net v0.0.0-20211109214657-ef0fda0de508/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
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=
|
||||
|
@ -687,8 +690,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211109065445-02f5c0300f6e h1:i6Vklmyu+fZMFYpum+sR4ZWABGW7MyIxfJZXYvcnbns=
|
||||
golang.org/x/sys v0.0.0-20211109065445-02f5c0300f6e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3 h1:T6tyxxvHMj2L1R2kZg0uNMpS8ZhB9lRa9XRGTCSA65w=
|
||||
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
||||
|
@ -717,6 +720,7 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190603231351-8aaa1484dc10/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
|
|
3
http.go
3
http.go
|
@ -187,6 +187,9 @@ func (a *goBlog) buildRouter() (http.Handler, error) {
|
|||
// Media files
|
||||
r.Route("/m", a.mediaFilesRouter)
|
||||
|
||||
// Other routes
|
||||
r.Route("/x", a.xRouter)
|
||||
|
||||
// Captcha
|
||||
r.Handle("/captcha/*", captcha.Server(500, 250))
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ func (a *goBlog) securityHeaders(next http.Handler) http.Handler {
|
|||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
||||
w.Header().Set("X-Xss-Protection", "1; mode=block")
|
||||
w.Header().Set("Content-Security-Policy", "default-src 'self'"+cspDomains)
|
||||
w.Header().Set("Content-Security-Policy", "default-src 'self'"+cspDomains+"; img-src 'self' "+cspDomains+" data:")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -96,6 +96,13 @@ func (a *goBlog) mediaFilesRouter(r chi.Router) {
|
|||
r.Get(mediaFileRoute, a.serveMediaFile)
|
||||
}
|
||||
|
||||
// Various other routes
|
||||
func (a *goBlog) xRouter(r chi.Router) {
|
||||
r.Use(a.privateModeHandler)
|
||||
r.Get("/tiles/{z}/{x}/{y}.png", a.proxyTiles("/x/tiles"))
|
||||
r.With(cacheLoggedIn, a.cacheMiddleware).HandleFunc("/leaflet/*", a.serveFs(leafletFiles, "/x/"))
|
||||
}
|
||||
|
||||
// Blog
|
||||
func (a *goBlog) blogRouter(blog string, conf *configBlog) func(r chi.Router) {
|
||||
return func(r chi.Router) {
|
||||
|
@ -398,14 +405,7 @@ func (a *goBlog) blogGeoMapRouter(conf *configBlog) func(r chi.Router) {
|
|||
return func(r chi.Router) {
|
||||
if mc := conf.Map; mc != nil && mc.Enabled {
|
||||
mapPath := conf.getRelativePath(defaultIfEmpty(mc.Path, defaultGeoMapPath))
|
||||
r.Route(mapPath, func(r chi.Router) {
|
||||
r.Use(a.privateModeHandler)
|
||||
r.Group(func(r chi.Router) {
|
||||
r.With(a.cacheMiddleware).Get("/", a.serveGeoMap)
|
||||
r.With(cacheLoggedIn, a.cacheMiddleware).HandleFunc("/leaflet/*", a.serveFs(leafletFiles, mapPath+"/"))
|
||||
})
|
||||
r.Get("/tiles/{z}/{x}/{y}.png", a.proxyTiles(mapPath+"/tiles"))
|
||||
})
|
||||
r.With(a.privateModeHandler, a.cacheMiddleware).Get(mapPath, a.serveGeoMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,13 +15,15 @@ const (
|
|||
LDJSON = "application/ld+json"
|
||||
MultipartForm = "multipart/form-data"
|
||||
RSS = "application/rss+xml"
|
||||
Text = "text/plain"
|
||||
WWWForm = "application/x-www-form-urlencoded"
|
||||
XML = "text/xml"
|
||||
|
||||
ASUTF8 = AS + CharsetUtf8Suffix
|
||||
CSSUTF8 = CSS + CharsetUtf8Suffix
|
||||
HTMLUTF8 = HTML + CharsetUtf8Suffix
|
||||
JSUTF8 = JS + CharsetUtf8Suffix
|
||||
JSONUTF8 = JSON + CharsetUtf8Suffix
|
||||
JSUTF8 = JS + CharsetUtf8Suffix
|
||||
TextUTF8 = Text + CharsetUtf8Suffix
|
||||
XMLUTF8 = XML + CharsetUtf8Suffix
|
||||
)
|
||||
|
|
|
@ -61,6 +61,7 @@ func (a *goBlog) initRendering() error {
|
|||
"likelink": a.likeLink,
|
||||
"liketitle": a.likeTitle,
|
||||
"photolinks": a.photoLinks,
|
||||
"rendertrip": a.renderTrip,
|
||||
// Others
|
||||
"dateformat": dateFormat,
|
||||
"isodate": isoDateFormat,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
let map = L.map('map')
|
||||
|
||||
L.tileLayer(mapEl.dataset.tiles, {
|
||||
L.tileLayer("/x/tiles/{z}/{x}/{y}.png", {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}).addTo(map)
|
||||
|
||||
|
@ -16,7 +16,5 @@
|
|||
})
|
||||
markers.push(marker)
|
||||
})
|
||||
|
||||
map.fitBounds(markers)
|
||||
map.zoomOut(2, { animate: false })
|
||||
map.fitBounds(markers, { padding: [5, 5] })
|
||||
})()
|
|
@ -0,0 +1,22 @@
|
|||
(function () {
|
||||
let mapEl = document.getElementById('map')
|
||||
let paths = JSON.parse(mapEl.dataset.paths)
|
||||
|
||||
let map = L.map('map')
|
||||
|
||||
L.tileLayer("/x/tiles/{z}/{x}/{y}.png", {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}).addTo(map)
|
||||
|
||||
let polylines = []
|
||||
paths.forEach(path => {
|
||||
let pathPoints = []
|
||||
path.forEach(point => {
|
||||
pathPoints.push([point.Lat, point.Lon])
|
||||
})
|
||||
let pl = L.polyline(pathPoints, { color: 'blue' }).addTo(map)
|
||||
polylines.push(pl)
|
||||
})
|
||||
let fgb = L.featureGroup(polylines).getBounds()
|
||||
map.fitBounds(fgb, { padding: [5, 5] })
|
||||
})()
|
|
@ -44,6 +44,14 @@
|
|||
<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>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ mdtitle .Blog.Title }}</title>
|
||||
{{ if not .Data.nolocations }}
|
||||
<link rel="stylesheet" href="{{ .Data.mappath }}/leaflet/leaflet.css"/>
|
||||
<script src="{{ .Data.mappath }}/leaflet/leaflet.js"></script>
|
||||
<link rel="stylesheet" href="/x/leaflet/leaflet.css"/>
|
||||
<script src="/x/leaflet/leaflet.js"></script>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
|||
{{ if .Data.nolocations }}
|
||||
<p>{{ string .Blog.Lang "nolocations" }}</p>
|
||||
{{ else }}
|
||||
<div class="p" id="map" data-locations="{{ .Data.locations }}" data-tiles="{{ .Data.mappath }}/tiles/{z}/{x}/{y}.png"></div>
|
||||
<div class="p" id="map" data-locations="{{ .Data.locations }}"></div>
|
||||
<script defer src="{{ asset "js/geomap.js" }}"></script>
|
||||
{{ end }}
|
||||
</main>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<title>{{ with .Data.RenderedTitle }}{{ . }} - {{end}}{{ mdtitle .Blog.Title }}</title>
|
||||
{{ include "postheadmeta" . }}
|
||||
{{ with shorturl .Data }}<link rel="shortlink" href="{{ . }}">{{ end }}
|
||||
{{ include "tripheader" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
|
@ -17,6 +18,7 @@
|
|||
{{ content .Data false }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ include "tripdetails" . }}
|
||||
{{ include "posttax" . }}
|
||||
</article>
|
||||
{{ include "author" . }}
|
||||
|
|
|
@ -18,8 +18,11 @@ editorpostdesc: "Leere Parameter werden automatisch entfernt. Mehr mögliche Par
|
|||
emailopt: "E-Mail (optional)"
|
||||
fileuses: "Datei-Verwendungen"
|
||||
gentts: "Text-To-Speech-Audio erzeugen"
|
||||
gpxhelper: "GPX-Helfer"
|
||||
gpxhelperdesc: "GPX minimieren und YAML für das Frontmatter generieren."
|
||||
interactions: "Interaktionen & Kommentare"
|
||||
interactionslabel: "Hast du eine Antwort hierzu veröffentlicht? Füge hier die URL ein."
|
||||
kilometers: "Kilometer"
|
||||
likeof: "Gefällt mir von"
|
||||
loading: "Laden..."
|
||||
location: "Standort"
|
||||
|
|
|
@ -22,9 +22,12 @@ emailopt: "Email (optional)"
|
|||
feed: "Feed"
|
||||
fileuses: "file uses"
|
||||
gentts: "Generate Text-To-Speech audio"
|
||||
gpxhelper: "GPX helper"
|
||||
gpxhelperdesc: "Minify GPX and generate YAML for the frontmatter."
|
||||
indieauth: "IndieAuth"
|
||||
interactions: "Interactions & Comments"
|
||||
interactionslabel: "Have you published a response to this? Paste the URL here."
|
||||
kilometers: "kilometers"
|
||||
likeof: "Like of"
|
||||
loading: "Loading..."
|
||||
location: "Location"
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{{ define "tripdetails" }}
|
||||
{{ if .Data.HasTrip }}
|
||||
{{ $trip := (rendertrip .Data) }}
|
||||
{{ if $trip.HasPoints }}
|
||||
{{ $lang := .Blog.Lang }}
|
||||
<p>{{ with $trip.Name }}<b>{{ . }}</b> {{ end }}{{ with $trip.Kilometers }}🏁 {{ . }} {{ string $lang "kilometers" }} {{ end }}{{ with $trip.Hours }}⌛ {{ . }}{{ end }}</p>
|
||||
<div class="p" id="map" data-paths="{{ $trip.PathsJSON }}" data-tiles="/tiles/{z}/{x}/{y}.png"></div>
|
||||
<script defer src="{{ asset "js/geotrip.js" }}"></script>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
|
@ -0,0 +1,6 @@
|
|||
{{ define "tripheader" }}
|
||||
{{ if .Data.HasTrip }}
|
||||
<link rel="stylesheet" href="/x/leaflet/leaflet.css"/>
|
||||
<script src="/x/leaflet/leaflet.js"></script>
|
||||
{{ end }}
|
||||
{{ end }}
|
File diff suppressed because it is too large
Load Diff
4
utils.go
4
utils.go
|
@ -220,8 +220,8 @@ func urlHasExt(rawUrl string, allowed ...string) (ext string, has bool) {
|
|||
return ext, funk.ContainsString(allowed, strings.ToLower(ext))
|
||||
}
|
||||
|
||||
// Get SHA-256 hash of file
|
||||
func getSHA256(file io.ReadSeeker) (filename string, err error) {
|
||||
// Get SHA-256 hash
|
||||
func getSHA256(file io.ReadSeeker) (hash string, err error) {
|
||||
if _, err = file.Seek(0, 0); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue