Support post signatures Ref T582pull/360/head
@@ -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"` | ||||
} | } | ||||
@@ -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 | |||||
row := db.QueryRow("SELECT id, alias, title, description, style_sheet, script, format, owner_id, privacy, view_count FROM collections WHERE "+condition, value) | |||||
var styleSheet, script, signature, format zero.String | |||||
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 | ||||
@@ -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 | |||||
} |
@@ -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 | ||||
@@ -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 | |||||
} |
@@ -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) | ||||
} | } | ||||
@@ -1141,6 +1141,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) | ||||
} | } | ||||
@@ -1432,6 +1433,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" | ||||
@@ -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> | ||||