Merge pull request #369 from writeas/web-monetization
Support Web Monetization
This commit is contained in:
commit
e1cde913e2
|
@ -826,6 +826,9 @@ func viewEditCollection(app *App, u *User, w http.ResponseWriter, r *http.Reques
|
|||
return ErrCollectionNotFound
|
||||
}
|
||||
|
||||
// Add collection properties
|
||||
c.MonetizationPointer = app.db.GetCollectionAttribute(c.ID, "monetization_pointer")
|
||||
|
||||
silenced, err := app.db.IsUserSilenced(u.ID)
|
||||
if err != nil {
|
||||
log.Error("view edit collection %v", err)
|
||||
|
|
1
admin.go
1
admin.go
|
@ -529,6 +529,7 @@ func handleAdminUpdateConfig(apper Apper, u *User, w http.ResponseWriter, r *htt
|
|||
}
|
||||
apper.App().cfg.App.Federation = r.FormValue("federation") == "on"
|
||||
apper.App().cfg.App.PublicStats = r.FormValue("public_stats") == "on"
|
||||
apper.App().cfg.App.Monetization = r.FormValue("monetization") == "on"
|
||||
apper.App().cfg.App.Private = r.FormValue("private") == "on"
|
||||
apper.App().cfg.App.LocalTimeline = r.FormValue("local_timeline") == "on"
|
||||
if apper.App().cfg.App.LocalTimeline && apper.App().timeline == nil {
|
||||
|
|
|
@ -56,6 +56,8 @@ type (
|
|||
PublicOwner bool `datastore:"public_owner" json:"-"`
|
||||
URL string `json:"url,omitempty"`
|
||||
|
||||
MonetizationPointer string `json:"monetization_pointer,omitempty"`
|
||||
|
||||
db *datastore
|
||||
hostName string
|
||||
}
|
||||
|
@ -87,14 +89,15 @@ type (
|
|||
Handle string `schema:"handle" json:"handle"`
|
||||
|
||||
// Actual collection values updated in the DB
|
||||
Alias *string `schema:"alias" json:"alias"`
|
||||
Title *string `schema:"title" json:"title"`
|
||||
Description *string `schema:"description" json:"description"`
|
||||
StyleSheet *sql.NullString `schema:"style_sheet" json:"style_sheet"`
|
||||
Script *sql.NullString `schema:"script" json:"script"`
|
||||
Signature *sql.NullString `schema:"signature" json:"signature"`
|
||||
Visibility *int `schema:"visibility" json:"public"`
|
||||
Format *sql.NullString `schema:"format" json:"format"`
|
||||
Alias *string `schema:"alias" json:"alias"`
|
||||
Title *string `schema:"title" json:"title"`
|
||||
Description *string `schema:"description" json:"description"`
|
||||
StyleSheet *sql.NullString `schema:"style_sheet" json:"style_sheet"`
|
||||
Script *sql.NullString `schema:"script" json:"script"`
|
||||
Signature *sql.NullString `schema:"signature" json:"signature"`
|
||||
Monetization *string `schema:"monetization_pointer" json:"monetization_pointer"`
|
||||
Visibility *int `schema:"visibility" json:"public"`
|
||||
Format *sql.NullString `schema:"format" json:"format"`
|
||||
}
|
||||
CollectionFormat struct {
|
||||
Format string
|
||||
|
@ -552,6 +555,7 @@ type CollectionPage struct {
|
|||
IsOwner bool
|
||||
CanPin bool
|
||||
Username string
|
||||
Monetization string
|
||||
Collections *[]Collection
|
||||
PinnedPosts *[]PublicPost
|
||||
IsAdmin bool
|
||||
|
@ -829,6 +833,7 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
|
|||
// Add more data
|
||||
// TODO: fix this mess of collections inside collections
|
||||
displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, isOwner)
|
||||
displayPage.Monetization = app.db.GetCollectionAttribute(coll.ID, "monetization_pointer")
|
||||
|
||||
collTmpl := "collection"
|
||||
if app.cfg.App.Chorus {
|
||||
|
@ -947,6 +952,7 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e
|
|||
// Add more data
|
||||
// TODO: fix this mess of collections inside collections
|
||||
displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, isOwner)
|
||||
displayPage.Monetization = app.db.GetCollectionAttribute(coll.ID, "monetization_pointer")
|
||||
|
||||
err = templates["collection-tags"].ExecuteTemplate(w, "collection-tags", displayPage)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright © 2018-2019 A Bunch Tell LLC.
|
||||
* Copyright © 2018-2020 A Bunch Tell LLC.
|
||||
*
|
||||
* This file is part of WriteFreely.
|
||||
*
|
||||
|
@ -137,9 +137,11 @@ type (
|
|||
MinUsernameLen int `ini:"min_username_len"`
|
||||
MaxBlogs int `ini:"max_blogs"`
|
||||
|
||||
// Options for public instances
|
||||
// Federation
|
||||
Federation bool `ini:"federation"`
|
||||
PublicStats bool `ini:"public_stats"`
|
||||
Federation bool `ini:"federation"`
|
||||
PublicStats bool `ini:"public_stats"`
|
||||
Monetization bool `ini:"monetization"`
|
||||
|
||||
// Access
|
||||
Private bool `ini:"private"`
|
||||
|
|
45
database.go
45
database.go
|
@ -905,6 +905,29 @@ func (db *datastore) UpdateCollection(c *SubmittedCollection, alias string) erro
|
|||
}
|
||||
}
|
||||
|
||||
// Update Monetization value
|
||||
if c.Monetization != nil {
|
||||
skipUpdate := false
|
||||
if *c.Monetization != "" {
|
||||
// Strip away any excess spaces
|
||||
trimmed := strings.TrimSpace(*c.Monetization)
|
||||
// Only update value when it starts with "$", per spec: https://paymentpointers.org
|
||||
if strings.HasPrefix(trimmed, "$") {
|
||||
c.Monetization = &trimmed
|
||||
} else {
|
||||
// Value appears invalid, so don't update
|
||||
skipUpdate = true
|
||||
}
|
||||
}
|
||||
if !skipUpdate {
|
||||
_, err = db.Exec("INSERT INTO collectionattributes (collection_id, attribute, value) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE value = ?", collID, "monetization_pointer", *c.Monetization, *c.Monetization)
|
||||
if err != nil {
|
||||
log.Error("Unable to insert monetization_pointer value: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update rest of the collection data
|
||||
res, err = db.Exec("UPDATE collections SET "+q.Updates+" WHERE "+q.Conditions, q.Params...)
|
||||
if err != nil {
|
||||
|
@ -2162,6 +2185,28 @@ func (db *datastore) CollectionHasAttribute(id int64, attr string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (db *datastore) GetCollectionAttribute(id int64, attr string) string {
|
||||
var v string
|
||||
err := db.QueryRow("SELECT value FROM collectionattributes WHERE collection_id = ? AND attribute = ?", id, attr).Scan(&v)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
return ""
|
||||
case err != nil:
|
||||
log.Error("Couldn't SELECT value in getCollectionAttribute for attribute '%s': %v", attr, err)
|
||||
return ""
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (db *datastore) SetCollectionAttribute(id int64, attr, v string) error {
|
||||
_, err := db.Exec("INSERT INTO collectionattributes (collection_id, attribute, value) VALUES (?, ?, ?)", id, attr, v)
|
||||
if err != nil {
|
||||
log.Error("Unable to INSERT into collectionattributes: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAccount will delete the entire account for userID
|
||||
func (db *datastore) DeleteAccount(userID int64) error {
|
||||
// Get all collections
|
||||
|
|
2
posts.go
2
posts.go
|
@ -1476,6 +1476,7 @@ Are you sure it was ever here?`,
|
|||
IsOwner bool
|
||||
IsPinned bool
|
||||
IsCustomDomain bool
|
||||
Monetization string
|
||||
PinnedPosts *[]PublicPost
|
||||
IsFound bool
|
||||
IsAdmin bool
|
||||
|
@ -1493,6 +1494,7 @@ Are you sure it was ever here?`,
|
|||
tp.CanInvite = canUserInvite(app.cfg, tp.IsAdmin)
|
||||
tp.PinnedPosts, _ = app.db.GetPinnedPosts(coll, p.IsOwner)
|
||||
tp.IsPinned = len(*tp.PinnedPosts) > 0 && PostsContains(tp.PinnedPosts, p)
|
||||
tp.Monetization = app.db.GetCollectionAttribute(coll.ID, "monetization_pointer")
|
||||
|
||||
if !postFound {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<meta property="og:updated_time" content="{{.Created8601}}" />
|
||||
{{range .Images}}<meta property="og:image" content="{{.}}" />{{else}}<meta property="og:image" content="{{.Collection.AvatarURL}}">{{end}}
|
||||
<meta property="article:published_time" content="{{.Created8601}}">
|
||||
{{template "collection-meta" .}}
|
||||
{{if .Collection.StyleSheet}}<style type="text/css">{{.Collection.StyleSheetDisplay}}</style>{{end}}
|
||||
<style type="text/css">
|
||||
body footer {
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
<meta property="og:url" content="{{.CanonicalURL}}" />
|
||||
<meta property="og:description" content="{{.Description}}" />
|
||||
<meta property="og:image" content="{{.AvatarURL}}">
|
||||
{{template "collection-meta" .}}
|
||||
{{if .StyleSheet}}<style type="text/css">{{.StyleSheetDisplay}}</style>{{end}}
|
||||
<style type="text/css">
|
||||
body#collection header {
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
{{range .Images}}<meta property="og:image" content="{{.}}" />{{else}}<meta property="og:image" content="{{.Collection.AvatarURL}}">{{end}}
|
||||
<meta property="article:published_time" content="{{.Created8601}}">
|
||||
{{ end }}
|
||||
{{template "collection-meta" .}}
|
||||
{{if .Collection.StyleSheet}}<style type="text/css">{{.Collection.StyleSheetDisplay}}</style>{{end}}
|
||||
|
||||
{{if .Collection.RenderMathJax}}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<meta property="og:type" content="article" />
|
||||
<meta property="og:url" content="{{.CanonicalURL}}tag:{{.Tag}}" />
|
||||
<meta property="og:image" content="{{.Collection.AvatarURL}}">
|
||||
{{template "collection-meta" .}}
|
||||
{{if .Collection.StyleSheet}}<style type="text/css">{{.Collection.StyleSheetDisplay}}</style>{{end}}
|
||||
|
||||
{{if .Collection.RenderMathJax}}
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
<meta property="og:url" content="{{.CanonicalURL}}" />
|
||||
<meta property="og:description" content="{{.Description}}" />
|
||||
<meta property="og:image" content="{{.AvatarURL}}">
|
||||
{{template "collection-meta" .}}
|
||||
{{if .StyleSheet}}<style type="text/css">{{.StyleSheetDisplay}}</style>{{end}}
|
||||
|
||||
{{if .RenderMathJax}}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
<!-- Miscelaneous render related template parts we use multiple times -->
|
||||
{{define "collection-meta"}}
|
||||
{{if .Monetization -}}
|
||||
<meta name="monetization" content="{{.Monetization}}" />
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
{{define "highlighting"}}
|
||||
<script>
|
||||
// TODO: this feels more like a mutation observer
|
||||
|
|
|
@ -136,6 +136,13 @@ select {
|
|||
</label></div>
|
||||
<div><input type="checkbox" name="public_stats" id="public_stats" {{if .Config.PublicStats}}checked="checked"{{end}} /></div>
|
||||
</div>
|
||||
<div class="features row">
|
||||
<div><label for="monetization">
|
||||
Monetization
|
||||
<p>Enable blogs on this site to receive micro­pay­ments from readers via <a target="wm" href="https://webmonetization.org/">Web Monetization</a>.</p>
|
||||
</label></div>
|
||||
<div><input type="checkbox" name="monetization" id="monetization" {{if .Config.Monetization}}checked="checked"{{end}} /></div>
|
||||
</div>
|
||||
<div class="features row">
|
||||
<div><label for="min_username_len">
|
||||
Minimum Username Length
|
||||
|
|
|
@ -151,6 +151,16 @@ textarea.section.norm {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{if .Monetization}}
|
||||
<div class="option">
|
||||
<h2>Web Monetization</h2>
|
||||
<div class="section">
|
||||
<p class="explain">Web Monetization enables you to receive micropayments from readers that have a <a href="https://coil.com">Coil membership</a>. Add your payment pointer to enable Web Monetization on your blog.</p>
|
||||
<input type="text" name="monetization_pointer" style="width:100%" value="{{.MonetizationPointer}}" placeholder="$wallet.example.com/alice" />
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="option" style="text-align: center; margin-top: 4em;">
|
||||
<input type="submit" id="save-changes" value="Save changes" />
|
||||
<p><a href="{{if .SingleUser}}/{{else}}/{{.Alias}}/{{end}}">View Blog</a></p>
|
||||
|
|
Loading…
Reference in New Issue