Add Subscribers page Closes T826verify-collection-max-lengths
@@ -1059,17 +1059,20 @@ func viewStats(app *App, u *User, w http.ResponseWriter, r *http.Request) error | |||
} | |||
obj := struct { | |||
*UserPage | |||
VisitsBlog string | |||
Collection *Collection | |||
TopPosts *[]PublicPost | |||
APFollowers int | |||
Silenced bool | |||
VisitsBlog string | |||
Collection *Collection | |||
TopPosts *[]PublicPost | |||
APFollowers int | |||
EmailEnabled bool | |||
EmailSubscribers int | |||
Silenced bool | |||
}{ | |||
UserPage: NewUserPage(app, r, u, titleStats+"Stats", flashes), | |||
VisitsBlog: alias, | |||
Collection: c, | |||
TopPosts: topPosts, | |||
Silenced: silenced, | |||
UserPage: NewUserPage(app, r, u, titleStats+"Stats", flashes), | |||
VisitsBlog: alias, | |||
Collection: c, | |||
TopPosts: topPosts, | |||
EmailEnabled: app.cfg.Email.Enabled(), | |||
Silenced: silenced, | |||
} | |||
obj.UserPage.CollAlias = c.Alias | |||
if app.cfg.App.Federation { | |||
@@ -1079,11 +1082,73 @@ func viewStats(app *App, u *User, w http.ResponseWriter, r *http.Request) error | |||
} | |||
obj.APFollowers = len(*folls) | |||
} | |||
if obj.EmailEnabled { | |||
subs, err := app.db.GetEmailSubscribers(c.ID, true) | |||
if err != nil { | |||
return err | |||
} | |||
obj.EmailSubscribers = len(subs) | |||
} | |||
showUserPage(w, "stats", obj) | |||
return nil | |||
} | |||
func handleViewSubscribers(app *App, u *User, w http.ResponseWriter, r *http.Request) error { | |||
vars := mux.Vars(r) | |||
c, err := app.db.GetCollection(vars["collection"]) | |||
if err != nil { | |||
return err | |||
} | |||
filter := r.FormValue("filter") | |||
flashes, _ := getSessionFlashes(app, w, r, nil) | |||
obj := struct { | |||
*UserPage | |||
Collection CollectionNav | |||
EmailSubs []*EmailSubscriber | |||
Followers *[]RemoteUser | |||
Silenced bool | |||
Filter string | |||
FederationEnabled bool | |||
CanEmailSub bool | |||
CanAddSubs bool | |||
EmailSubsEnabled bool | |||
}{ | |||
UserPage: NewUserPage(app, r, u, c.DisplayTitle()+" Subscribers", flashes), | |||
Collection: CollectionNav{ | |||
Collection: c, | |||
Path: r.URL.Path, | |||
SingleUser: app.cfg.App.SingleUser, | |||
}, | |||
Silenced: u.IsSilenced(), | |||
Filter: filter, | |||
FederationEnabled: app.cfg.App.Federation, | |||
CanEmailSub: app.cfg.Email.Enabled(), | |||
EmailSubsEnabled: c.EmailSubsEnabled(), | |||
} | |||
obj.Followers, err = app.db.GetAPFollowers(c) | |||
if err != nil { | |||
return err | |||
} | |||
obj.EmailSubs, err = app.db.GetEmailSubscribers(c.ID, true) | |||
if err != nil { | |||
return err | |||
} | |||
if obj.Filter == "" { | |||
// Set permission to add email subscribers | |||
//obj.CanAddSubs = app.db.GetUserAttribute(c.OwnerID, userAttrCanAddEmailSubs) == "1" | |||
} | |||
showUserPage(w, "subscribers", obj) | |||
return nil | |||
} | |||
func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) error { | |||
fullUser, err := app.db.GetUserByID(u.ID) | |||
if err != nil { | |||
@@ -84,6 +84,14 @@ type ( | |||
TotalPages int | |||
Silenced bool | |||
} | |||
CollectionNav struct { | |||
*Collection | |||
Path string | |||
SingleUser bool | |||
CanPost bool | |||
} | |||
SubmittedCollection struct { | |||
// Data used for updating a given collection | |||
ID int64 | |||
@@ -60,6 +60,35 @@ nav#admin { | |||
background: #ccc; | |||
} | |||
} | |||
&.sub { | |||
margin: 1em 0 2em; | |||
a:not(.toggle) { | |||
border: 0; | |||
border-bottom: 2px transparent solid; | |||
.rounded(0); | |||
padding: 0.5em; | |||
margin-left: 0.5em; | |||
margin-right: 0.5em; | |||
&:hover { | |||
color: @primary; | |||
background: transparent; | |||
} | |||
&.selected { | |||
color: @primary; | |||
background: transparent; | |||
border-bottom-color: @primary; | |||
} | |||
&+a { | |||
margin-left: 1em; | |||
} | |||
} | |||
a.toggle { | |||
margin-top: -0.5em; | |||
float: right; | |||
} | |||
} | |||
} | |||
.admin-actions { | |||
@@ -99,6 +99,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { | |||
me.HandleFunc("/c/", handler.User(viewCollections)).Methods("GET") | |||
me.HandleFunc("/c/{collection}", handler.User(viewEditCollection)).Methods("GET") | |||
me.HandleFunc("/c/{collection}/stats", handler.User(viewStats)).Methods("GET") | |||
me.HandleFunc("/c/{collection}/subscribers", handler.User(handleViewSubscribers)).Methods("GET") | |||
me.Path("/delete").Handler(csrf.Protect(apper.App().keys.CSRFKey)(handler.User(handleUserDelete))).Methods("POST") | |||
me.HandleFunc("/posts", handler.Redirect("/me/posts/", UserLevelUser)).Methods("GET") | |||
me.HandleFunc("/posts/", handler.User(viewArticles)).Methods("GET") | |||
@@ -54,6 +54,7 @@ | |||
{{if .SimpleNav}}<li><a href="/new#{{.Alias}}">New Post</a></li>{{end}} | |||
<li><a href="/me/c/{{.Alias}}">Customize</a></li> | |||
<li><a href="/me/c/{{.Alias}}/stats">Stats</a></li> | |||
<li><a href="/me/c/{{.Alias}}/subscribers">Subscribers</a></li> | |||
<li class="separator"><hr /></li> | |||
{{if not .SingleUser}}<li><a href="/me/c/"><img class="ic-18dp" src="/img/ic_blogs_dark@2x.png" /> View Blogs</a></li>{{end}} | |||
<li><a href="/me/posts/"><img class="ic-18dp" src="/img/ic_list_dark@2x.png" /> View Drafts</a></li> | |||
@@ -18,6 +18,7 @@ | |||
<nav class="tabs"> | |||
<a href="/me/c/{{.Username}}" {{if and (hasPrefix .Path "/me/c/") (hasSuffix .Path .Username)}}class="selected"{{end}}>Customize</a> | |||
<a href="/me/c/{{.Username}}/stats" {{if hasSuffix .Path "/stats"}}class="selected"{{end}}>Stats</a> | |||
<a href="/me/c/{{.Username}}/subscribers" {{if hasSuffix .Path "/subscribers"}}class="selected"{{end}}>Subscribers</a> | |||
<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a> | |||
</nav> | |||
</nav> | |||
@@ -9,6 +9,7 @@ | |||
{{if .CanPost}}<a href="{{if .SingleUser}}/me/new{{else}}/#{{.Alias}}{{end}}" class="btn gentlecta">New Post</a>{{end}} | |||
<a href="/me/c/{{.Alias}}" {{if and (hasPrefix .Path "/me/c/") (hasSuffix .Path .Alias)}}class="selected"{{end}}>Customize</a> | |||
<a href="/me/c/{{.Alias}}/stats" {{if hasSuffix .Path "/stats"}}class="selected"{{end}}>Stats</a> | |||
<a href="/me/c/{{.Alias}}/subscribers" {{if hasSuffix .Path "/subscribers"}}class="selected"{{end}}>Subscribers</a> | |||
<a href="{{if .SingleUser}}/{{else}}/{{.Alias}}/{{end}}">View Blog →</a> | |||
</nav> | |||
</header> | |||
@@ -30,15 +30,17 @@ td.none { | |||
{{end}} | |||
<p>Stats for all time.</p> | |||
{{if .Federation}} | |||
<h3>Fediverse stats</h3> | |||
{{if or .Federation .EmailEnabled}} | |||
<h3>Subscribers</h3> | |||
<table id="fediverse" class="classy export"> | |||
<tr> | |||
<th>Followers</th> | |||
{{if .Federation}}<th>Fediverse Followers</th>{{end}} | |||
{{if .EmailEnabled}}<th>Email Subscribers</th>{{end}} | |||
</tr> | |||
<tr> | |||
<td>{{.APFollowers}}</td> | |||
{{if .Federation}}<td><a href="/me/c/{{.Collection.Alias}}/subscribers?filter=fediverse">{{.APFollowers}}</a></td>{{end}} | |||
{{if .EmailEnabled}}<td><a href="/me/c/{{.Collection.Alias}}/subscribers">{{.EmailSubscribers}}</a></td>{{end}} | |||
</tr> | |||
</table> | |||
{{end}} | |||
@@ -0,0 +1,98 @@ | |||
{{define "subscribers"}} | |||
{{template "header" .}} | |||
<style> | |||
.toolbar { | |||
text-align: right; | |||
margin: 1em 0; | |||
} | |||
</style> | |||
<div class="snug content-container {{if not .CanEmailSub}}clean{{end}}"> | |||
{{if .Silenced}} | |||
{{template "user-silenced"}} | |||
{{end}} | |||
{{if .Collection.Collection}}{{template "collection-breadcrumbs" .}}{{end}} | |||
<h1>Subscribers</h1> | |||
{{if .Collection.Collection}} | |||
{{template "collection-nav" .Collection}} | |||
<nav class="pager sub"> | |||
<a href="/me/c/{{.Collection.Alias}}/subscribers" {{if eq .Filter ""}}class="selected"{{end}}>Email ({{len .EmailSubs}})</a> | |||
<a href="/me/c/{{.Collection.Alias}}/subscribers?filter=fediverse" {{if eq .Filter "fediverse"}}class="selected"{{end}}>Followers ({{len .Followers}})</a> | |||
</nav> | |||
{{end}} | |||
{{if .Flashes -}} | |||
<ul class="errors"> | |||
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}} | |||
</ul> | |||
{{- end}} | |||
{{ if eq .Filter "fediverse" }} | |||
<table class="classy export"> | |||
<tr> | |||
<th style="width: 60%">Username</th> | |||
<th colspan="2">Since</th> | |||
</tr> | |||
{{if and (gt (len .Followers) 0) (not .FederationEnabled)}} | |||
<div class="alert info"> | |||
<p><strong>Federation is disabled on this server</strong>, so followers won't receive any new posts.</p> | |||
</div> | |||
{{end}} | |||
{{ if gt (len .Followers) 0 }} | |||
{{range $el := .Followers}} | |||
<tr> | |||
<td><a href="{{.ActorID}}">@{{.EstimatedHandle}}</a></td> | |||
<td>{{.CreatedFriendly}}</td> | |||
</tr> | |||
{{end}} | |||
{{ else }} | |||
<tr> | |||
<td colspan="2">No followers yet.</td> | |||
</tr> | |||
{{ end }} | |||
</table> | |||
{{ else }} | |||
{{if or .CanEmailSub .EmailSubs}} | |||
{{if not .CanEmailSub}} | |||
<div class="alert info"> | |||
<p><strong>Email subscriptions are disabled on this server</strong>, so no new emails will be sent out.</p> | |||
</div> | |||
{{end}} | |||
{{if not .EmailSubsEnabled}} | |||
<div class="alert info"> | |||
<p><strong>Email subscriptions are disabled</strong>. {{if .EmailSubs}}No new emails will be sent out.{{end}} To enable email subscriptions, turn the option on from your blog's <a href="/me/c/{{.Collection.Alias}}#updates">Customize</a> page.</p> | |||
</div> | |||
{{end}} | |||
<table class="classy export"> | |||
<tr> | |||
<th style="width: 60%">Email Address</th> | |||
<th colspan="2">Since</th> | |||
</tr> | |||
{{ if .EmailSubs }} | |||
{{range $el := .EmailSubs}} | |||
<tr> | |||
<td><a href="mailto:{{.Email.String}}">{{.Email.String}}</a></td> | |||
<td>{{.SubscribedFriendly}}</td> | |||
</tr> | |||
{{end}} | |||
{{ else }} | |||
<tr> | |||
<td colspan="2">No subscribers yet.</td> | |||
</tr> | |||
{{ end }} | |||
</table> | |||
{{end}} | |||
{{ end }} | |||
</div> | |||
{{template "foot" .}} | |||
{{template "body-end" .}} | |||
{{end}} |