Fully support single-user mode

- New editor nav
- New backend nav
- Support for drafts
- Different footers on backend
This commit is contained in:
Matt Baer 2018-11-09 22:10:46 -05:00
parent aecbc3c014
commit 001fc8bb2d
21 changed files with 148 additions and 55 deletions

View File

@ -17,7 +17,7 @@ body {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
color: #111; color: #111;
h1 { h1, header h2 {
a { a {
color: @headerTextColor; color: @headerTextColor;
.transition-duration(0.2s); .transition-duration(0.2s);
@ -1161,9 +1161,11 @@ header {
text-transform: uppercase; text-transform: uppercase;
margin-left: 0.5em; margin-left: 0.5em;
margin-right: 0.5em; margin-right: 0.5em;
&.pro { }
color: @proSelectedCol; &.title {
} font-size: 1.6em;
font-family: @serifFont;
font-weight: bold;
} }
} }
nav > ul > li:first-child { nav > ul > li:first-child {
@ -1188,9 +1190,22 @@ header {
} }
} }
&.tabs { &.tabs {
margin: 0 0 0 1em;
}
&+ nav.tabs {
margin: 0; margin: 0;
} }
} }
&.singleuser {
margin: 0.5em 0.25em;
nav#user-nav {
nav > ul > li:first-child {
img {
top: -0.75em;
}
}
}
}
.dash-nav { .dash-nav {
font-weight: bold; font-weight: bold;
} }

View File

