Support post signatures

This enables users to add a signature to all blog posts, and update it from a single location.

Requires database migration with: writefreely db migrate

Closes T582
This commit is contained in:
Matt Baer 2020-06-23 16:24:45 -04:00
parent 5c94d23466
commit a25664bb97
8 changed files with 83 additions and 4 deletions

View File

@ -47,6 +47,7 @@ type (
Language string `schema:"lang" json:"lang,omitempty"` Language string `schema:"lang" json:"lang,omitempty"`
StyleSheet string `datastore:"style_sheet" schema:"style_sheet" json:"style_sheet"` StyleSheet string `datastore:"style_sheet" schema:"style_sheet" json:"style_sheet"`
Script string `datastore:"script" schema:"script" json:"script,omitempty"` Script string `datastore:"script" schema:"script" json:"script,omitempty"`
Signature string `datastore:"post_signature" schema:"signature" json:"-"`
Public bool `datastore:"public" json:"public"` Public bool `datastore:"public" json:"public"`
Visibility collVisibility `datastore:"private" json:"-"` Visibility collVisibility `datastore:"private" json:"-"`
Format string `datastore:"format" json:"format,omitempty"` Format string `datastore:"format" json:"format,omitempty"`
@ -91,6 +92,7 @@ type (
Description *string `schema:"description" json:"description"` Description *string `schema:"description" json:"description"`
StyleSheet *sql.NullString `schema:"style_sheet" json:"style_sheet"` StyleSheet *sql.NullString `schema:"style_sheet" json:"style_sheet"`
Script *sql.NullString `schema:"script" json:"script"` Script *sql.NullString `schema:"script" json:"script"`
Signature *sql.NullString `schema:"signature" json:"signature"`
Visibility *int `schema:"visibility" json:"public"` Visibility *int `schema:"visibility" json:"public"`
Format *sql.NullString `schema:"format" json:"format"` Format *sql.NullString `schema:"format" json:"format"`
} }

View File

@ -791,10 +791,10 @@ func (db *datastore) GetCollectionBy(condition string, value interface{}) (*Coll
c := &Collection{} c := &Collection{}
// FIXME: change Collection to reflect database values. Add helper functions to get actual values // FIXME: change Collection to reflect database values. Add helper functions to get actual values
var styleSheet, script, format zero.String var styleSheet, script, signature, format zero.String
row := db.QueryRow("SELECT id, alias, title, description, style_sheet, script, format, owner_id, privacy, view_count FROM collections WHERE "+condition, value) row := db.QueryRow("SELECT id, alias, title, description, style_sheet, script, post_signature, format, owner_id, privacy, view_count FROM collections WHERE "+condition, value)
err := row.Scan(&c.ID, &c.Alias, &c.Title, &c.Description, &styleSheet, &script, &format, &c.OwnerID, &c.Visibility, &c.Views) err := row.Scan(&c.ID, &c.Alias, &c.Title, &c.Description, &styleSheet, &script, &signature, &format, &c.OwnerID, &c.Visibility, &c.Views)
switch { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows:
return nil, impart.HTTPError{http.StatusNotFound, "Collection doesn't exist."} return nil, impart.HTTPError{http.StatusNotFound, "Collection doesn't exist."}
@ -806,6 +806,7 @@ func (db *datastore) GetCollectionBy(condition string, value interface{}) (*Coll
} }
c.StyleSheet = styleSheet.String c.StyleSheet = styleSheet.String
c.Script = script.String c.Script = script.String
c.Signature = signature.String
c.Format = format.String c.Format = format.String
c.Public = c.IsPublic() c.Public = c.IsPublic()
@ -849,7 +850,8 @@ func (db *datastore) UpdateCollection(c *SubmittedCollection, alias string) erro
SetStringPtr(c.Title, "title"). SetStringPtr(c.Title, "title").
SetStringPtr(c.Description, "description"). SetStringPtr(c.Description, "description").
SetNullString(c.StyleSheet, "style_sheet"). SetNullString(c.StyleSheet, "style_sheet").
SetNullString(c.Script, "script") SetNullString(c.Script, "script").
SetNullString(c.Signature, "post_signature")
if c.Format != nil { if c.Format != nil {
cf := &CollectionFormat{Format: c.Format.String} cf := &CollectionFormat{Format: c.Format.String}
@ -1150,6 +1152,7 @@ func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, inclu
break break
} }
p.extractData() p.extractData()
p.augmentContent(c)
p.formatContent(cfg, c, includeFuture) p.formatContent(cfg, c, includeFuture)
posts = append(posts, p.processPost()) posts = append(posts, p.processPost())
@ -1214,6 +1217,7 @@ func (db *datastore) GetPostsTagged(cfg *config.Config, c *Collection, tag strin
break break
} }
p.extractData() p.extractData()
p.augmentContent(c)
p.formatContent(cfg, c, includeFuture) p.formatContent(cfg, c, includeFuture)
posts = append(posts, p.processPost()) posts = append(posts, p.processPost())
@ -1590,6 +1594,7 @@ func (db *datastore) GetPinnedPosts(coll *CollectionObj, includeFuture bool) (*[
break break
} }
p.extractData() p.extractData()
p.augmentContent(&coll.Collection)
pp := p.processPost() pp := p.processPost()
pp.Collection = coll pp.Collection = coll

