Merge branch 'develop' into chorus
This commit is contained in:
commit
deec914ccb
|
@ -168,7 +168,7 @@ func signupWithRegistration(app *App, signup userRegistration, w http.ResponseWr
|
|||
}
|
||||
|
||||
// Create actual user
|
||||
if err := app.db.CreateUser(u, desiredUsername); err != nil {
|
||||
if err := app.db.CreateUser(app.cfg, u, desiredUsername); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
1
admin.go
1
admin.go
|
@ -391,6 +391,7 @@ func handleAdminUpdateConfig(apper Apper, u *User, w http.ResponseWriter, r *htt
|
|||
if apper.App().cfg.App.UserInvites == "none" {
|
||||
apper.App().cfg.App.UserInvites = ""
|
||||
}
|
||||
apper.App().cfg.App.DefaultVisibility = r.FormValue("default_visibility")
|
||||
|
||||
m := "?cm=Configuration+saved."
|
||||
err = apper.SaveConfig(apper.App().cfg)
|
||||
|
|
4
app.go
4
app.go
|
@ -558,7 +558,7 @@ func DoConfig(app *App, configSections string) {
|
|||
|
||||
// Create blog
|
||||
log.Info("Creating user %s...\n", u.Username)
|
||||
err = app.db.CreateUser(u, app.cfg.App.SiteName)
|
||||
err = app.db.CreateUser(app.cfg, u, app.cfg.App.SiteName)
|
||||
if err != nil {
|
||||
log.Error("Unable to create user: %s", err)
|
||||
os.Exit(1)
|
||||
|
@ -753,7 +753,7 @@ func CreateUser(apper Apper, username, password string, isAdmin bool) error {
|
|||
userType = "admin"
|
||||
}
|
||||
log.Info("Creating %s %s...", userType, usernameDesc)
|
||||
err = apper.App().db.CreateUser(u, desiredUsername)
|
||||
err = apper.App().db.CreateUser(apper.App().Config(), u, desiredUsername)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create user: %s", err)
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/writeas/web-core/log"
|
||||
waposts "github.com/writeas/web-core/posts"
|
||||
"github.com/writeas/writefreely/author"
|
||||
"github.com/writeas/writefreely/config"
|
||||
"github.com/writeas/writefreely/page"
|
||||
)
|
||||
|
||||
|
@ -126,6 +127,21 @@ const (
|
|||
CollProtected
|
||||
)
|
||||
|
||||
var collVisibilityStrings = map[string]collVisibility{
|
||||
"unlisted": CollUnlisted,
|
||||
"public": CollPublic,
|
||||
"private": CollPrivate,
|
||||
"protected": CollProtected,
|
||||
}
|
||||
|
||||
func defaultVisibility(cfg *config.Config) collVisibility {
|
||||
vis, ok := collVisibilityStrings[cfg.App.DefaultVisibility]
|
||||
if !ok {
|
||||
vis = CollUnlisted
|
||||
}
|
||||
return vis
|
||||
}
|
||||
|
||||
func (cf *CollectionFormat) Ascending() bool {
|
||||
return cf.Format == "novel"
|
||||
}
|
||||
|
@ -362,36 +378,32 @@ func newCollection(app *App, w http.ResponseWriter, r *http.Request) error {
|
|||
return impart.HTTPError{http.StatusBadRequest, fmt.Sprintf("Parameter(s) %srequired.", missingParams)}
|
||||
}
|
||||
|
||||
var userID int64
|
||||
if reqJSON && !c.Web {
|
||||
accessToken = r.Header.Get("Authorization")
|
||||
if accessToken == "" {
|
||||
return ErrNoAccessToken
|
||||
}
|
||||
userID = app.db.GetUserID(accessToken)
|
||||
if userID == -1 {
|
||||
return ErrBadAccessToken
|
||||
}
|
||||
} else {
|
||||
u = getUserSession(app, r)
|
||||
if u == nil {
|
||||
return ErrNotLoggedIn
|
||||
}
|
||||
userID = u.ID
|
||||
}
|
||||
|
||||
if !author.IsValidUsername(app.cfg, c.Alias) {
|
||||
return impart.HTTPError{http.StatusPreconditionFailed, "Collection alias isn't valid."}
|
||||
}
|
||||
|
||||
var coll *Collection
|
||||
var err error
|
||||
if accessToken != "" {
|
||||
coll, err = app.db.CreateCollectionFromToken(c.Alias, c.Title, accessToken)
|
||||
if err != nil {
|
||||
// TODO: handle this
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
coll, err = app.db.CreateCollection(c.Alias, c.Title, u.ID)
|
||||
if err != nil {
|
||||
// TODO: handle this
|
||||
return err
|
||||
}
|
||||
coll, err := app.db.CreateCollection(app.cfg, c.Alias, c.Title, userID)
|
||||
if err != nil {
|
||||
// TODO: handle this
|
||||
return err
|
||||
}
|
||||
|
||||
res := &CollectionObj{Collection: *coll}
|
||||
|
|
|
@ -69,6 +69,7 @@ type (
|
|||
WebFonts bool `ini:"webfonts"`
|
||||
Landing string `ini:"landing"`
|
||||
SimpleNav bool `ini:"simple_nav"`
|
||||
WFModesty bool `ini:"wf_modesty"`
|
||||
Chorus bool `ini:"chorus"`
|
||||
|
||||
// Users
|
||||
|
@ -87,6 +88,9 @@ type (
|
|||
// Additional functions
|
||||
LocalTimeline bool `ini:"local_timeline"`
|
||||
UserInvites string `ini:"user_invites"`
|
||||
|
||||
// Defaults
|
||||
DefaultVisibility string `ini:"default_visibility"`
|
||||
}
|
||||
|
||||
// Config holds the complete configuration for running a writefreely instance
|
||||
|
|
25
database.go
25
database.go
|
@ -45,7 +45,7 @@ var (
|
|||
)
|
||||
|
||||
type writestore interface {
|
||||
CreateUser(*User, string) error
|
||||
CreateUser(*config.Config, *User, string) error
|
||||
UpdateUserEmail(keys *key.Keychain, userID int64, email string) error
|
||||
UpdateEncryptedUserEmail(int64, []byte) error
|
||||
GetUserByID(int64) (*User, error)
|
||||
|
@ -83,8 +83,8 @@ type writestore interface {
|
|||
GetOwnedPost(id string, ownerID int64) (*PublicPost, error)
|
||||
GetPostProperty(id string, collectionID int64, property string) (interface{}, error)
|
||||
|
||||
CreateCollectionFromToken(string, string, string) (*Collection, error)
|
||||
CreateCollection(string, string, int64) (*Collection, error)
|
||||
CreateCollectionFromToken(*config.Config, string, string, string) (*Collection, error)
|
||||
CreateCollection(*config.Config, string, string, int64) (*Collection, error)
|
||||
GetCollectionBy(condition string, value interface{}) (*Collection, error)
|
||||
GetCollection(alias string) (*Collection, error)
|
||||
GetCollectionForPad(alias string) (*Collection, error)
|
||||
|
@ -103,7 +103,7 @@ type writestore interface {
|
|||
CanCollect(cpr *ClaimPostRequest, userID int64) bool
|
||||
AttemptClaim(p *ClaimPostRequest, query string, params []interface{}, slugIdx int) (sql.Result, error)
|
||||
DispersePosts(userID int64, postIDs []string) (*[]ClaimPostResult, error)
|
||||
ClaimPosts(userID int64, collAlias string, posts *[]ClaimPostRequest) (*[]ClaimPostResult, error)
|
||||
ClaimPosts(cfg *config.Config, userID int64, collAlias string, posts *[]ClaimPostRequest) (*[]ClaimPostResult, error)
|
||||
|
||||
GetPostsCount(c *CollectionObj, includeFuture bool)
|
||||
GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool) (*[]PublicPost, error)
|
||||
|
@ -163,7 +163,7 @@ func (db *datastore) dateSub(l int, unit string) string {
|
|||
return fmt.Sprintf("DATE_SUB(NOW(), INTERVAL %d %s)", l, unit)
|
||||
}
|
||||
|
||||
func (db *datastore) CreateUser(u *User, collectionTitle string) error {
|
||||
func (db *datastore) CreateUser(cfg *config.Config, u *User, collectionTitle string) error {
|
||||
if db.PostIDExists(u.Username) {
|
||||
return impart.HTTPError{http.StatusConflict, "Invalid collection name."}
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ func (db *datastore) CreateUser(u *User, collectionTitle string) error {
|
|||
if collectionTitle == "" {
|
||||
collectionTitle = u.Username
|
||||
}
|
||||
res, err = t.Exec("INSERT INTO collections (alias, title, description, privacy, owner_id, view_count) VALUES (?, ?, ?, ?, ?, ?)", u.Username, collectionTitle, "", CollUnlisted, u.ID, 0)
|
||||
res, err = t.Exec("INSERT INTO collections (alias, title, description, privacy, owner_id, view_count) VALUES (?, ?, ?, ?, ?, ?)", u.Username, collectionTitle, "", defaultVisibility(cfg), u.ID, 0)
|
||||
if err != nil {
|
||||
t.Rollback()
|
||||
if db.isDuplicateKeyErr(err) {
|
||||
|
@ -239,13 +239,13 @@ func (db *datastore) UpdateEncryptedUserEmail(userID int64, encEmail []byte) err
|
|||
return nil
|
||||
}
|
||||
|
||||
func (db *datastore) CreateCollectionFromToken(alias, title, accessToken string) (*Collection, error) {
|
||||
func (db *datastore) CreateCollectionFromToken(cfg *config.Config, alias, title, accessToken string) (*Collection, error) {
|
||||
userID := db.GetUserID(accessToken)
|
||||
if userID == -1 {
|
||||
return nil, ErrBadAccessToken
|
||||
}
|
||||
|
||||
return db.CreateCollection(alias, title, userID)
|
||||
return db.CreateCollection(cfg, alias, title, userID)
|
||||
}
|
||||
|
||||
func (db *datastore) GetUserCollectionCount(userID int64) (uint64, error) {
|
||||
|
@ -262,13 +262,13 @@ func (db *datastore) GetUserCollectionCount(userID int64) (uint64, error) {
|
|||
return collCount, nil
|
||||
}
|
||||
|
||||
func (db *datastore) CreateCollection(alias, title string, userID int64) (*Collection, error) {
|
||||
func (db *datastore) CreateCollection(cfg *config.Config, alias, title string, userID int64) (*Collection, error) {
|
||||
if db.PostIDExists(alias) {
|
||||
return nil, impart.HTTPError{http.StatusConflict, "Invalid collection name."}
|
||||
}
|
||||
|
||||
// All good, so create new collection
|
||||
res, err := db.Exec("INSERT INTO collections (alias, title, description, privacy, owner_id, view_count) VALUES (?, ?, ?, ?, ?, ?)", alias, title, "", CollUnlisted, userID, 0)
|
||||
res, err := db.Exec("INSERT INTO collections (alias, title, description, privacy, owner_id, view_count) VALUES (?, ?, ?, ?, ?, ?)", alias, title, "", defaultVisibility(cfg), userID, 0)
|
||||
if err != nil {
|
||||
if db.isDuplicateKeyErr(err) {
|
||||
return nil, impart.HTTPError{http.StatusConflict, "Collection already exists."}
|
||||
|
@ -282,6 +282,7 @@ func (db *datastore) CreateCollection(alias, title string, userID int64) (*Colle
|
|||
Title: title,
|
||||
OwnerID: userID,
|
||||
PublicOwner: false,
|
||||
Public: defaultVisibility(cfg) == CollPublic,
|
||||
}
|
||||
|
||||
c.ID, err = res.LastInsertId()
|
||||
|
@ -1326,7 +1327,7 @@ func (db *datastore) DispersePosts(userID int64, postIDs []string) (*[]ClaimPost
|
|||
return &res, nil
|
||||
}
|
||||
|
||||
func (db *datastore) ClaimPosts(userID int64, collAlias string, posts *[]ClaimPostRequest) (*[]ClaimPostResult, error) {
|
||||
func (db *datastore) ClaimPosts(cfg *config.Config, userID int64, collAlias string, posts *[]ClaimPostRequest) (*[]ClaimPostResult, error) {
|
||||
postClaimReqs := map[string]bool{}
|
||||
res := []ClaimPostResult{}
|
||||
postCollAlias := collAlias
|
||||
|
@ -1383,7 +1384,7 @@ func (db *datastore) ClaimPosts(userID int64, collAlias string, posts *[]ClaimPo
|
|||
// This is a new collection
|
||||
// TODO: consider removing this. This seriously complicates this
|
||||
// method and adds another (unnecessary?) logic path.
|
||||
coll, err = db.CreateCollection(postCollAlias, "", userID)
|
||||
coll, err = db.CreateCollection(cfg, postCollAlias, "", userID)
|
||||
if err != nil {
|
||||
if err, ok := err.(impart.HTTPError); ok {
|
||||
r.Code = err.Status
|
||||
|
|
4
pad.go
4
pad.go
|
@ -60,6 +60,10 @@ func handleViewPad(app *App, w http.ResponseWriter, r *http.Request) error {
|
|||
|
||||
if action == "" && slug == "" {
|
||||
// Not editing any post; simply render the Pad
|
||||
if templates[padTmpl] == nil {
|
||||
log.Info("No template '%s' found. Falling back to default 'pad' template.", padTmpl)
|
||||
padTmpl = "pad"
|
||||
}
|
||||
if err = templates[padTmpl].ExecuteTemplate(w, "pad", appData); err != nil {
|
||||
log.Error("Unable to execute template: %v", err)
|
||||
}
|
||||
|
|
4
pages.go
4
pages.go
|
@ -65,9 +65,9 @@ func defaultPrivacyTitle() sql.NullString {
|
|||
|
||||
func defaultAboutPage(cfg *config.Config) string {
|
||||
if cfg.App.Federation {
|
||||
return `_` + cfg.App.SiteName + `_ is an interconnected place for you to write and publish, powered by WriteFreely and ActivityPub.`
|
||||
return `_` + cfg.App.SiteName + `_ is an interconnected place for you to write and publish, powered by [WriteFreely](https://writefreely.org) and ActivityPub.`
|
||||
}
|
||||
return `_` + cfg.App.SiteName + `_ is a place for you to write and publish, powered by WriteFreely.`
|
||||
return `_` + cfg.App.SiteName + `_ is a place for you to write and publish, powered by [WriteFreely](https://writefreely.org).`
|
||||
}
|
||||
|
||||
func defaultPrivacyPolicy(cfg *config.Config) string {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<p><em>{{.SiteName}}</em> is home to <strong>{{largeNumFmt .AboutStats.NumPosts}}</strong> {{pluralize "article" "articles" .AboutStats.NumPosts}} across <strong>{{largeNumFmt .AboutStats.NumBlogs}}</strong> {{pluralize "blog" "blogs" .AboutStats.NumBlogs}}.</p>
|
||||
{{end}}
|
||||
|
||||
{{if not .WFModesty}}
|
||||
<h2 style="margin-top:2em">About WriteFreely</h2>
|
||||
<p><a href="https://writefreely.org">WriteFreely</a> is a self-hosted, decentralized blogging platform for publishing beautiful, simple blogs.</p>
|
||||
<p>It lets you publish a single blog, or host a community of writers who can create multiple blogs under one account. You can also enable federation, which allows people in the fediverse to follow your blog, bookmark your posts, and share them with others.</p>
|
||||
|
@ -23,5 +24,6 @@
|
|||
<p><a href="https://writefreely.org">WriteFreely</a></p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
2
posts.go
2
posts.go
|
@ -870,7 +870,7 @@ func addPost(app *App, w http.ResponseWriter, r *http.Request) error {
|
|||
collAlias := vars["alias"]
|
||||
|
||||
// Update all given posts
|
||||
res, err := app.db.ClaimPosts(ownerID, collAlias, claims)
|
||||
res, err := app.db.ClaimPosts(app.cfg, ownerID, collAlias, claims)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ function pinPost(e, postID, slug, title) {
|
|||
var $header = document.getElementsByTagName('header')[0];
|
||||
var $pinnedNavs = $header.getElementsByTagName('nav');
|
||||
// Add link to nav
|
||||
var link = '<a class="pinned" href="/{{.Alias}}/'+slug+'">'+title+'</a>';
|
||||
var link = '<a class="pinned" href="{{if not .SingleUser}}/{{.Alias}}/{{end}}'+slug+'">'+title+'</a>';
|
||||
if ($pinnedNavs.length == 0) {
|
||||
$header.insertAdjacentHTML("beforeend", '<nav>'+link+'</nav>');
|
||||
} else {
|
||||
|
|
|
@ -177,7 +177,7 @@ function pinPost(e, postID, slug, title) {
|
|||
var $header = document.getElementsByTagName('header')[0];
|
||||
var $pinnedNavs = $header.getElementsByTagName('nav');
|
||||
// Add link to nav
|
||||
var link = '<a class="pinned" href="/{{.Alias}}/'+slug+'">'+title+'</a>';
|
||||
var link = '<a class="pinned" href="{{if not .SingleUser}}/{{.Alias}}/{{end}}'+slug+'">'+title+'</a>';
|
||||
if ($pinnedNavs.length == 0) {
|
||||
$header.insertAdjacentHTML("beforeend", '<nav>'+link+'</nav>');
|
||||
} else {
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
{{define "footer"}}
|
||||
<footer{{if not .SingleUser}} class="contain-me"{{end}}>
|
||||
<footer{{if not (or .SingleUser .WFModesty)}} class="contain-me"{{end}}>
|
||||
<hr />
|
||||
{{if .SingleUser}}
|
||||
{{if or .SingleUser .WFModesty}}
|
||||
<nav>
|
||||
<a class="home" href="/">{{.SiteName}}</a>
|
||||
<a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">writer's guide</a>
|
||||
<a href="https://developers.write.as/" title="Build on WriteFreely with our open developer API.">developers</a>
|
||||
<a href="https://github.com/writeas/writefreely">source code</a>
|
||||
<a href="https://writefreely.org">writefreely {{.Version}}</a>
|
||||
{{if not .SingleUser}}
|
||||
<a href="/about">about</a>
|
||||
{{if .LocalTimeline}}<a href="/read">reader</a>{{end}}
|
||||
{{if .Username}}<a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">writer's guide</a>{{end}}
|
||||
<a href="/privacy">privacy</a>
|
||||
<p style="font-size: 0.9em">powered by <a href="https://writefreely.org">writefreely</a></p>
|
||||
{{else}}
|
||||
<a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">writer's guide</a>
|
||||
<a href="https://developers.write.as/" title="Build on WriteFreely with our open developer API.">developers</a>
|
||||
<a href="https://github.com/writeas/writefreely">source code</a>
|
||||
<a href="https://writefreely.org">writefreely {{.Version}}</a>
|
||||
{{end}}
|
||||
</nav>
|
||||
{{else}}
|
||||
<div class="marketing-section">
|
||||
|
|
|
@ -95,6 +95,14 @@ p.docs {
|
|||
<option value="admin" {{if eq .Config.UserInvites "admin"}}selected="selected"{{end}}>Admins</option>
|
||||
</select>
|
||||
</dd>
|
||||
<dt{{if .Config.SingleUser}} class="invisible"{{end}}><label for="default_visibility">Default blog visibility</label></dt>
|
||||
<dd{{if .Config.SingleUser}} class="invisible"{{end}}>
|
||||
<select name="default_visibility" id="default_visibility">
|
||||
<option value="unlisted" {{if eq .Config.DefaultVisibility "unlisted"}}selected="selected"{{end}}>Unlisted</option>
|
||||
<option value="public" {{if eq .Config.DefaultVisibility "public"}}selected="selected"{{end}}>Public</option>
|
||||
<option value="private" {{if eq .Config.DefaultVisibility "private"}}selected="selected"{{end}}>Private</option>
|
||||
</select>
|
||||
</dd>
|
||||
</dl>
|
||||
<input type="submit" value="Save Configuration" />
|
||||
</div>
|
||||
|
|
|
@ -34,8 +34,8 @@
|
|||
{{if .Summary}}<p>{{.Summary}}</p>{{end}}
|
||||
</div>{{end}}
|
||||
</div>{{ else }}<div id="no-posts-published"><p>You haven't saved any drafts yet.</p>
|
||||
<p>They'll show up here once you do. Find your blog posts from the <a href="/me/c/">Blogs</a> page.</p>
|
||||
<p class="text-cta"><a href="/">Start writing</a></p></div>{{ end }}
|
||||
<p>They'll show up here once you do. {{if not .SingleUser}}Find your blog posts from the <a href="/me/c/">Blogs</a> page.{{end}}</p>
|
||||
<p class="text-cta"><a href="{{if .SingleUser}}/me/new{{else}}/{{end}}">Start writing</a></p></div>{{ end }}
|
||||
|
||||
<div id="moving"></div>
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
</label>
|
||||
<p>A password is required to read this blog.</p>
|
||||
</li>
|
||||
{{if not .SingleUser}}
|
||||
<li>
|
||||
<label class="option-text{{if not .LocalTimeline}} disabled{{end}}"><input type="radio" name="visibility" id="visibility-public" value="1" {{if .IsPublic}}checked="checked"{{end}} {{if not .LocalTimeline}}disabled="disabled"{{end}} />
|
||||
Public
|
||||
|
@ -65,6 +66,7 @@
|
|||
{{if .LocalTimeline}}<p>This blog is displayed on the public <a href="/read">reader</a>, and is visible to {{if .Private}}any registered user on this instance{{else}}anyone with its link{{end}}.</p>
|
||||
{{else}}<p>The public reader is currently turned off for this community.</p>{{end}}
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,11 +8,15 @@
|
|||
<hr />
|
||||
<nav>
|
||||
<a class="home" href="/">{{.SiteName}}</a>
|
||||
<a href="/about">about</a>
|
||||
{{if not .SingleUser}}<a href="/about">about</a>{{end}}
|
||||
{{if and (not .SingleUser) .LocalTimeline}}<a href="/read">reader</a>{{end}}
|
||||
<a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">writer's guide</a>
|
||||
<a href="/privacy">privacy</a>
|
||||
{{if not .SingleUser}}<a href="/privacy">privacy</a>{{end}}
|
||||
{{if .WFModesty}}
|
||||
<p style="font-size: 0.9em">powered by <a href="https://writefreely.org">writefreely</a></p>
|
||||
{{else}}
|
||||
<a href="https://writefreely.org">writefreely {{.Version}}</a>
|
||||
{{end}}
|
||||
</nav>
|
||||
</footer>
|
||||
|
||||
|
|
Loading…
Reference in New Issue