Merge branch 'develop'
This commit is contained in:
commit
831209f4b6
29 changed files with 195 additions and 82 deletions
31
README.md
31
README.md
|
@ -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
|
||||
|
||||
|
|
23
admin.go
23
admin.go
|
@ -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()
|
||||
}
|
||||
|
|
4
app.go
4
app.go
|
@ -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() {
|
||||
|
|
24
database.go
24
database.go
|
@ -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)
|
||||
|
|
2
go.mod
2
go.mod
|
@ -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
|
||||
|
|
2
go.sum
2
go.sum
|
@ -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 ""
|
||||
|
|
|
@ -56,6 +56,7 @@ func (m *migration) Migrate(db *datastore) error {
|
|||
|
||||
var migrations = []Migration{
|
||||
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
|
||||
|
|
35
migrations/v2.go
Normal file
35
migrations/v2.go
Normal file
|
@ -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]
|
||||
}
|
||||
|
|
17
pages.go
17
pages.go
|
@ -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.`
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{{define "head"}}<title>About {{.SiteName}}</title>
|
||||
{{define "head"}}<title>{{.ContentTitle}} — {{.SiteName}}</title>
|
||||
<meta name="description" content="{{.PlainContent}}">
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="content-container snug">
|
||||
<h1>About {{.SiteName}}</h1>
|
||||
<h1>{{.ContentTitle}}</h1>
|
||||
|
||||
{{.Content}}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{{define "head"}}<title>{{.SiteName}} Privacy Policy</title>
|
||||
{{define "head"}}<title>{{.ContentTitle}} — {{.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}}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
{{if .SingleUser}}
|
||||
<nav>
|
||||
<a class="home" href="/">{{.SiteName}}</a>
|
||||
<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="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>
|
||||
|
@ -23,7 +23,7 @@
|
|||
<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://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>
|
||||
|
|
|
@ -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 <username></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…
Add table
Reference in a new issue