View File

@ -78,3 +78,10 @@ func (db *datastore) engine() string {
} }
return " ENGINE = InnoDB" return " ENGINE = InnoDB"
} }
func (db *datastore) after(colName string) string {
if db.driverName == driverSQLite {
return ""
}
return " AFTER " + colName
}

View File

@ -65,6 +65,7 @@ var migrations = []Migration{
New("support oauth attach", oauthAttach), // V6 -> V7 New("support oauth attach", oauthAttach), // V6 -> V7
New("support oauth via invite", oauthInvites), // V7 -> V8 (v0.12.0) New("support oauth via invite", oauthInvites), // V7 -> V8 (v0.12.0)
New("optimize drafts retrieval", optimizeDrafts), // V8 -> V9 New("optimize drafts retrieval", optimizeDrafts), // V8 -> V9
New("support post signatures", supportPostSignatures), // V9 -> V10
} }
// CurrentVer returns the current migration version the application is on // CurrentVer returns the current migration version the application is on

33
migrations/v10.go Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright © 2020 A Bunch Tell LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package migrations
func supportPostSignatures(db *datastore) error {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`ALTER TABLE collections ADD COLUMN post_signature ` + db.typeText() + db.collateMultiByte() + ` NULL` + db.after("script"))
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

View File

@ -58,6 +58,17 @@ func (p *PublicPost) formatContent(cfg *config.Config, isOwner bool) {
p.Post.formatContent(cfg, &p.Collection.Collection, isOwner) p.Post.formatContent(cfg, &p.Collection.Collection, isOwner)
} }
func (p *Post) augmentContent(c *Collection) {
// Add post signatures
if c.Signature != "" {
p.Content += "\n\n" + c.Signature
}
}
func (p *PublicPost) augmentContent() {
p.Post.augmentContent(&p.Collection.Collection)
}
func applyMarkdown(data []byte, baseURL string, cfg *config.Config) string { func applyMarkdown(data []byte, baseURL string, cfg *config.Config) string {
return applyMarkdownSpecial(data, false, baseURL, cfg) return applyMarkdownSpecial(data, false, baseURL, cfg)
} }

View File

@ -1140,6 +1140,7 @@ func (p *PublicPost) ActivityObject(app *App) *activitystreams.Object {
p.Collection.FederatedAccount() + "/followers", p.Collection.FederatedAccount() + "/followers",
} }
o.Name = p.DisplayTitle() o.Name = p.DisplayTitle()
p.augmentContent()
if p.HTMLContent == template.HTML("") { if p.HTMLContent == template.HTML("") {
p.formatContent(cfg, false) p.formatContent(cfg, false)
} }
@ -1430,6 +1431,8 @@ Are you sure it was ever here?`,
return impart.HTTPError{http.StatusGone, "Post was unpublished."} return impart.HTTPError{http.StatusGone, "Post was unpublished."}
} }
p.augmentContent()
// Serve collection post // Serve collection post
if isRaw { if isRaw {
contentType := "text/plain" contentType := "text/plain"

View File

@ -5,6 +5,15 @@
{{define "collection"}} {{define "collection"}}
{{template "header" .}} {{template "header" .}}
<style>
textarea.section.norm {
font-family: Lora,'Palatino Linotype','Book Antiqua','New York','DejaVu serif',serif !important;
min-height: 10em;
max-height: 20em;
resize: vertical;
}
</style>
<div class="content-container snug"> <div class="content-container snug">
<div id="overlay"></div> <div id="overlay"></div>
@ -129,6 +138,14 @@
</div> </div>
</div> </div>
<div class="option">
<h2>Post Signature</h2>
<div class="section">
<p class="explain">This content will be added to the end of every post on this blog, as if it were part of the post itself. Markdown, HTML, and shortcodes are allowed.</p>
<textarea id="signature" class="section norm" name="signature">{{.Signature}}</textarea>
</div>
</div>
<div class="option" style="text-align: center; margin-top: 4em;"> <div class="option" style="text-align: center; margin-top: 4em;">
<input type="submit" id="save-changes" value="Save changes" /> <input type="submit" id="save-changes" value="Save changes" />
<p><a href="{{if .SingleUser}}/{{else}}/{{.Alias}}/{{end}}">View Blog</a></p> <p><a href="{{if .SingleUser}}/{{else}}/{{.Alias}}/{{end}}">View Blog</a></p>