Merge branch 'develop'

pull/95/head
Matt Baer 6 years ago
commit 831209f4b6
  1. 33
      README.md
  2. 23
      admin.go
  3. 4
      app.go
  4. 2
      config.ini.example
  5. 2
      config/setup.go
  6. 24
      database.go
  7. 2
      go.mod
  8. 2
      go.sum
  9. 14
      migrations/drivers.go
  10. 3
      migrations/migrations.go
  11. 35
      migrations/v2.go
  12. 1
      nodeinfo.go
  13. 5
      page/page.go
  14. 19
      pages.go
  15. 4
      pages/about.tmpl
  16. 4
      pages/landing.tmpl
  17. 4
      pages/privacy.tmpl
  18. 2
      postrender.go
  19. 2
      templates.go
  20. 2
      templates/collection-post.tmpl
  21. 2
      templates/collection-tags.tmpl
  22. 4
      templates/collection.tmpl
  23. 8
      templates/include/footer.tmpl
  24. 2
      templates/password-collection.tmpl
  25. 15
      templates/user/admin.tmpl
  26. 10
      templates/user/admin/pages.tmpl
  27. 45
      templates/user/admin/view-page.tmpl
  28. 2
      templates/user/include/footer.tmpl
  29. 2
      templates/user/include/header.tmpl

@ -1,6 +1,6 @@
 
