Add Subscribers page

- Shows all fediverse followers and email subscribers
- Shows number of email subscribers on Stats page
- Links to Subscribers page from Stats page

Requires running `make ui` to regenerate stylesheet.

Ref T826
This commit is contained in:
Matt Baer 2023-09-25 16:55:57 -04:00
parent c6323dba8c
commit 82e7dcd3f3
7 changed files with 217 additions and 15 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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")

View File

@ -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 &rarr;</a>
</nav>
</header>

View File

@ -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}}

View File

@ -0,0 +1,96 @@
{{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}}