mirror of https://github.com/jlelse/GoBlog
Reactions
This commit is contained in:
parent
ddd097f809
commit
1e03474539
|
@ -18,7 +18,7 @@ RUN go build -ldflags '-w -s' -o GoBlog
|
||||||
|
|
||||||
FROM build as test
|
FROM build as test
|
||||||
|
|
||||||
RUN go test -timeout 15s -cover ./...
|
RUN go test -timeout 20s -cover ./...
|
||||||
|
|
||||||
FROM alpine:3.15 as base
|
FROM alpine:3.15 as base
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ type config struct {
|
||||||
EasterEgg *configEasterEgg `mapstructure:"easterEgg"`
|
EasterEgg *configEasterEgg `mapstructure:"easterEgg"`
|
||||||
MapTiles *configMapTiles `mapstructure:"mapTiles"`
|
MapTiles *configMapTiles `mapstructure:"mapTiles"`
|
||||||
TTS *configTTS `mapstructure:"tts"`
|
TTS *configTTS `mapstructure:"tts"`
|
||||||
|
Reactions *configReactions `mapstructure:"reactions"`
|
||||||
Pprof *configPprof `mapstructure:"pprof"`
|
Pprof *configPprof `mapstructure:"pprof"`
|
||||||
Debug bool `mapstructure:"debug"`
|
Debug bool `mapstructure:"debug"`
|
||||||
initialized bool
|
initialized bool
|
||||||
|
@ -311,6 +312,10 @@ type configTTS struct {
|
||||||
GoogleAPIKey string `mapstructure:"googleApiKey"`
|
GoogleAPIKey string `mapstructure:"googleApiKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type configReactions struct {
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
type configPprof struct {
|
type configPprof struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
Address string `mapstructure:"address"`
|
Address string `mapstructure:"address"`
|
||||||
|
|
|
@ -84,7 +84,7 @@ func (a *goBlog) openDatabase(file string, logging bool) (*database, error) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// Open db
|
// Open db
|
||||||
db, err := sql.Open(dbDriverName, file+"?mode=rwc&_journal_mode=WAL&_busy_timeout=100&cache=shared")
|
db, err := sql.Open(dbDriverName, file+"?mode=rwc&_journal=WAL&_timeout=100&cache=shared&_fk=1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
create table reactions (
|
||||||
|
path text not null,
|
||||||
|
reaction text not null,
|
||||||
|
count integer default 0,
|
||||||
|
primary key (path, reaction),
|
||||||
|
foreign key (path) references posts(path) on update cascade on delete cascade
|
||||||
|
);
|
|
@ -43,4 +43,8 @@ If configured, GoBlog will also send a notification using a Telegram Bot or [Ntf
|
||||||
|
|
||||||
## Tor Hidden Services
|
## Tor Hidden Services
|
||||||
|
|
||||||
GoBlog can be configured to provide a Tor Hidden Service. This is useful if you want to offer your visitors a way to connect to your blog from censored networks or countries. See the `example-config.yml` file for how to enable the Tor Hidden Service. If you don't need to hide your server, you can enable the Single Hop mode.
|
GoBlog can be configured to provide a Tor Hidden Service. This is useful if you want to offer your visitors a way to connect to your blog from censored networks or countries. See the `example-config.yml` file for how to enable the Tor Hidden Service. If you don't need to hide your server, you can enable the Single Hop mode.
|
||||||
|
|
||||||
|
## Reactions
|
||||||
|
|
||||||
|
It's possible to enable post reactions. GoBlog currently has a hardcoded list of reactions: "❤️", "👍", "👎", "😂" and "😱". If enabled, users can react to a post by clicking on the reaction button below the post. If you want to disable reactions for a single post, you can set the `reactions` parameter to `false` in the post's metadata.
|
|
@ -168,6 +168,10 @@ tts:
|
||||||
enabled: true
|
enabled: true
|
||||||
googleApiKey: "xxxxxxxx"
|
googleApiKey: "xxxxxxxx"
|
||||||
|
|
||||||
|
# Reactions (see docs for more info)
|
||||||
|
reactions:
|
||||||
|
enabled: true # Enable reactions (default is false)
|
||||||
|
|
||||||
# Blogs
|
# Blogs
|
||||||
defaultBlog: en # Default blog (needed because you can define multiple blogs)
|
defaultBlog: en # Default blog (needed because you can define multiple blogs)
|
||||||
blogs:
|
blogs:
|
||||||
|
|
|
@ -99,8 +99,16 @@ func (a *goBlog) mediaFilesRouter(r chi.Router) {
|
||||||
// Various other routes
|
// Various other routes
|
||||||
func (a *goBlog) otherRoutesRouter(r chi.Router) {
|
func (a *goBlog) otherRoutesRouter(r chi.Router) {
|
||||||
r.Use(a.privateModeHandler)
|
r.Use(a.privateModeHandler)
|
||||||
|
|
||||||
|
// Leaflet
|
||||||
r.Get("/tiles/{s}/{z}/{x}/{y}.png", a.proxyTiles())
|
r.Get("/tiles/{s}/{z}/{x}/{y}.png", a.proxyTiles())
|
||||||
r.With(cacheLoggedIn, a.cacheMiddleware).HandleFunc("/leaflet/*", a.serveFs(leafletFiles, "/-/"))
|
r.With(cacheLoggedIn, a.cacheMiddleware).HandleFunc("/leaflet/*", a.serveFs(leafletFiles, "/-/"))
|
||||||
|
|
||||||
|
// Reactions
|
||||||
|
if a.reactionsEnabled() {
|
||||||
|
r.Get("/reactions", a.getReactions)
|
||||||
|
r.Post("/reactions", a.postReaction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blog
|
// Blog
|
||||||
|
|
|
@ -280,6 +280,11 @@ details summary {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#reactions button:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
// Print
|
// Print
|
||||||
@media print {
|
@media print {
|
||||||
html {
|
html {
|
||||||
|
|
19
postsDb.go
19
postsDb.go
|
@ -175,14 +175,19 @@ func (db *database) savePost(p *post, o *postCreationOptions) error {
|
||||||
var sqlArgs = []any{dbNoCache}
|
var sqlArgs = []any{dbNoCache}
|
||||||
// Start transaction
|
// Start transaction
|
||||||
sqlBuilder.WriteString("begin;")
|
sqlBuilder.WriteString("begin;")
|
||||||
// Delete old post
|
// Update or create post
|
||||||
if !o.new {
|
if o.new {
|
||||||
sqlBuilder.WriteString("delete from posts where path = ?;delete from post_parameters where path = ?;")
|
// New post, create it
|
||||||
sqlArgs = append(sqlArgs, o.oldPath, o.oldPath)
|
sqlBuilder.WriteString("insert into posts (path, content, published, updated, blog, section, status, priority) values (?, ?, ?, ?, ?, ?, ?, ?);")
|
||||||
|
sqlArgs = append(sqlArgs, p.Path, p.Content, toUTCSafe(p.Published), toUTCSafe(p.Updated), p.Blog, p.Section, p.Status, p.Priority)
|
||||||
|
} else {
|
||||||
|
// Update old post
|
||||||
|
sqlBuilder.WriteString("update posts set path = ?, content = ?, published = ?, updated = ?, blog = ?, section = ?, status = ?, priority = ? where path = ?;")
|
||||||
|
sqlArgs = append(sqlArgs, p.Path, p.Content, toUTCSafe(p.Published), toUTCSafe(p.Updated), p.Blog, p.Section, p.Status, p.Priority, o.oldPath)
|
||||||
|
// Delete post parameters
|
||||||
|
sqlBuilder.WriteString("delete from post_parameters where path = ?;")
|
||||||
|
sqlArgs = append(sqlArgs, o.oldPath)
|
||||||
}
|
}
|
||||||
// Insert new post
|
|
||||||
sqlBuilder.WriteString("insert into posts (path, content, published, updated, blog, section, status, priority) values (?, ?, ?, ?, ?, ?, ?, ?);")
|
|
||||||
sqlArgs = append(sqlArgs, p.Path, p.Content, toUTCSafe(p.Published), toUTCSafe(p.Updated), p.Blog, p.Section, p.Status, p.Priority)
|
|
||||||
// Insert post parameters
|
// Insert post parameters
|
||||||
for param, value := range p.Parameters {
|
for param, value := range p.Parameters {
|
||||||
for _, value := range value {
|
for _, value := range value {
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"go.goblog.app/app/pkgs/bufferpool"
|
||||||
|
"go.goblog.app/app/pkgs/contenttype"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hardcoded for now
|
||||||
|
var allowedReactions = []string{
|
||||||
|
"❤️",
|
||||||
|
"👍",
|
||||||
|
"🎉",
|
||||||
|
"😂",
|
||||||
|
"😱",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) reactionsEnabled() bool {
|
||||||
|
return a.cfg.Reactions != nil && a.cfg.Reactions.Enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
const reactionsPostParam = "reactions"
|
||||||
|
|
||||||
|
func (a *goBlog) reactionsEnabledForPost(post *post) bool {
|
||||||
|
return a.reactionsEnabled() && post != nil && post.firstParameter(reactionsPostParam) != "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) postReaction(w http.ResponseWriter, r *http.Request) {
|
||||||
|
path := r.FormValue("path")
|
||||||
|
reaction := r.FormValue("reaction")
|
||||||
|
if path == "" || reaction == "" {
|
||||||
|
a.serveError(w, r, "", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := a.saveReaction(reaction, path)
|
||||||
|
if err != nil {
|
||||||
|
a.serveError(w, r, "", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) saveReaction(reaction, path string) error {
|
||||||
|
// Check if reaction is allowed
|
||||||
|
if !lo.Contains(allowedReactions, reaction) {
|
||||||
|
return errors.New("reaction not allowed")
|
||||||
|
}
|
||||||
|
// Insert reaction
|
||||||
|
_, err := a.db.exec("insert into reactions (path, reaction, count) values (?, ?, 1) on conflict (path, reaction) do update set count=count+1", path, reaction)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) getReactions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
path := r.FormValue("path")
|
||||||
|
reactions, err := a.getReactionsFromDatabase(path)
|
||||||
|
if err != nil {
|
||||||
|
a.serveError(w, r, "", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf := bufferpool.Get()
|
||||||
|
defer bufferpool.Put(buf)
|
||||||
|
err = json.NewEncoder(buf).Encode(reactions)
|
||||||
|
if err != nil {
|
||||||
|
a.serveError(w, r, "", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set(contentType, contenttype.JSONUTF8)
|
||||||
|
_ = a.min.Get().Minify(contenttype.JSON, w, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) getReactionsFromDatabase(path string) (map[string]int, error) {
|
||||||
|
sqlBuf := bufferpool.Get()
|
||||||
|
defer bufferpool.Put(sqlBuf)
|
||||||
|
sqlArgs := []any{}
|
||||||
|
sqlBuf.WriteString("select reaction, count from reactions where path=? and reaction in (")
|
||||||
|
sqlArgs = append(sqlArgs, path)
|
||||||
|
for i, reaction := range allowedReactions {
|
||||||
|
if i > 0 {
|
||||||
|
sqlBuf.WriteString(",")
|
||||||
|
}
|
||||||
|
sqlBuf.WriteString("?")
|
||||||
|
sqlArgs = append(sqlArgs, reaction)
|
||||||
|
}
|
||||||
|
sqlBuf.WriteString(") and path not in (select path from post_parameters where parameter=? and value=?)")
|
||||||
|
sqlArgs = append(sqlArgs, reactionsPostParam, "false")
|
||||||
|
rows, err := a.db.query(sqlBuf.String(), sqlArgs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
reactions := map[string]int{}
|
||||||
|
for rows.Next() {
|
||||||
|
var reaction string
|
||||||
|
var count int
|
||||||
|
err = rows.Scan(&reaction, &count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reactions[reaction] = count
|
||||||
|
}
|
||||||
|
return reactions, nil
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_reactionsLowLevel(t *testing.T) {
|
||||||
|
app := &goBlog{
|
||||||
|
cfg: createDefaultTestConfig(t),
|
||||||
|
}
|
||||||
|
_ = app.initConfig()
|
||||||
|
_ = app.initDatabase(false)
|
||||||
|
defer app.db.close()
|
||||||
|
app.initComponents(false)
|
||||||
|
|
||||||
|
err := app.saveReaction("🖕", "/testpost")
|
||||||
|
assert.ErrorContains(t, err, "not allowed")
|
||||||
|
|
||||||
|
err = app.saveReaction("❤️", "/testpost")
|
||||||
|
assert.ErrorContains(t, err, "constraint failed")
|
||||||
|
|
||||||
|
// Create a post
|
||||||
|
err = app.createPost(&post{
|
||||||
|
Path: "/testpost",
|
||||||
|
Content: "test",
|
||||||
|
Status: statusPublished,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Create 4 reactions
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
err = app.saveReaction("❤️", "/testpost")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if reaction count is 4
|
||||||
|
reacts, err := app.getReactionsFromDatabase("/testpost")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, len(reacts))
|
||||||
|
assert.Equal(t, 4, reacts["❤️"])
|
||||||
|
|
||||||
|
// Change post path
|
||||||
|
err = app.replacePost(&post{
|
||||||
|
Path: "/newpost",
|
||||||
|
Content: "test",
|
||||||
|
Status: statusPublished,
|
||||||
|
}, "/testpost", statusPublished)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check if reaction count is 4
|
||||||
|
reacts, err = app.getReactionsFromDatabase("/newpost")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, len(reacts))
|
||||||
|
assert.Equal(t, 4, reacts["❤️"])
|
||||||
|
|
||||||
|
// Delete post
|
||||||
|
err = app.deletePost("/newpost")
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = app.deletePost("/newpost")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check if reaction count is 0
|
||||||
|
reacts, err = app.getReactionsFromDatabase("/newpost")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(reacts))
|
||||||
|
|
||||||
|
// Create a post with disabled reactions
|
||||||
|
err = app.createPost(&post{
|
||||||
|
Path: "/testpost2",
|
||||||
|
Content: "test",
|
||||||
|
Status: statusPublished,
|
||||||
|
Parameters: map[string][]string{
|
||||||
|
"reactions": {"false"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Create reaction
|
||||||
|
err = app.saveReaction("❤️", "/testpost2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check if reaction count is 0
|
||||||
|
reacts, err = app.getReactionsFromDatabase("/testpost2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(reacts))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_reactionsHighLevel(t *testing.T) {
|
||||||
|
app := &goBlog{
|
||||||
|
cfg: createDefaultTestConfig(t),
|
||||||
|
}
|
||||||
|
_ = app.initConfig()
|
||||||
|
_ = app.initDatabase(false)
|
||||||
|
defer app.db.close()
|
||||||
|
app.initComponents(false)
|
||||||
|
|
||||||
|
// Send unsuccessful reaction
|
||||||
|
form := url.Values{
|
||||||
|
"reaction": {"❤️"},
|
||||||
|
"path": {"/testpost"},
|
||||||
|
}
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(form.Encode()))
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
app.postReaction(rec, req)
|
||||||
|
assert.Equal(t, http.StatusBadRequest, rec.Code)
|
||||||
|
|
||||||
|
// Create a post
|
||||||
|
err := app.createPost(&post{
|
||||||
|
Path: "/testpost",
|
||||||
|
Content: "test",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Send successful reaction
|
||||||
|
form = url.Values{
|
||||||
|
"reaction": {"❤️"},
|
||||||
|
"path": {"/testpost"},
|
||||||
|
}
|
||||||
|
req = httptest.NewRequest(http.MethodPost, "/", strings.NewReader(form.Encode()))
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
app.postReaction(rec, req)
|
||||||
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
// Check if reaction count is 1
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/?path=/testpost", nil)
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
app.getReactions(rec, req)
|
||||||
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
assert.Equal(t, `{"❤️":1}`, rec.Body.String())
|
||||||
|
|
||||||
|
// Get reactions for a non-existing post
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/?path=/non-existing-post", nil)
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
app.getReactions(rec, req)
|
||||||
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
assert.Equal(t, `{}`, rec.Body.String())
|
||||||
|
|
||||||
|
}
|
|
@ -233,6 +233,11 @@ details summary > *:first-child {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
#reactions button:focus, #reactions .button:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
html {
|
html {
|
||||||
--background: #fff;
|
--background: #fff;
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
(function () {
|
||||||
|
|
||||||
|
// Get reactions element
|
||||||
|
let reactions = document.querySelector('#reactions')
|
||||||
|
|
||||||
|
// Get post path
|
||||||
|
let path = reactions.dataset.path
|
||||||
|
|
||||||
|
// Define update counts function
|
||||||
|
let updateCounts = function () {
|
||||||
|
// Fetch reactions json
|
||||||
|
fetch('/-/reactions?path=' + encodeURI(path))
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(json => {
|
||||||
|
// For every reaction
|
||||||
|
for (let reaction in json) {
|
||||||
|
// Get reaction buttons
|
||||||
|
let button = document.querySelector('#reactions button[data-reaction="' + reaction + '"]')
|
||||||
|
// Set reaction count
|
||||||
|
button.innerText = reaction + ' ' + json[reaction]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get allowed reactions
|
||||||
|
let allowed = reactions.dataset.allowed.split(',')
|
||||||
|
allowed.forEach(allowedReaction => {
|
||||||
|
|
||||||
|
// Create reaction button
|
||||||
|
let button = document.createElement('button')
|
||||||
|
button.dataset.reaction = allowedReaction
|
||||||
|
|
||||||
|
// Set click event
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
// Send reaction to server
|
||||||
|
let data = new FormData()
|
||||||
|
data.append('path', path)
|
||||||
|
data.append('reaction', allowedReaction)
|
||||||
|
fetch('/-/reactions', { method: 'POST', body: data })
|
||||||
|
.then(updateCounts)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set reaction text
|
||||||
|
button.innerText = allowedReaction
|
||||||
|
|
||||||
|
// Add button to reactions element
|
||||||
|
reactions.appendChild(button)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update reaction counts
|
||||||
|
updateCounts()
|
||||||
|
|
||||||
|
})()
|
2
ui.go
2
ui.go
|
@ -914,6 +914,8 @@ func (a *goBlog) renderPost(hb *htmlBuilder, rd *renderData) {
|
||||||
// Author
|
// Author
|
||||||
a.renderAuthor(hb)
|
a.renderAuthor(hb)
|
||||||
hb.writeElementClose("main")
|
hb.writeElementClose("main")
|
||||||
|
// Reactions
|
||||||
|
a.renderPostReactions(hb, p)
|
||||||
// Post edit actions
|
// Post edit actions
|
||||||
if rd.LoggedIn() {
|
if rd.LoggedIn() {
|
||||||
hb.writeElementOpen("div", "class", "actions")
|
hb.writeElementOpen("div", "class", "actions")
|
||||||
|
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.goblog.app/app/pkgs/bufferpool"
|
"go.goblog.app/app/pkgs/bufferpool"
|
||||||
|
@ -465,3 +466,13 @@ func (a *goBlog) renderPostGPX(hb *htmlBuilder, p *post, b *configBlog) {
|
||||||
hb.writeElementOpen("script", "defer", "", "src", a.assetFileName("js/geomap.js"))
|
hb.writeElementOpen("script", "defer", "", "src", a.assetFileName("js/geomap.js"))
|
||||||
hb.writeElementClose("script")
|
hb.writeElementClose("script")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) renderPostReactions(hb *htmlBuilder, p *post) {
|
||||||
|
if !a.reactionsEnabledForPost(p) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hb.writeElementOpen("div", "id", "reactions", "class", "actions", "data-path", p.Path, "data-allowed", strings.Join(allowedReactions, ","))
|
||||||
|
hb.writeElementClose("div")
|
||||||
|
hb.writeElementOpen("script", "defer", "", "src", a.assetFileName("js/reactions.js"))
|
||||||
|
hb.writeElementClose("script")
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue