Add map feature

This commit is contained in:
Jan-Lukas Else 2021-07-06 21:06:39 +02:00
parent 9369305c7d
commit 09804d7640
12 changed files with 170 additions and 4 deletions

View File

@ -70,6 +70,7 @@ type configBlog struct {
PostAsHome bool `mapstructure:"postAsHome"`
RandomPost *randomPost `mapstructure:"randomPost"`
Comments *comments `mapstructure:"comments"`
Map *configMap `mapstructure:"map"`
}
type section struct {
@ -145,6 +146,11 @@ type comments struct {
Enabled bool `mapstructure:"enabled"`
}
type configMap struct {
Enabled bool `mapstructure:"enabled"`
Path string `mapstructure:"path"`
}
type configUser struct {
Nick string `mapstructure:"nick"`
Name string `mapstructure:"name"`

2
geo.go
View File

@ -12,6 +12,8 @@ import (
"github.com/thoas/go-funk"
)
const geoParam = "location"
func (a *goBlog) geoTitle(g *gogeouri.Geo, lang string) string {
if name, ok := g.Parameters["name"]; ok && len(name) > 0 && name[0] != "" {
return name[0]

64
geoMap.go Normal file
View File

@ -0,0 +1,64 @@
package main
import (
"encoding/json"
"net/http"
)
func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
blog := r.Context().Value(blogContextKey).(string)
allPostsWithLocation, err := a.db.getPosts(&postsRequestConfig{
blog: blog,
status: statusPublished,
parameter: geoParam,
withOnlyParameters: []string{geoParam},
})
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
if len(allPostsWithLocation) == 0 {
a.render(w, r, templateGeoMap, &renderData{
BlogString: blog,
Data: map[string]interface{}{
"nolocations": true,
},
})
return
}
type templateLocation struct {
Lat float64
Lon float64
Post string
}
var locations []*templateLocation
for _, p := range allPostsWithLocation {
if g := p.GeoURI(); g != nil {
locations = append(locations, &templateLocation{
Lat: g.Latitude,
Lon: g.Longitude,
Post: p.Path,
})
}
}
jb, err := json.Marshal(locations)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
// Manipulate CSP header
w.Header().Set(cspHeader, w.Header().Get(cspHeader)+" https://unpkg.com/ https://tile.openstreetmap.org")
a.render(w, r, templateGeoMap, &renderData{
BlogString: blog,
Data: map[string]interface{}{
"locations": string(jb),
},
})
}

14
http.go
View File

@ -538,6 +538,16 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
r.Get(brPath+".opml", a.serveBlogrollExport)
})
}
// Geo map
if mc := blogConfig.Map; mc != nil && mc.Enabled {
mapPath := mc.Path
if mc.Path == "" {
mapPath = "/map"
}
r.With(a.privateModeHandler...).With(a.cache.cacheMiddleware, sbm).Get(blogConfig.getRelativePath(mapPath), a.serveGeoMap)
}
}
// Sitemap
@ -573,6 +583,8 @@ func (a *goBlog) refreshCSPDomains() {
}
}
const cspHeader = "Content-Security-Policy"
func (a *goBlog) securityHeaders(next http.Handler) http.Handler {
a.refreshCSPDomains()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -581,7 +593,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'"+a.cspDomains)
w.Header().Set(cspHeader, "default-src 'self'"+a.cspDomains)
if a.cfg.Server.Tor && a.torAddress != "" {
w.Header().Set("Onion-Location", fmt.Sprintf("http://%v%v", a.torAddress, r.RequestURI))
}

View File

@ -231,6 +231,10 @@ footer {
transition: transform 2s ease;
}
#map {
height: 400px;
}
/* Print */
@media print {
html {

View File

@ -227,6 +227,7 @@ type postsRequestConfig struct {
publishedYear, publishedMonth, publishedDay int
randomOrder bool
withoutParameters bool
withOnlyParameters []string
}
func buildPostsQuery(c *postsRequestConfig, selection string) (query string, args []interface{}) {
@ -303,11 +304,28 @@ func buildPostsQuery(c *postsRequestConfig, selection string) (query string, arg
return query, args
}
func (d *database) getPostParameters(path string) (params map[string][]string, err error) {
rows, err := d.query("select parameter, value from post_parameters where path = @path order by id", sql.Named("path", path))
func (d *database) getPostParameters(path string, parameters ...string) (params map[string][]string, err error) {
var sqlArgs []interface{}
// Parameter filter
paramFilter := ""
if len(parameters) > 0 {
paramFilter = " and parameter in ("
for i, p := range parameters {
if i > 0 {
paramFilter += ", "
}
named := fmt.Sprintf("param%v", i)
paramFilter += "@" + named
sqlArgs = append(sqlArgs, sql.Named(named, p))
}
paramFilter += ")"
}
// Query
rows, err := d.query("select parameter, value from post_parameters where path = @path"+paramFilter+" order by id", append(sqlArgs, sql.Named("path", path))...)
if err != nil {
return nil, err
}
// Result
var name, value string
params = map[string][]string{}
for rows.Next() {
@ -343,7 +361,7 @@ func (d *database) getPosts(config *postsRequestConfig) (posts []*post, err erro
Status: postStatus(status),
}
if !config.withoutParameters {
if p.Parameters, err = d.getPostParameters(path); err != nil {
if p.Parameters, err = d.getPostParameters(path, config.withOnlyParameters...); err != nil {
return nil, err
}
}

View File

@ -39,6 +39,7 @@ const (
templateNotificationsAdmin = "notificationsadmin"
templateWebmentionAdmin = "webmentionadmin"
templateBlogroll = "blogroll"
templateGeoMap = "geomap"
)
func (a *goBlog) initRendering() error {

View File

@ -195,6 +195,10 @@ footer * {
transition: transform 2s ease;
}
#map {
height: 400px;
}
/* Print */
@media print {
html {

View File

@ -0,0 +1,22 @@
(function () {
let mapEl = document.getElementById('map')
let locations = JSON.parse(mapEl.dataset.locations)
let map = L.map('map')
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map)
let markers = []
locations.forEach(loc => {
let marker = [loc.Lat, loc.Lon]
L.marker(marker).addTo(map).on('click', function () {
window.open(loc.Post, '_blank').focus()
})
markers.push(marker)
})
map.fitBounds(markers)
map.zoomOut(2, { animate: false })
})()

31
templates/geomap.gohtml Normal file
View File

@ -0,0 +1,31 @@
{{ define "title" }}
<title>{{ .Blog.Title }}</title>
{{ if not .Data.nolocations }}
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""
/>
<script
src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
crossorigin=""
></script>
{{ end }}
{{ end }}
{{ define "main" }}
<main>
{{ if .Data.nolocations }}
<p>{{ string .Blog.Lang "nolocations" }}</p>
{{ else }}
<div class="p" id="map" data-locations="{{ .Data.locations }}"></div>
<script defer src="{{ asset "js/geomap.js" }}"></script>
{{ end }}
</main>
{{ end }}
{{ define "geomap" }}
{{ template "base" . }}
{{ end }}

View File

@ -23,6 +23,7 @@ locationnotsupported: "Die Standort-API wird von diesem Browser nicht unterstüt
mediafiles: "Medien-Dateien"
next: "Weiter"
nofiles: "Keine Dateien"
nolocations: "Keine Posts mit Standorten"
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

@ -33,6 +33,7 @@ mediafiles: "Media files"
nameopt: "Name (optional)"
next: "Next"
nofiles: "No files"
nolocations: "No posts with locations"
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."