<p align="center">
<a href="https://writefreely.org"><img src="https://writefreely.org/img/writefreely.svg" width="350px" alt="Write Freely" /></a>
<a href="https://writefreely.org"><img src="https://writefreely.org/img/writefreely.svg" width="350px" alt="WriteFreely" /></a>
</p>
<hr />
<p align="center">
@ -120,36 +120,7 @@ make run # Runs the application
## Docker
### Using Docker for Development
If you'd like to use Docker as a base for working on a site's styles and such,
you can run the following from a Bash shell.
*Note: This process is intended only for working on site styling. If you'd
like to run Write Freely in production as a Docker service, it'll require a
little more work.*
The `docker-setup.sh` script will present you with a few questions to set up
your dev instance. You can hit enter for most of them, except for "Admin username"
and "Admin password." You'll probably have to wait a few seconds after running
`docker-compose up -d` for the Docker services to come up before running the
bash script.
```
docker-compose up -d
./docker-setup.sh
```
Now you should be able to navigate to http://localhost:8080 and start working!
When you're completely done working, you can run `docker-compose down` to destroy
your virtual environment, including your database data. Otherwise, `docker-compose stop`
will shut down your environment without destroying your data.
### Using Docker for Production
Write Freely doesn't yet provide an official Docker pathway to production. We're
working on it, though!
Read about using Docker in the [documentation](https://writefreely.org/docs/latest/admin/docker).
## Contributing

@ -11,6 +11,7 @@
package writefreely
import (
"database/sql"
"fmt"
"github.com/gogits/gogs/pkg/tool"
"github.com/gorilla/mux"
@ -81,7 +82,7 @@ type inspectedCollection struct {
type instanceContent struct {
ID string
Type string
Title string
Title sql.NullString
Content string
Updated time.Time
}
@ -249,19 +250,26 @@ func handleViewAdminPages(app *app, u *User, w http.ResponseWriter, r *http.Requ
// Add in default pages
var hasAbout, hasPrivacy bool
for _, c := range p.Pages {
for i, c := range p.Pages {
if hasAbout && hasPrivacy {
break
}
if c.ID == "about" {
hasAbout = true
if !c.Title.Valid {
p.Pages[i].Title = defaultAboutTitle(app.cfg)
}
} else if c.ID == "privacy" {
hasPrivacy = true
if !c.Title.Valid {
p.Pages[i].Title = defaultPrivacyTitle()
}
}
}
if !hasAbout {
p.Pages = append(p.Pages, &instanceContent{
ID: "about",
Title: defaultAboutTitle(app.cfg),
Content: defaultAboutPage(app.cfg),
Updated: defaultPageUpdatedTime,
})
@ -269,6 +277,7 @@ func handleViewAdminPages(app *app, u *User, w http.ResponseWriter, r *http.Requ
if !hasPrivacy {
p.Pages = append(p.Pages, &instanceContent{
ID: "privacy",
Title: defaultPrivacyTitle(),
Content: defaultPrivacyPolicy(app.cfg),
Updated: defaultPageUpdatedTime,
})
@ -308,7 +317,13 @@ func handleViewAdminPage(app *app, u *User, w http.ResponseWriter, r *http.Reque
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get page: %v", err)}
}
p.UserPage = NewUserPage(app, r, u, p.Content.ID, nil)
title := "New page"
if p.Content != nil {
title = "Edit " + p.Content.ID
} else {
p.Content = &instanceContent{}
}
p.UserPage = NewUserPage(app, r, u, title, nil)
showUserPage(w, "view-page", p)
return nil
@ -325,7 +340,7 @@ func handleAdminUpdateSite(app *app, u *User, w http.ResponseWriter, r *http.Req
// Update page
m := ""
err := app.db.UpdateDynamicContent(id, r.FormValue("content"))
err := app.db.UpdateDynamicContent(id, r.FormValue("title"), r.FormValue("content"), "page")
if err != nil {
m = "?m=" + err.Error()
}

@ -54,7 +54,7 @@ var (
debugging bool
// Software version can be set from git env using -ldflags
softwareVer = "0.8.1"
softwareVer = "0.9.0"
// DEPRECATED VARS
// TODO: pass app.cfg into GetCollection* calls so we can get these values
@ -115,6 +115,7 @@ func handleViewHome(app *app, w http.ResponseWriter, r *http.Request) error {
func handleTemplatedPage(app *app, w http.ResponseWriter, r *http.Request, t *template.Template) error {
p := struct {
page.StaticPage
ContentTitle string
Content template.HTML
PlainContent string
Updated string
@ -141,6 +142,7 @@ func handleTemplatedPage(app *app, w http.ResponseWriter, r *http.Request, t *te
if err != nil {
return err
}
p.ContentTitle = c.Title.String
p.Content = template.HTML(applyMarkdown([]byte(c.Content), ""))
p.PlainContent = shortPostDescription(stripmd.Strip(c.Content))
if !c.Updated.IsZero() {

@ -11,7 +11,7 @@ host = db
port = 3306
[app]
site_name = Write Freely Example Blog!
site_name = WriteFreely Example Blog!
host = http://localhost:8080
theme = write
disable_js = false

@ -47,7 +47,7 @@ func Configure(fname string) (*SetupData, error) {
intro := color.New(color.Bold, color.FgWhite).PrintlnFunc()
fmt.Println()
intro(" ✍ Write Freely Configuration ✍")
intro(" ✍ WriteFreely Configuration ✍")
fmt.Println()
fmt.Println(wordwrap.WrapString(" This quick configuration process will "+action+" the application's config file, "+fname+".\n\n It validates your input along the way, so you can be sure any future errors aren't caused by a bad configuration. If you'd rather configure your server manually, instead run: writefreely --create-config and edit that file.", 75))
fmt.Println()

@ -116,7 +116,7 @@ type writestore interface {
CreateInvitedUser(inviteID string, userID int64) error
GetDynamicContent(id string) (*instanceContent, error)
UpdateDynamicContent(id, content string) error
UpdateDynamicContent(id, title, content, contentType string) error
GetAllUsers(page uint) (*[]User, error)
GetAllUsersCount() int64
GetUserLastPostTime(id int64) (*time.Time, error)
@ -2263,7 +2263,17 @@ func (db *datastore) CreateInvitedUser(inviteID string, userID int64) error {
}
func (db *datastore) GetInstancePages() ([]*instanceContent, error) {
rows, err := db.Query("SELECT id, content, updated FROM appcontent")
return db.GetAllDynamicContent("page")
}
func (db *datastore) GetAllDynamicContent(t string) ([]*instanceContent, error) {
where := ""
params := []interface{}{}
if t != "" {
where = " WHERE content_type = ?"
params = append(params, t)
}
rows, err := db.Query("SELECT id, title, content, updated, content_type FROM appcontent"+where, params...)
if err != nil {
log.Error("Failed selecting from appcontent: %v", err)
return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve instance pages."}
@ -2273,7 +2283,7 @@ func (db *datastore) GetInstancePages() ([]*instanceContent, error) {
pages := []*instanceContent{}
for rows.Next() {
c := &instanceContent{}
err = rows.Scan(&c.ID, &c.Content, &c.Updated)
err = rows.Scan(&c.ID, &c.Title, &c.Content, &c.Updated, &c.Type)
if err != nil {
log.Error("Failed scanning row: %v", err)
break
@ -2292,7 +2302,7 @@ func (db *datastore) GetDynamicContent(id string) (*instanceContent, error) {
c := &instanceContent{
ID: id,
}
err := db.QueryRow("SELECT content, updated FROM appcontent WHERE id = ?", id).Scan(&c.Content, &c.Updated)
err := db.QueryRow("SELECT title, content, updated, content_type FROM appcontent WHERE id = ?", id).Scan(&c.Title, &c.Content, &c.Updated, &c.Type)
switch {
case err == sql.ErrNoRows:
return nil, nil
@ -2303,12 +2313,12 @@ func (db *datastore) GetDynamicContent(id string) (*instanceContent, error) {
return c, nil
}
func (db *datastore) UpdateDynamicContent(id, content string) error {
func (db *datastore) UpdateDynamicContent(id, title, content, contentType string) error {
var err error
if db.driverName == driverSQLite {
_, err = db.Exec("INSERT OR REPLACE INTO appcontent (id, content, updated) VALUES (?, ?, "+db.now()+")", id, content)
_, err = db.Exec("INSERT OR REPLACE INTO appcontent (id, title, content, updated, content_type) VALUES (?, ?, ?, "+db.now()+", ?)", id, title, content, contentType)
} else {
_, err = db.Exec("INSERT INTO appcontent (id, content, updated) VALUES (?, ?, "+db.now()+") "+db.upsert("id")+" content = ?, updated = "+db.now(), id, content, content)
_, err = db.Exec("INSERT INTO appcontent (id, title, content, updated, content_type) VALUES (?, ?, ?, "+db.now()+", ?) "+db.upsert("id")+" title = ?, content = ?, updated = "+db.now(), id, title, content, contentType, title, content)
}
if err != nil {
log.Error("Unable to INSERT appcontent for '%s': %v", id, err)

@ -62,7 +62,7 @@ require (
github.com/writeas/saturday v1.7.1
github.com/writeas/slug v1.2.0
github.com/writeas/web-core v1.0.0
github.com/writefreely/go-nodeinfo v1.1.0
github.com/writefreely/go-nodeinfo v1.2.0
golang.org/x/crypto v0.0.0-20190208162236-193df9c0f06f // indirect
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1 // indirect
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006 // indirect

@ -164,6 +164,8 @@ github.com/writeas/web-core v1.0.0 h1:5VKkCakQgdKZcbfVKJXtRpc5VHrkflusCl/KRCPzpQ
github.com/writeas/web-core v1.0.0/go.mod h1:Si3chV7VWgY8CsV+3gRolMXSO2Vx1ZFAQ/mkrpvmyEE=
github.com/writefreely/go-nodeinfo v1.1.0 h1:dp/ieEu0/gTeNKFvJTYhzBBouyFn7aiWtWzkb8J1JLg=
github.com/writefreely/go-nodeinfo v1.1.0/go.mod h1:UTvE78KpcjYOlRHupZIiSEFcXHioTXuacCbHU+CAcPg=
github.com/writefreely/go-nodeinfo v1.2.0 h1:La+YbTCvmpTwFhBSlebWDDL81N88Qf/SCAvRLR7F8ss=
github.com/writefreely/go-nodeinfo v1.2.0/go.mod h1:UTvE78KpcjYOlRHupZIiSEFcXHioTXuacCbHU+CAcPg=
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59 h1:hk3yo72LXLapY9EXVttc3Z1rLOxT9IuAPPX3GpY2+jo=
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190208162236-193df9c0f06f h1:ETU2VEl7TnT5bl7IvuKEzTDpplg5wzGYsOCAPhdoEIg=

@ -47,6 +47,13 @@ func (db *datastore) typeChar(l int) string {
return fmt.Sprintf("CHAR(%d)", l)
}
func (db *datastore) typeVarChar(l int) string {
if db.driverName == driverSQLite {
return "TEXT"
}
return fmt.Sprintf("VARCHAR(%d)", l)
}
func (db *datastore) typeBool() string {
if db.driverName == driverSQLite {
return "INTEGER"
@ -58,6 +65,13 @@ func (db *datastore) typeDateTime() string {
return "DATETIME"
}
func (db *datastore) collateMultiByte() string {
if db.driverName == driverSQLite {
return ""
}
return " COLLATE utf8_bin"
}
func (db *datastore) engine() string {
if db.driverName == driverSQLite {
return ""

@ -55,7 +55,8 @@ func (m *migration) Migrate(db *datastore) error {
}
var migrations = []Migration{
New("support user invites", supportUserInvites), // -> V1 (v0.8.0)
New("support user invites", supportUserInvites), // -> V1 (v0.8.0)
New("support dynamic instance pages", supportInstancePages), // V1 -> V2 (v0.9.0)
}
// CurrentVer returns the current migration version the application is on

@ -0,0 +1,35 @@
/*
* Copyright © 2019 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 supportInstancePages(db *datastore) error {
t, err := db.Begin()
_, err = t.Exec(`ALTER TABLE appcontent ADD COLUMN title ` + db.typeVarChar(255) + db.collateMultiByte() + ` NULL`)
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`ALTER TABLE appcontent ADD COLUMN content_type ` + db.typeVarChar(36) + ` DEFAULT 'page' NOT NULL`)
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

@ -50,6 +50,7 @@ func nodeInfoConfig(db *datastore, cfg *config.Config) *nodeinfo.Config {
},
MaxBlogs: cfg.App.MaxBlogs,
PublicReader: cfg.App.LocalTimeline,
Invites: cfg.App.UserInvites != "",
},
Protocols: []nodeinfo.NodeProtocol{
nodeinfo.ProtocolActivityPub,

@ -37,3 +37,8 @@ func (sp *StaticPage) SanitizeHost(cfg *config.Config) {
sp.Host = cfg.Server.HiddenHost
}
}
func (sp StaticPage) OfficialVersion() string {
p := strings.Split(sp.Version, "-")
return p[0]
}

@ -11,6 +11,7 @@
package writefreely
import (
"database/sql"
"github.com/writeas/writefreely/config"
"time"
)
@ -25,12 +26,20 @@ func getAboutPage(app *app) (*instanceContent, error) {
if c == nil {
c = &instanceContent{
ID: "about",
Type: "page",
Content: defaultAboutPage(app.cfg),
}
}
if !c.Title.Valid {
c.Title = defaultAboutTitle(app.cfg)
}
return c, nil
}
func defaultAboutTitle(cfg *config.Config) sql.NullString {
return sql.NullString{String: "About " + cfg.App.SiteName, Valid: true}
}
func getPrivacyPage(app *app) (*instanceContent, error) {
c, err := app.db.GetDynamicContent("privacy")
if err != nil {
@ -39,13 +48,21 @@ func getPrivacyPage(app *app) (*instanceContent, error) {
if c == nil {
c = &instanceContent{
ID: "privacy",
Type: "page",
Content: defaultPrivacyPolicy(app.cfg),
Updated: defaultPageUpdatedTime,
}
}
if !c.Title.Valid {
c.Title = defaultPrivacyTitle()
}
return c, nil
}
func defaultPrivacyTitle() sql.NullString {
return sql.NullString{String: "Privacy Policy", Valid: true}
}
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.`
@ -54,7 +71,7 @@ func defaultAboutPage(cfg *config.Config) string {
}
func defaultPrivacyPolicy(cfg *config.Config) string {
return `[Write Freely](https://writefreely.org), the software that powers this site, is built to enforce your right to privacy by default.
return `[WriteFreely](https://writefreely.org), the software that powers this site, is built to enforce your right to privacy by default.
It retains as little data about you as possible, not even requiring an email address to sign up. However, if you _do_ give us your email address, it is stored encrypted in our database. We salt and hash your account's password.

@ -1,9 +1,9 @@
{{define "head"}}<title>About {{.SiteName}}</title>
{{define "head"}}<title>{{.ContentTitle}} &mdash; {{.SiteName}}</title>
<meta name="description" content="{{.PlainContent}}">
{{end}}
{{define "content"}}
<div class="content-container snug">
<h1>About {{.SiteName}}</h1>
<h1>{{.ContentTitle}}</h1>
{{.Content}}

@ -110,14 +110,14 @@ form dd {
<div class="content-container snug">
<h2>Join the Fediverse</h2>
<p>The fediverse is a large network of platforms that all speak a common language. Imagine if you could reply to Instagram posts from Twitter, or interact with your favorite Medium blogs from Facebook &mdash; federated alternatives like <a href="https://pixelfed.org/" target="pixel">PixelFed</a>, <a href="https://joinmastodon.org/" target="masto">Mastodon</a>, and Write Freely enable you to do these types of things.</p>
<p>The fediverse is a large network of platforms that all speak a common language. Imagine if you could reply to Instagram posts from Twitter, or interact with your favorite Medium blogs from Facebook &mdash; federated alternatives like <a href="https://pixelfed.org/" target="pixel">PixelFed</a>, <a href="https://joinmastodon.org/" target="masto">Mastodon</a>, and WriteFreely enable you to do these types of things.</p>
<div style="text-align:center">
<iframe style="width: 560px; height: 315px; max-width: 100%;" sandbox="allow-same-origin allow-scripts" src="https://video.writeas.org/videos/embed/cc55e615-d204-417c-9575-7b57674cc6f3" frameborder="0" allowfullscreen></iframe>
</div>
<h2>Write More Socially</h2>
<p>Write Freely can communicate with other federated platforms like Mastodon, so people can follow your blogs, bookmark their favorite posts, and boost them to their followers. Sign up above to create a blog and join the fediverse.</p>
<p>WriteFreely can communicate with other federated platforms like Mastodon, so people can follow your blogs, bookmark their favorite posts, and boost them to their followers. Sign up above to create a blog and join the fediverse.</p>
</div>
{{ end }}

@ -1,8 +1,8 @@
{{define "head"}}<title>{{.SiteName}} Privacy Policy</title>
{{define "head"}}<title>{{.ContentTitle}} &mdash; {{.SiteName}}</title>
<meta name="description" content="{{.PlainContent}}">
{{end}}
{{define "content"}}<div class="content-container snug">
<h1>Privacy Policy</h1>
<h1>{{.ContentTitle}}</h1>
<p style="font-style:italic">Last updated {{.Updated}}</p>
{{.Content}}

@ -185,7 +185,7 @@ func postDescription(content, title, friendlyId string) string {
maxLen := 140
if content == "" {
content = "Write Freely is a painless, simple, federated blogging platform."
content = "WriteFreely is a painless, simple, federated blogging platform."
} else {
fmtStr := "%s"
truncation := 0

@ -188,6 +188,6 @@ func localHTML(term, lang string) template.HTML {
if s == "" {
s = l10n.Strings("")[term]
}
s = strings.Replace(s, "write.as", "<a href=\"https://writefreely.org\">write freely</a>", 1)
s = strings.Replace(s, "write.as", "<a href=\"https://writefreely.org\">writefreely</a>", 1)
return template.HTML(s)
}

@ -9,7 +9,7 @@
<link rel="shortcut icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="canonical" href="{{.CanonicalURL}}" />
<meta name="generator" content="Write Freely">
<meta name="generator" content="WriteFreely">
<meta name="title" content="{{.PlainDisplayTitle}} {{localhtml "title dash" .Language.String}} {{if .Collection.Title}}{{.Collection.Title}}{{else}}{{.Collection.Alias}}{{end}}">
<meta name="description" content="{{.Summary}}">
{{if gt .Views 1}}<meta name="twitter:label1" value="Views">

@ -62,7 +62,7 @@
<footer dir="ltr">
<hr>
<nav>
<p style="font-size: 0.9em"><a class="home pubd" href="/">{{.SiteName}}</a> &middot; powered by <a style="margin-left:0" href="https://writefreely.org">write freely</a></p>
<p style="font-size: 0.9em"><a class="home pubd" href="/">{{.SiteName}}</a> &middot; powered by <a style="margin-left:0" href="https://writefreely.org">writefreely</a></p>
</nav>
</footer>
{{ end }}

@ -13,7 +13,7 @@
{{if not .IsPrivate}}<link rel="alternate" type="application/rss+xml" title="{{.DisplayTitle}} &raquo; Feed" href="{{.CanonicalURL}}feed/" />{{end}}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="generator" content="Write Freely">
<meta name="generator" content="WriteFreely">
<meta name="description" content="{{.Description}}">
<meta itemprop="name" content="{{.DisplayTitle}}">
<meta itemprop="description" content="{{.Description}}">
@ -100,7 +100,7 @@
<footer>
<hr />
<nav dir="ltr">
{{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>
{{if not .SingleUser}}<a class="home pubd" href="/">{{.SiteName}}</a> &middot; {{end}}powered by <a style="margin-left:0" href="https://writefreely.org">writefreely</a>
</nav>
</footer>
{{ end }}

@ -4,8 +4,8 @@
{{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://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>
</nav>
@ -23,8 +23,8 @@
<div class="half">
<h3><a href="https://writefreely.org" style="color:#444;text-transform:lowercase;">WriteFreely</a></h3>
<ul>
<li><a href="https://writefreely.org/guide" target="guide">writer's guide</a></li>
<li><a href="https://developers.write.as/" title="Build on Write Freely with our open developer API.">developers</a></li>
<li><a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">writer's guide</a></li>
<li><a href="https://developers.write.as/" title="Build on WriteFreely with our open developer API.">developers</a></li>
<li><a href="https://github.com/writeas/writefreely">source code</a></li>
<li style="margin-top:0.8em">{{.Version}}</li>
</ul>

@ -49,7 +49,7 @@
<footer>
<hr />
<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>
<a class="home pubd" href="/">{{.SiteName}}</a> &middot; powered by <a style="margin-left:0" href="https://writefreely.org">writefreely</a>
</nav>
</footer>
</body>

@ -32,6 +32,9 @@ form dt {
.invisible {
display: none;
}
p.docs {
font-size: 0.86em;
}
</style>
<div class="content-container snug">
@ -41,21 +44,19 @@ form dt {
<h2>On this page</h2>
<ul class="pagenav">
<li><a href="#reset-pass">Reset user password</a></li>
<li><a href="#config">Configuration</a></li>
<li><a href="#monitor">Application monitor</a></li>
</ul>
<hr />
<h2>Users</h2>
<h3><a name="reset-pass"></a>reset password</h3>
<pre><code>writefreely --reset-pass &lt;username&gt;</code></pre>
<h2>Resources</h2>
<ul class="pagenav">
<li><a href="https://writefreely.org/docs/{{.OfficialVersion}}/admin">Admin Guide</a></li>
</ul>
<hr />
<h2><a name="config"></a>App Configuration</h2>
<p class="docs">Read more in the <a href="https://writefreely.org/docs/{{.OfficialVersion}}/admin/config">configuration docs</a>.</p>
{{if .ConfigMessage}}<p class="success" style="text-align: center">{{.ConfigMessage}}</p>{{end}}

@ -1,6 +1,12 @@
{{define "pages"}}
{{template "header" .}}
<style>
table.classy.export .disabled, table.classy.export a {
text-transform: initial;
}
</style>
<div class="snug content-container">
{{template "admin-header" .}}
@ -8,12 +14,12 @@
<table class="classy export" style="width:100%">
<tr>
<th>Pages</th>
<th>Page</th>
<th>Last Modified</th>
</tr>
{{range .Pages}}
<tr>
<td><a href="/admin/page/{{.ID}}">{{.ID}}</a></td>
<td><a href="/admin/page/{{.ID}}">{{if .Title.Valid}}{{.Title.String}}{{else}}{{.ID}}{{end}}</a></td>
<td style="text-align:right">{{.UpdatedFriendly}}</td>
</tr>
{{end}}

@ -1,22 +1,53 @@
{{define "view-page"}}
{{template "header" .}}
<style>
label {
display: block;
margin-top: 1em;
padding: 0 0 1em;
color: #666;
}
.content-desc {
font-size: 0.95em;
}
.page-desc {
margin: 0 0 0.5em;
}
textarea + .content-desc {
margin: 0.5em 0 1em;
font-style: italic;
}
input[type=text] {
/* Match textarea color. TODO: y is it like this thooo */
border-color: #ccc;
}
</style>
<div class="snug content-container">
{{template "admin-header" .}}
<h2 id="posts-header">{{.Content.ID}} page</h2>
{{if .Message}}<p>{{.Message}}</p>{{end}}
{{if eq .Content.ID "about"}}
<p>Describe what your instance is <a href="/about" target="page">about</a>. <em>Accepts Markdown</em>.</p>
<p class="page-desc content-desc">Describe what your instance is <a href="/about" target="page">about</a>.</p>
{{else if eq .Content.ID "privacy"}}
<p>Outline your <a href="/privacy" target="page">privacy policy</a>. <em>Accepts Markdown</em>.</p>
{{else}}
<p><em>Accepts Markdown and HTML</em>.</p>
<p class="page-desc content-desc">Outline your <a href="/privacy" target="page">privacy policy</a>.</p>
{{end}}
{{if .Message}}<p>{{.Message}}</p>{{end}}
<form method="post" action="/admin/update/{{.Content.ID}}" onsubmit="savePage(this)">
<textarea id="about-editor" class="section codable norm edit-page" name="content">{{.Content.Content}}</textarea>
<label for="title">
Title
</label>
<input type="text" name="title" id="title" value="{{.Content.Title.String}}" />
<label for="content">
Content
</label>
<textarea id="content" class="section codable norm edit-page" name="content">{{.Content.Content}}</textarea>
<p class="content-desc">Accepts Markdown and HTML.</p>
<input type="submit" value="Save" />
</form>

@ -10,7 +10,7 @@
<a class="home" href="/">{{.SiteName}}</a>
<a href="/about">about</a>
{{if and (not .SingleUser) .LocalTimeline}}<a href="/read">reader</a>{{end}}
<a href="https://writefreely.org/guide" target="guide">writer's guide</a>
<a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">writer's guide</a>
<a href="/privacy">privacy</a>
<a href="https://writefreely.org">writefreely {{.Version}}</a>
</nav>

@ -24,6 +24,7 @@
<li><a href="/me/c/{{.Username}}">Customize</a></li>
<li><a href="/me/c/{{.Username}}/stats">Stats</a></li>
<li class="separator"><hr /></li>
{{if .IsAdmin}}<li><a href="/admin">Admin</a></li>{{end}}
<li><a href="/me/settings">Settings</a></li>
<li><a href="/me/export">Export</a></li>
<li class="separator"><hr /></li>
@ -41,6 +42,7 @@
<nav id="user-nav">
<nav class="dropdown-nav">
<ul><li><a>{{.Username}}</a> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /><ul>
{{if .IsAdmin}}<li><a href="/admin">Admin dashboard</a></li>{{end}}
<li><a href="/me/settings">Account settings</a></li>
<li><a href="/me/export">Export</a></li>
{{if .CanInvite}}<li><a href="/me/invites">Invite people</a></li>{{end}}

Loading…
Cancel
Save