@ -39,7 +39,7 @@ body {
header { header {
margin: 1em; margin: 1em;
h1 { h1, h2 {
display: inline; display: inline;
} }
nav { nav {

View File

@ -12,15 +12,23 @@ type nodeInfoResolver struct {
db *datastore db *datastore
} }
func nodeInfoConfig(cfg *config.Config) *nodeinfo.Config { func nodeInfoConfig(db *datastore, cfg *config.Config) *nodeinfo.Config {
name := cfg.App.SiteName name := cfg.App.SiteName
desc := "Minimal, federated blogging platform."
if cfg.App.SingleUser {
// Fetch blog information, instead
coll, err := db.GetCollectionByID(1)
if err == nil {
desc = coll.Description
}
}
return &nodeinfo.Config{ return &nodeinfo.Config{
BaseURL: cfg.App.Host, BaseURL: cfg.App.Host,
InfoURL: "/api/nodeinfo", InfoURL: "/api/nodeinfo",
Metadata: nodeinfo.Metadata{ Metadata: nodeinfo.Metadata{
NodeName: name, NodeName: name,
NodeDescription: "Minimal, federated blogging platform.", NodeDescription: desc,
Private: cfg.App.Private, Private: cfg.App.Private,
Software: nodeinfo.SoftwareMeta{ Software: nodeinfo.SoftwareMeta{
HomePage: softwareURL, HomePage: softwareURL,

9
pad.go
View File

@ -14,6 +14,14 @@ func handleViewPad(app *app, w http.ResponseWriter, r *http.Request) error {
action := vars["action"] action := vars["action"]
slug := vars["slug"] slug := vars["slug"]
collAlias := vars["collection"] collAlias := vars["collection"]
if app.cfg.App.SingleUser {
// TODO: refactor all of this, especially for single-user blogs
c, err := app.db.GetCollectionByID(1)
if err != nil {
return err
}
collAlias = c.Alias
}
appData := &struct { appData := &struct {
page.StaticPage page.StaticPage
Post *RawPost Post *RawPost
@ -52,6 +60,7 @@ func handleViewPad(app *app, w http.ResponseWriter, r *http.Request) error {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Expires", "Thu, 04 Oct 1990 20:00:00 GMT") w.Header().Set("Expires", "Thu, 04 Oct 1990 20:00:00 GMT")
if slug != "" { if slug != "" {
// TODO: refactor all of this, especially for single-user blogs
appData.Post = getRawCollectionPost(app, slug, collAlias) appData.Post = getRawCollectionPost(app, slug, collAlias)
if appData.Post.OwnerID != appData.User.ID { if appData.Post.OwnerID != appData.User.ID {
// TODO: add ErrForbiddenEditPost message to flashes // TODO: add ErrForbiddenEditPost message to flashes

View File

@ -1,6 +1,6 @@
{{define "head"}}<title>Log in &mdash; {{.SiteName}}</title> {{define "head"}}<title>Log in &mdash; {{.SiteName}}</title>
<meta name="description" content="Log in to access your blogs, published posts, and linked accounts."> <meta name="description" content="Log in to {{.SiteName}}.">
<meta itemprop="description" content="Log in to access your blogs, published posts, and linked accounts."> <meta itemprop="description" content="Log in to {{.SiteName}}.">
<style>input{margin-bottom:0.5em;}</style> <style>input{margin-bottom:0.5em;}</style>
{{end}} {{end}}
{{define "content"}} {{define "content"}}
@ -18,7 +18,7 @@
<input type="submit" id="btn-login" value="Login" /> <input type="submit" id="btn-login" value="Login" />
</form> </form>
<p style="text-align:center;font-size:0.9em;margin:3em auto;max-width:26em;">{{if .Message}}{{.Message}}{{else}}<em>No account yet?</em> <a href="/new/blog">Sign up</a> to start a blog.{{end}}</p> {{if and (not .SingleUser) .OpenRegistration}}<p style="text-align:center;font-size:0.9em;margin:3em auto;max-width:26em;">{{if .Message}}{{.Message}}{{else}}<em>No account yet?</em> <a href="/">Sign up</a> to start a blog.{{end}}</p>{{end}}
<script type="text/javascript"> <script type="text/javascript">
function disableSubmit() { function disableSubmit() {

View File

@ -27,7 +27,7 @@ var (
func (p *Post) formatContent(c *Collection, isOwner bool) { func (p *Post) formatContent(c *Collection, isOwner bool) {
baseURL := c.CanonicalURL() baseURL := c.CanonicalURL()
if isOwner { if !isSingleUser {
baseURL = "/" + c.Alias + "/" baseURL = "/" + c.Alias + "/"
} }
newCon := hashtagReg.ReplaceAllFunc([]byte(p.Content), func(b []byte) []byte { newCon := hashtagReg.ReplaceAllFunc([]byte(p.Content), func(b []byte) []byte {

View File

@ -1255,12 +1255,14 @@ func viewCollectionPost(app *app, w http.ResponseWriter, r *http.Request) error
p.formatContent(cr.isCollOwner) p.formatContent(cr.isCollOwner)
tp := struct { tp := struct {
*PublicPost *PublicPost
page.StaticPage
IsOwner bool IsOwner bool
IsPinned bool IsPinned bool
IsCustomDomain bool IsCustomDomain bool
PinnedPosts *[]PublicPost PinnedPosts *[]PublicPost
}{ }{
PublicPost: p, PublicPost: p,
StaticPage: pageForReq(app, r),
IsOwner: cr.isCollOwner, IsOwner: cr.isCollOwner,
IsCustomDomain: cr.isCustomDomain, IsCustomDomain: cr.isCustomDomain,
} }

View File

@ -39,7 +39,7 @@ func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datasto
// webfinger // webfinger
write.HandleFunc(webfinger.WebFingerPath, handler.LogHandlerFunc(http.HandlerFunc(wf.Webfinger))) write.HandleFunc(webfinger.WebFingerPath, handler.LogHandlerFunc(http.HandlerFunc(wf.Webfinger)))
// nodeinfo // nodeinfo
niCfg := nodeInfoConfig(cfg) niCfg := nodeInfoConfig(db, cfg)
ni := nodeinfo.NewService(*niCfg, nodeInfoResolver{cfg, db}) ni := nodeinfo.NewService(*niCfg, nodeInfoResolver{cfg, db})
write.HandleFunc(nodeinfo.NodeInfoPath, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfoDiscover))) write.HandleFunc(nodeinfo.NodeInfoPath, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfoDiscover)))
write.HandleFunc(niCfg.InfoURL, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfo))) write.HandleFunc(niCfg.InfoURL, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfo)))
@ -118,15 +118,17 @@ func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datasto
// Handle special pages first // Handle special pages first
write.HandleFunc("/login", handler.Web(viewLogin, UserLevelNoneRequired)) write.HandleFunc("/login", handler.Web(viewLogin, UserLevelNoneRequired))
draftEditPrefix := ""
if cfg.App.SingleUser { if cfg.App.SingleUser {
draftEditPrefix = "/d"
write.HandleFunc("/me/new", handler.Web(handleViewPad, UserLevelOptional)).Methods("GET") write.HandleFunc("/me/new", handler.Web(handleViewPad, UserLevelOptional)).Methods("GET")
} else { } else {
write.HandleFunc("/new", handler.Web(handleViewPad, UserLevelOptional)).Methods("GET") write.HandleFunc("/new", handler.Web(handleViewPad, UserLevelOptional)).Methods("GET")
} }
// All the existing stuff // All the existing stuff
write.HandleFunc("/{action}/edit", handler.Web(handleViewPad, UserLevelOptional)).Methods("GET") write.HandleFunc(draftEditPrefix+"/{action}/edit", handler.Web(handleViewPad, UserLevelOptional)).Methods("GET")
write.HandleFunc("/{action}/meta", handler.Web(handleViewMeta, UserLevelOptional)).Methods("GET") write.HandleFunc(draftEditPrefix+"/{action}/meta", handler.Web(handleViewMeta, UserLevelOptional)).Methods("GET")
// Collections // Collections
if cfg.App.SingleUser { if cfg.App.SingleUser {
RouteCollections(handler, write.PathPrefix("/").Subrouter()) RouteCollections(handler, write.PathPrefix("/").Subrouter())
@ -135,8 +137,8 @@ func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datasto
write.HandleFunc("/{collection}/", handler.Web(handleViewCollection, UserLevelOptional)) write.HandleFunc("/{collection}/", handler.Web(handleViewCollection, UserLevelOptional))
RouteCollections(handler, write.PathPrefix("/{prefix:[@~$!\\-+]?}{collection}").Subrouter()) RouteCollections(handler, write.PathPrefix("/{prefix:[@~$!\\-+]?}{collection}").Subrouter())
// Posts // Posts
write.HandleFunc("/{post}", handler.Web(handleViewPost, UserLevelOptional))
} }
write.HandleFunc(draftEditPrefix+"/{post}", handler.Web(handleViewPost, UserLevelOptional))
write.HandleFunc("/", handler.Web(handleViewHome, UserLevelOptional)) write.HandleFunc("/", handler.Web(handleViewHome, UserLevelOptional))
} }

View File

@ -1,6 +1,6 @@
var postActions = function() { var postActions = function() {
var $container = He.get('moving'); var $container = He.get('moving');
var MultiMove = function(el, id) { var MultiMove = function(el, id, singleUser) {
var lbl = el.options[el.selectedIndex].textContent; var lbl = el.options[el.selectedIndex].textContent;
var collAlias = el.options[el.selectedIndex].value; var collAlias = el.options[el.selectedIndex].value;
var $lbl = He.$('label[for=move-'+id+']')[0]; var $lbl = He.$('label[for=move-'+id+']')[0];
@ -18,7 +18,11 @@ var postActions = function() {
for (var i=0; i<resp.data.length; i++) { for (var i=0; i<resp.data.length; i++) {
if (resp.data[i].code == 200) { if (resp.data[i].code == 200) {
$lbl.innerHTML = "moved to <strong>"+lbl+"</strong>"; $lbl.innerHTML = "moved to <strong>"+lbl+"</strong>";
var newPostURL = "/"+collAlias+"/"+resp.data[i].post.slug; var pre = "/"+collAlias;
if (typeof singleUser !== 'undefined' && singleUser) {
pre = "";
}
var newPostURL = pre+"/"+resp.data[i].post.slug;
try { try {
// Posts page // Posts page
He.$('#post-'+resp.data[i].post.id+' > h3 > a')[0].href = newPostURL; He.$('#post-'+resp.data[i].post.id+' > h3 > a')[0].href = newPostURL;
@ -27,7 +31,11 @@ var postActions = function() {
var $article = He.get('post-'+resp.data[i].post.id); var $article = He.get('post-'+resp.data[i].post.id);
$article.className = 'norm moved'; $article.className = 'norm moved';
if (collAlias == '|anonymous|') { if (collAlias == '|anonymous|') {
$article.innerHTML = '<p><a href="/'+resp.data[i].post.id+'">Unpublished post</a>.</p>'; var draftPre = "";
if (typeof singleUser !== 'undefined' && singleUser) {
draftPre = "d/";
}
$article.innerHTML = '<p><a href="/'+draftPre+resp.data[i].post.id+'">Unpublished post</a>.</p>';
} else { } else {
$article.innerHTML = '<p>Moved to <a style="font-weight:bold" href="'+newPostURL+'">'+lbl+'</a>.</p>'; $article.innerHTML = '<p>Moved to <a style="font-weight:bold" href="'+newPostURL+'">'+lbl+'</a>.</p>';
} }
@ -44,7 +52,7 @@ var postActions = function() {
He.postJSON("/api/collections/"+collAlias+"/collect", params, callback); He.postJSON("/api/collections/"+collAlias+"/collect", params, callback);
} }
}; };
var Move = function(el, id, collAlias) { var Move = function(el, id, collAlias, singleUser) {
var lbl = el.textContent; var lbl = el.textContent;
try { try {
var m = lbl.match(/move to (.*)/); var m = lbl.match(/move to (.*)/);
@ -69,7 +77,11 @@ var postActions = function() {
if (resp.data[i].code == 200) { if (resp.data[i].code == 200) {
el.innerHTML = "moved to <strong>"+lbl+"</strong>"; el.innerHTML = "moved to <strong>"+lbl+"</strong>";
el.onclick = null; el.onclick = null;
var newPostURL = "/"+collAlias+"/"+resp.data[i].post.slug; var pre = "/"+collAlias;
if (typeof singleUser !== 'undefined' && singleUser) {
pre = "";
}
var newPostURL = pre+"/"+resp.data[i].post.slug;
el.href = newPostURL; el.href = newPostURL;
el.title = "View on "+lbl; el.title = "View on "+lbl;
try { try {
@ -80,7 +92,11 @@ var postActions = function() {
var $article = He.get('post-'+resp.data[i].post.id); var $article = He.get('post-'+resp.data[i].post.id);
$article.className = 'norm moved'; $article.className = 'norm moved';
if (collAlias == '|anonymous|') { if (collAlias == '|anonymous|') {
$article.innerHTML = '<p><a href="/'+resp.data[i].post.id+'">Unpublished post</a>.</p>'; var draftPre = "";
if (typeof singleUser !== 'undefined' && singleUser) {
draftPre = "d/";
}
$article.innerHTML = '<p><a href="/'+draftPre+resp.data[i].post.id+'">Unpublished post</a>.</p>';
} else { } else {
$article.innerHTML = '<p>Moved to <a style="font-weight:bold" href="'+newPostURL+'">'+lbl+'</a>.</p>'; $article.innerHTML = '<p>Moved to <a style="font-weight:bold" href="'+newPostURL+'">'+lbl+'</a>.</p>';
} }

View File

@ -14,11 +14,11 @@
<div id="overlay"></div> <div id="overlay"></div>
<header> <header>
<h2><a href="/">{{.SiteName}}</a></h2> <h2><a href="/">{{.SiteName}}</a></h2>
{{if .HeaderNav}} {{if not .SingleUser}}
<nav id="user-nav"> <nav id="user-nav">
<nav class="tabs"> <nav class="tabs">
<a href="/about"{{if eq .Path "/about"}} class="selected"{{end}}>About</a> <a href="/about"{{if eq .Path "/about"}} class="selected"{{end}}>About</a>
<a href="/login"{{if eq .Path "/login"}} class="selected"{{end}}>Log in</a> {{if and (not .SingleUser) (not .Username)}}<a href="/login"{{if eq .Path "/login"}} class="selected"{{end}}>Log in</a>{{end}}
</nav> </nav>
</nav> </nav>
{{end}} {{end}}

View File

@ -54,10 +54,10 @@
<h1 dir="{{.Direction}}" id="blog-title"><a rel="author" href="{{if .IsTopLevel}}/{{else}}/{{.Collection.Alias}}/{{end}}" class="h-card p-author">{{.Collection.DisplayTitle}}</a></h1> <h1 dir="{{.Direction}}" id="blog-title"><a rel="author" href="{{if .IsTopLevel}}/{{else}}/{{.Collection.Alias}}/{{end}}" class="h-card p-author">{{.Collection.DisplayTitle}}</a></h1>
<nav> <nav>
{{if .PinnedPosts}} {{if .PinnedPosts}}
{{range .PinnedPosts}}<a class="pinned{{if eq .Slug.String $.Slug.String}} selected{{end}}" href="{{if $.IsOwner}}/{{$.Collection.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL}}{{end}}">{{.PlainDisplayTitle}}</a>{{end}} {{range .PinnedPosts}}<a class="pinned{{if eq .Slug.String $.Slug.String}} selected{{end}}" href="{{if not $.SingleUser}}/{{$.Collection.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL}}{{end}}">{{.PlainDisplayTitle}}</a>{{end}}
{{end}} {{end}}
{{ if .IsOwner }}<span class="views" dir="ltr"><strong>{{largeNumFmt .Views}}</strong> {{pluralize "view" "views" .Views}}</span> {{ if .IsOwner }}<span class="views" dir="ltr"><strong>{{largeNumFmt .Views}}</strong> {{pluralize "view" "views" .Views}}</span>
<a class="xtra-feature" href="/{{.Collection.Alias}}/{{.Slug.String}}/edit" dir="{{.Direction}}">Edit</a> <a class="xtra-feature" href="/{{if not .SingleUser}}{{.Collection.Alias}}/{{end}}{{.Slug.String}}/edit" dir="{{.Direction}}">Edit</a>
{{if .IsPinned}}<a class="xtra-feature unpin" href="/{{.Collection.Alias}}/{{.Slug.String}}/unpin" dir="{{.Direction}}" onclick="unpinPost(event, '{{.ID}}')">Unpin</a>{{end}} {{if .IsPinned}}<a class="xtra-feature unpin" href="/{{.Collection.Alias}}/{{.Slug.String}}/unpin" dir="{{.Direction}}" onclick="unpinPost(event, '{{.ID}}')">Unpin</a>{{end}}
{{ end }} {{ end }}
</nav> </nav>

View File

@ -52,7 +52,7 @@
<h1 dir="{{.Direction}}" id="blog-title"><a href="{{if .IsTopLevel}}/{{else}}/{{.Collection.Alias}}/{{end}}" class="h-card p-author">{{.Collection.DisplayTitle}}</a></h1> <h1 dir="{{.Direction}}" id="blog-title"><a href="{{if .IsTopLevel}}/{{else}}/{{.Collection.Alias}}/{{end}}" class="h-card p-author">{{.Collection.DisplayTitle}}</a></h1>
<nav> <nav>
{{if .PinnedPosts}} {{if .PinnedPosts}}
{{range .PinnedPosts}}<a class="pinned" href="{{if $.IsOwner}}/{{$.Collection.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL}}{{end}}">{{.DisplayTitle}}</a>{{end}} {{range .PinnedPosts}}<a class="pinned" href="{{if not $.SingleUser}}/{{$.Collection.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL}}{{end}}">{{.DisplayTitle}}</a>{{end}}
{{end}} {{end}}
</nav> </nav>
</header> </header>

View File

@ -46,11 +46,15 @@
{{if .IsOwner}}<nav id="manage"><ul> {{if .IsOwner}}<nav id="manage"><ul>
<li><a onclick="void(0)">&#9776; Menu</a> <li><a onclick="void(0)">&#9776; Menu</a>
<ul> <ul>
{{if .SingleUser}}
<li><a href="/me/new">New Post</a></li>
{{else}}
<li><a href="/#{{.Alias}}" class="write">{{.SiteName}}</a></li> <li><a href="/#{{.Alias}}" class="write">{{.SiteName}}</a></li>
{{end}}
<li><a href="/me/c/{{.Alias}}">Customize</a></li> <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}}/stats">Stats</a></li>
<li class="separator"><hr /></li> <li class="separator"><hr /></li>
<li><a href="/me/c/"><img class="ic-18dp" src="/img/ic_blogs_dark@2x.png" /> View Blogs</a></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> <li><a href="/me/posts/"><img class="ic-18dp" src="/img/ic_list_dark@2x.png" /> View Drafts</a></li>
</ul> </ul>
</li> </li>
@ -63,7 +67,7 @@
<!--p class="meta-note"><span>Private collection</span>. Only you can see this page.</p--> <!--p class="meta-note"><span>Private collection</span>. Only you can see this page.</p-->
{{/*end*/}} {{/*end*/}}
{{if .PinnedPosts}}<nav> {{if .PinnedPosts}}<nav>
{{range .PinnedPosts}}<a class="pinned" href="{{if $.IsOwner}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL}}{{end}}">{{.PlainDisplayTitle}}</a>{{end}}</nav> {{range .PinnedPosts}}<a class="pinned" href="{{if not $.SingleUser}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL}}{{end}}">{{.PlainDisplayTitle}}</a>{{end}}</nav>
{{end}} {{end}}
</header> </header>
@ -96,7 +100,7 @@
<footer> <footer>
<hr /> <hr />
<nav dir="ltr"> <nav dir="ltr">
<a class="home pubd" href="/">{{.SiteName}}</a> &middot; powered by <a style="margin-left:0" href="https://writefreely.org">write freely</a> {{if not .SingleUser}}<a class="home pubd" href="/">{{.SiteName}}</a> &middot; {{end}}powered by <a style="margin-left:0" href="https://writefreely.org">write freely</a>
</nav> </nav>
</footer> </footer>
{{ end }} {{ end }}

View File

@ -47,13 +47,13 @@
<div id="belt"> <div id="belt">
<div class="tool if-room"><a href="{{if .EditCollection}}{{.EditCollection.CanonicalURL}}{{.Post.Slug}}/edit{{else}}/{{.Post.Id}}/edit{{end}}" title="Edit post" id="edit"><img class="ic-24dp" src="/img/ic_edit_dark@2x.png" /></a></div> <div class="tool if-room"><a href="{{if .EditCollection}}{{.EditCollection.CanonicalURL}}{{.Post.Slug}}/edit{{else}}/{{.Post.Id}}/edit{{end}}" title="Edit post" id="edit"><img class="ic-24dp" src="/img/ic_edit_dark@2x.png" /></a></div>
<div class="tool if-room room-2"><a href="#theme" title="Toggle theme" id="toggle-theme"><img class="ic-24dp" src="/img/ic_brightness_dark@2x.png" /></a></div> <div class="tool if-room room-2"><a href="#theme" title="Toggle theme" id="toggle-theme"><img class="ic-24dp" src="/img/ic_brightness_dark@2x.png" /></a></div>
<div class="tool if-room room-1"><a href="{{if not .User}}/pad/posts{{else}}/me/posts/{{end}}" title="View posts" id="view-posts"><img class="ic-24dp" src="/img/ic_list_dark@2x.png" /></a></div> <div class="tool if-room room-1"><a href="/me/posts/" title="View posts" id="view-posts"><img class="ic-24dp" src="/img/ic_list_dark@2x.png" /></a></div>
</div> </div>
</header> </header>
<div class="content-container tight"> <div class="content-container tight">
<form action="/api/{{if .EditCollection}}collections/{{.EditCollection.Alias}}/{{end}}posts/{{.Post.Id}}" method="post" onsubmit="return updateMeta()"> <form action="/api/{{if .EditCollection}}collections/{{.EditCollection.Alias}}/{{end}}posts/{{.Post.Id}}" method="post" onsubmit="return updateMeta()">
<h2>Edit metadata: {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}} <a href="/{{if .EditCollection}}{{.EditCollection.Alias}}/{{.Post.Slug}}{{else}}{{.Post.Id}}{{end}}">view post</a></h2> <h2>Edit metadata: {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}} <a href="/{{if .EditCollection}}{{.EditCollection.Alias}}/{{.Post.Slug}}{{else}}{{if .SingleUser}}d/{{end}}{{.Post.Id}}{{end}}">view post</a></h2>
{{if .Flashes}}<ul class="errors"> {{if .Flashes}}<ul class="errors">
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}} {{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}

View File

@ -1,6 +1,15 @@
{{define "footer"}} {{define "footer"}}
<footer class="contain-me"> <footer{{if not .SingleUser}} class="contain-me"{{end}}>
<hr /> <hr />
{{if .SingleUser}}
<nav>
<a class="home" href="/">{{.SiteName}}</a>
<a href="https://writefreely.org/guide" target="guide">writer's guide</a>
<a href="https://developers.write.as/" title="Build on Write Freely 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>
</nav>
{{else}}
<div class="marketing-section"> <div class="marketing-section">
<div class="clearfix blurbs"> <div class="clearfix blurbs">
<div class="half"> <div class="half">
@ -21,5 +30,6 @@
</div> </div>
</div> </div>
</div> </div>
{{end}}
</footer> </footer>
{{end}} {{end}}

View File

@ -1,13 +1,13 @@
{{ define "posts" }} {{ define "posts" }}
{{ range $el := .Posts }}<article id="post-{{.ID}}" class="{{.Font}} h-entry" itemscope itemtype="http://schema.org/BlogPosting"> {{ range $el := .Posts }}<article id="post-{{.ID}}" class="{{.Font}} h-entry" itemscope itemtype="http://schema.org/BlogPosting">
{{if .IsScheduled}}<p class="badge">Scheduled</p>{{end}} {{if .IsScheduled}}<p class="badge">Scheduled</p>{{end}}
{{if .Title.String}}<h2 class="post-title" itemprop="name" class="p-name">{{if .HasTitleLink}}{{.HTMLTitle}} <a class="user hidden action" href="{{if $.IsOwner}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}">view</a>{{else}}<a href="{{if $.IsOwner}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}" itemprop="url" class="u-url">{{.HTMLTitle}}</a>{{end}} {{if .Title.String}}<h2 class="post-title" itemprop="name" class="p-name">{{if .HasTitleLink}}{{.HTMLTitle}} <a class="user hidden action" href="{{if not $.SingleUser}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}">view</a>{{else}}<a href="{{if not $.SingleUser}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}" itemprop="url" class="u-url">{{.HTMLTitle}}</a>{{end}}
{{if $.IsOwner}} {{if $.IsOwner}}
<a class="user hidden action" href="/{{$.Alias}}/{{.Slug.String}}/edit">edit</a> <a class="user hidden action" href="/{{if not $.SingleUser}}{{$.Alias}}/{{end}}{{.Slug.String}}/edit">edit</a>
{{if $.CanPin}}<a class="user hidden pin action" href="/{{$.Alias}}/{{.Slug.String}}/pin" onclick="pinPost(event, '{{.ID}}', '{{.Slug.String}}', '{{.PlainDisplayTitle}}')">pin</a>{{end}} {{if $.CanPin}}<a class="user hidden pin action" href="/{{$.Alias}}/{{.Slug.String}}/pin" onclick="pinPost(event, '{{.ID}}', '{{.Slug.String}}', '{{.PlainDisplayTitle}}')">pin</a>{{end}}
<a class="user hidden delete action" onclick="delPost(event, '{{.ID}}')" href="/{{$.Alias}}/{{.Slug.String}}/delete">delete</a> <a class="user hidden delete action" onclick="delPost(event, '{{.ID}}')" href="/{{$.Alias}}/{{.Slug.String}}/delete">delete</a>
{{if gt (len $.Collections) 1}}<div class="user hidden action flat-select"> {{if gt (len $.Collections) 1}}<div class="user hidden action flat-select">
<select id="move-{{.ID}}" onchange="postActions.multiMove(this, '{{.ID}}')" title="Move this post to another blog"> <select id="move-{{.ID}}" onchange="postActions.multiMove(this, '{{.ID}}', {{if $.SingleUser}}true{{else}}false{{end}})" title="Move this post to another blog">
<option style="display:none"></option> <option style="display:none"></option>
<option value="|anonymous|" style="font-style:italic">Draft</option> <option value="|anonymous|" style="font-style:italic">Draft</option>
{{range $.Collections}}{{if ne .Alias $.Alias}}<option value="{{.Alias}}">{{.DisplayTitle}}</option>{{end}}{{end}} {{range $.Collections}}{{if ne .Alias $.Alias}}<option value="{{.Alias}}">{{.DisplayTitle}}</option>{{end}}{{end}}
@ -16,7 +16,7 @@
<img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" />
</div>{{else}} </div>{{else}}
{{range $.Collections}} {{range $.Collections}}
<a class="user hidden action" href="/{{$el.ID}}" title="Change to a draft" onclick="postActions.move(this, '{{$el.ID}}', '|anonymous|');return false">change to <em>draft</em></a> <a class="user hidden action" href="/{{$el.ID}}" title="Change to a draft" onclick="postActions.move(this, '{{$el.ID}}', '|anonymous|', {{if $.SingleUser}}true{{else}}false{{end}});return false">change to <em>draft</em></a>
{{end}} {{end}}
{{end}} {{end}}
{{end}} {{end}}
@ -24,14 +24,14 @@
{{if $.Format.ShowDates}}<time class="dt-published" datetime="{{.Created}}" pubdate itemprop="datePublished" content="{{.Created}}">{{if not .Title.String}}<a href="{{$.CanonicalURL}}{{.Slug.String}}" itemprop="url">{{end}}{{.DisplayDate}}{{if not .Title.String}}</a>{{end}}</time>{{end}} {{if $.Format.ShowDates}}<time class="dt-published" datetime="{{.Created}}" pubdate itemprop="datePublished" content="{{.Created}}">{{if not .Title.String}}<a href="{{$.CanonicalURL}}{{.Slug.String}}" itemprop="url">{{end}}{{.DisplayDate}}{{if not .Title.String}}</a>{{end}}</time>{{end}}
{{else}} {{else}}
<h2 class="post-title" itemprop="name"> <h2 class="post-title" itemprop="name">
{{if $.Format.ShowDates}}<time class="dt-published" datetime="{{.Created}}" pubdate itemprop="datePublished" content="{{.Created}}"><a href="{{if $.IsOwner}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}" itemprop="url" class="u-url">{{.DisplayDate}}</a></time>{{end}} {{if $.Format.ShowDates}}<time class="dt-published" datetime="{{.Created}}" pubdate itemprop="datePublished" content="{{.Created}}"><a href="{{if not $.SingleUser}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}" itemprop="url" class="u-url">{{.DisplayDate}}</a></time>{{end}}
{{if $.IsOwner}} {{if $.IsOwner}}
{{if not $.Format.ShowDates}}<a class="user hidden action" href="{{if $.IsOwner}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}">view</a>{{end}} {{if not $.Format.ShowDates}}<a class="user hidden action" href="{{if not $.SingleUser}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}">view</a>{{end}}
<a class="user hidden action" href="/{{$.Alias}}/{{.Slug.String}}/edit">edit</a> <a class="user hidden action" href="/{{if not $.SingleUser}}{{$.Alias}}/{{end}}{{.Slug.String}}/edit">edit</a>
{{if $.CanPin}}<a class="user hidden pin action" href="/{{$.Alias}}/{{.Slug.String}}/pin" onclick="pinPost(event, '{{.ID}}', '{{.Slug.String}}', '{{.PlainDisplayTitle}}')">pin</a>{{end}} {{if $.CanPin}}<a class="user hidden pin action" href="/{{if not $.SingleUser}}{{$.Alias}}/{{end}}{{.Slug.String}}/pin" onclick="pinPost(event, '{{.ID}}', '{{.Slug.String}}', '{{.PlainDisplayTitle}}')">pin</a>{{end}}
<a class="user hidden delete action" onclick="delPost(event, '{{.ID}}')" href="/{{$.Alias}}/{{.Slug.String}}/delete">delete</a> <a class="user hidden delete action" onclick="delPost(event, '{{.ID}}')" href="/{{$.Alias}}/{{.Slug.String}}/delete">delete</a>
{{if gt (len $.Collections) 1}}<div class="user hidden action flat-select"> {{if gt (len $.Collections) 1}}<div class="user hidden action flat-select">
<select id="move-{{.ID}}" onchange="postActions.multiMove(this, '{{.ID}}')" title="Move this post to another blog"> <select id="move-{{.ID}}" onchange="postActions.multiMove(this, '{{.ID}}', {{if $.SingleUser}}true{{else}}false{{end}})" title="Move this post to another blog">
<option style="display:none"></option> <option style="display:none"></option>
<option value="|anonymous|" style="font-style:italic">Draft</option> <option value="|anonymous|" style="font-style:italic">Draft</option>
{{range $.Collections}}{{if ne .Alias $.Alias}}<option value="{{.Alias}}">{{.DisplayTitle}}</option>{{end}}{{end}} {{range $.Collections}}{{if ne .Alias $.Alias}}<option value="{{.Alias}}">{{.DisplayTitle}}</option>{{end}}{{end}}
@ -40,7 +40,7 @@
<img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" />
</div>{{else}} </div>{{else}}
{{range $.Collections}} {{range $.Collections}}
<a class="user hidden action" href="/{{$el.ID}}" title="Change to a draft" onclick="postActions.move(this, '{{$el.ID}}', '|anonymous|');return false">change to <em>draft</em></a> <a class="user hidden action" href="/{{$el.ID}}" title="Change to a draft" onclick="postActions.move(this, '{{$el.ID}}', '|anonymous|', {{if $.SingleUser}}true{{else}}false{{end}});return false">change to <em>draft</em></a>
{{end}} {{end}}
{{end}} {{end}}
{{end}} {{end}}

View File

@ -19,9 +19,8 @@
<header id="tools"> <header id="tools">
<div id="clip"> <div id="clip">
<h1>{{if .User}}<a href="/me/c/" title="View blogs"><img class="ic-24dp" src="/img/ic_blogs_dark@2x.png" /></a>{{else}}<a href="/">w<span class="if-room">rite.as</span></a>{{end}} {{if not .SingleUser}}<h1><a href="/me/c/" title="View blogs"><img class="ic-24dp" src="/img/ic_blogs_dark@2x.png" /></a></h1>{{end}}
</h1> <nav id="target" {{if .SingleUser}}style="margin-left:0"{{end}}><ul>
<nav id="target" class=""><ul>
{{if .Editing}}<li>{{if .EditCollection}}<a href="{{.EditCollection.CanonicalURL}}">{{.EditCollection.Title}}</a>{{else}}<a>Draft</a>{{end}}</li> {{if .Editing}}<li>{{if .EditCollection}}<a href="{{.EditCollection.CanonicalURL}}">{{.EditCollection.Title}}</a>{{else}}<a>Draft</a>{{end}}</li>
{{else}}<li><a id="publish-to"><span id="target-name">Draft</span> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /></a> {{else}}<li><a id="publish-to"><span id="target-name">Draft</span> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /></a>
<ul> <ul>
@ -31,7 +30,13 @@
<li class="target" id="blog-{{.Alias}}"><a href="#{{.Alias}}"><i class="material-icons md-18">public</i> {{if .Title}}{{.Title}}{{else}}{{.Alias}}{{end}}</a></li> <li class="target" id="blog-{{.Alias}}"><a href="#{{.Alias}}"><i class="material-icons md-18">public</i> {{if .Title}}{{.Title}}{{else}}{{.Alias}}{{end}}</a></li>
{{end}}{{end}} {{end}}{{end}}
<li id="user-separator" class="separator"><hr /></li> <li id="user-separator" class="separator"><hr /></li>
{{ if .SingleUser }}
<li><a href="/"><i class="material-icons md-18">launch</i> View Blog</a></li>
<li><a href="/me/c/{{.Username}}"><i class="material-icons md-18">palette</i> Customize</a></li>
<li><a href="/me/c/{{.Username}}/stats"><i class="material-icons md-18">trending_up</i> Stats</a></li>
{{ else }}
<li><a href="/me/c/"><i class="material-icons md-18">library_books</i> View Blogs</a></li> <li><a href="/me/c/"><i class="material-icons md-18">library_books</i> View Blogs</a></li>
{{ end }}
<li><a href="/me/posts/"><i class="material-icons md-18">view_list</i> View Drafts</a></li> <li><a href="/me/posts/"><i class="material-icons md-18">view_list</i> View Drafts</a></li>
<li><a href="/me/logout"><i class="material-icons md-18">power_settings_new</i> Log out</a></li> <li><a href="/me/logout"><i class="material-icons md-18">power_settings_new</i> Log out</a></li>
</ul> </ul>
@ -51,7 +56,7 @@
</div> </div>
<noscript style="margin-left: 2em;"><strong>NOTE</strong>: for now, you'll need Javascript enabled to post.</noscript> <noscript style="margin-left: 2em;"><strong>NOTE</strong>: for now, you'll need Javascript enabled to post.</noscript>
<div id="belt"> <div id="belt">
{{if .Editing}}<div class="tool hidden if-room"><a href="{{if .EditCollection}}{{.EditCollection.CanonicalURL}}{{.Post.Slug}}/edit/meta{{else}}/{{.Post.Id}}/meta{{end}}" title="Edit post metadata" id="edit-meta"><img class="ic-24dp" src="/img/ic_info_dark@2x.png" /></a></div>{{end}} {{if .Editing}}<div class="tool hidden if-room"><a href="{{if .EditCollection}}{{.EditCollection.CanonicalURL}}{{.Post.Slug}}/edit/meta{{else}}/{{if .SingleUser}}d/{{end}}{{.Post.Id}}/meta{{end}}" title="Edit post metadata" id="edit-meta"><img class="ic-24dp" src="/img/ic_info_dark@2x.png" /></a></div>{{end}}
<div class="tool hidden if-room room-2"><a href="#theme" title="Toggle theme" id="toggle-theme"><img class="ic-24dp" src="/img/ic_brightness_dark@2x.png" /></a></div> <div class="tool hidden if-room room-2"><a href="#theme" title="Toggle theme" id="toggle-theme"><img class="ic-24dp" src="/img/ic_brightness_dark@2x.png" /></a></div>
<div class="tool if-room room-1"><a href="{{if not .User}}/pad/posts{{else}}/me/posts/{{end}}" title="View posts" id="view-posts"><img class="ic-24dp" src="/img/ic_list_dark@2x.png" /></a></div> <div class="tool if-room room-1"><a href="{{if not .User}}/pad/posts{{else}}/me/posts/{{end}}" title="View posts" id="view-posts"><img class="ic-24dp" src="/img/ic_list_dark@2x.png" /></a></div>
<div class="tool"><a href="#publish" title="Publish" id="publish"><img class="ic-24dp" src="/img/ic_send_dark@2x.png" /></a></div> <div class="tool"><a href="#publish" title="Publish" id="publish"><img class="ic-24dp" src="/img/ic_send_dark@2x.png" /></a></div>
@ -180,12 +185,12 @@
if (http.status == 200 || http.status == 201) { if (http.status == 200 || http.status == 201) {
data = JSON.parse(http.responseText); data = JSON.parse(http.responseText);
id = data.data.id; id = data.data.id;
nextURL = '/'+id; nextURL = '{{if .SingleUser}}/d{{end}}/'+id;
{{ if not .Post.Id }} {{ if not .Post.Id }}
// Post created // Post created
if (postTarget != 'anonymous') { if (postTarget != 'anonymous') {
nextURL = '/'+postTarget+'/'+data.data.slug; nextURL = {{if not .SingleUser}}'/'+postTarget+{{end}}'/'+data.data.slug;
} }
editToken = data.data.token; editToken = data.data.token;

View File

@ -42,7 +42,7 @@
{{if .IsCode}}<a href="/{{.ID}}.txt" rel="noindex" dir="{{.Direction}}">View raw</a>{{end}} {{if .IsCode}}<a href="/{{.ID}}.txt" rel="noindex" dir="{{.Direction}}">View raw</a>{{end}}
{{ if .Username }} {{ if .Username }}
{{if .IsOwner}} {{if .IsOwner}}
<a href="/{{.ID}}/edit" dir="{{.Direction}}">Edit</a> <a href="/{{if .SingleUser}}d/{{end}}{{.ID}}/edit" dir="{{.Direction}}">Edit</a>
{{end}} {{end}}
<a class="xtra-feature dash-nav" href="/me/posts/" dir="{{.Direction}}">Drafts</a> <a class="xtra-feature dash-nav" href="/me/posts/" dir="{{.Direction}}">Drafts</a>
{{ end }} {{ end }}

View File

@ -11,14 +11,14 @@
{{ if .AnonymousPosts }}<div class="atoms posts"> {{ if .AnonymousPosts }}<div class="atoms posts">
{{ range $el := .AnonymousPosts }}<div id="post-{{.ID}}" class="post"> {{ range $el := .AnonymousPosts }}<div id="post-{{.ID}}" class="post">
<h3><a href="/{{.ID}}" itemprop="url">{{.DisplayTitle}}</a></h3> <h3><a href="/{{if $.SingleUser}}d/{{end}}{{.ID}}" itemprop="url">{{.DisplayTitle}}</a></h3>
<h4> <h4>
<date datetime="{{.Created}}" pubdate itemprop="datePublished" content="{{.Created}}">{{.DisplayDate}}</date> <date datetime="{{.Created}}" pubdate itemprop="datePublished" content="{{.Created}}">{{.DisplayDate}}</date>
<a class="action" href="/{{.ID}}/edit">edit</a> <a class="action" href="/{{if $.SingleUser}}d/{{end}}{{.ID}}/edit">edit</a>
<a class="delete action" href="/{{.ID}}" onclick="delPost(event, '{{.ID}}', true)">delete</a> <a class="delete action" href="/{{.ID}}" onclick="delPost(event, '{{.ID}}', true)">delete</a>
{{ if $.Collections }} {{ if $.Collections }}
{{if gt (len $.Collections) 1}}<div class="action flat-select"> {{if gt (len $.Collections) 1}}<div class="action flat-select">
<select id="move-{{.ID}}" onchange="postActions.multiMove(this, '{{.ID}}')" title="Move this post to one of your blogs"> <select id="move-{{.ID}}" onchange="postActions.multiMove(this, '{{.ID}}', {{if $.SingleUser}}true{{else}}false{{end}})" title="Move this post to one of your blogs">
<option style="display:none"></option> <option style="display:none"></option>
{{range $.Collections}}<option value="{{.Alias}}">{{.DisplayTitle}}</option>{{end}} {{range $.Collections}}<option value="{{.Alias}}">{{.DisplayTitle}}</option>{{end}}
</select> </select>
@ -26,7 +26,7 @@
<img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" />
</div>{{else}} </div>{{else}}
{{range $.Collections}} {{range $.Collections}}
<a class="action" href="/{{$el.ID}}" title="Publish this post to your blog '{{.DisplayTitle}}'" onclick="postActions.move(this, '{{$el.ID}}', '{{.Alias}}');return false">move to {{.DisplayTitle}}</a> <a class="action" href="/{{$el.ID}}" title="Publish this post to your blog '{{.DisplayTitle}}'" onclick="postActions.move(this, '{{$el.ID}}', '{{.Alias}}', {{if $.SingleUser}}true{{else}}false{{end}});return false">move to {{.DisplayTitle}}</a>
{{end}} {{end}}
{{end}} {{end}}
{{ end }} {{ end }}

View File

@ -30,7 +30,7 @@
{{.FriendlyHost}}/<strong>{{.Alias}}</strong>/ {{.FriendlyHost}}/<strong>{{.Alias}}</strong>/
</li> </li>
<li> <li>
<strong id="normal-handle-env" class="fedi-handle" {{if or (not .Federation) .SingleUser}}style="display:none"{{end}}>@<span id="fedi-handle">{{.Alias}}</span>@<span id="fedi-domain">{{.FriendlyHost}}</span></strong> <strong id="normal-handle-env" class="fedi-handle" {{if not .Federation}}style="display:none"{{end}}>@<span id="fedi-handle">{{.Alias}}</span>@<span id="fedi-domain">{{.FriendlyHost}}</span></strong>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -15,7 +15,28 @@
<link rel="apple-touch-icon" sizes="180x180" href="/img/touch-icon-180.png"> <link rel="apple-touch-icon" sizes="180x180" href="/img/touch-icon-180.png">
</head> </head>
<body id="me"> <body id="me">
<header> <header{{if .SingleUser}} class="singleuser"{{end}}>
{{if .SingleUser}}
<nav id="user-nav">
<nav class="dropdown-nav">
<ul><li><a href="/" title="View blog" class="title">{{.SiteName}}</a> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" />
<ul>
<li><a href="/me/c/{{.Username}}">Customize</a></li>
<li><a href="/me/c/{{.Username}}/stats">Stats</a></li>
<li class="separator"><hr /></li>
<li><a href="/me/settings">Settings</a></li>
<li><a href="/me/export">Export</a></li>
<li class="separator"><hr /></li>
<li><a href="/me/logout">Log out</a></li>
</ul></li>
</ul>
</nav>
<nav class="tabs">
<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a>
<a href="/me/new">New Post</a>
</nav>
</nav>
{{else}}
<h1><a href="/" title="Return to editor">{{.SiteName}}</a></h1> <h1><a href="/" title="Return to editor">{{.SiteName}}</a></h1>
<nav id="user-nav"> <nav id="user-nav">
<nav class="dropdown-nav"> <nav class="dropdown-nav">
@ -32,6 +53,7 @@
<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a> <a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a>
</nav> </nav>
</nav> </nav>
{{end}}
</header> </header>
<div id="official-writing"> <div id="official-writing">
{{end}} {{end}}