~bouncepaw/mycorrhiza

0e5fd60b9d5a815bb0c4ad28b0bba391b92c24e4 — Timur Ismagilov 2 months ago b1cdb1e
Admin: Get rid of QTPL templates
13 files changed, 167 insertions(+), 613 deletions(-)

M admin/admin.go
D admin/admin.qtpl
D admin/admin.qtpl.go
M admin/view.go
A admin/view_delete_user.html
A admin/view_edit_user.html
A admin/view_new_user.html
D l18n/en/admin.json
D l18n/ru/admin.json
M main.go
M static/default.css
M viewutil/base.html
M viewutil/viewutil.go
M admin/admin.go => admin/admin.go +5 -22
@@ 4,7 4,6 @@ import (
	"fmt"
	"github.com/bouncepaw/mycorrhiza/viewutil"
	"github.com/gorilla/mux"
	"io"
	"log"
	"mime"
	"net/http"


@@ 12,7 11,6 @@ import (
	"sort"

	"github.com/bouncepaw/mycorrhiza/cfg"
	"github.com/bouncepaw/mycorrhiza/l18n"
	"github.com/bouncepaw/mycorrhiza/user"
	"github.com/bouncepaw/mycorrhiza/util"
)


@@ 87,15 85,12 @@ func handlerAdminUserEdit(w http.ResponseWriter, rq *http.Request) {

	f.Put("group", u.Group)

	var lc = l18n.FromRequest(rq)
	html := AdminUserEdit(u, f, lc)
	html = viewutil.Base(viewutil.MetaFrom(w, rq), fmt.Sprintf(lc.Get("admin.user_title"), u.Name), html)

	if f.HasError() {
		w.WriteHeader(http.StatusBadRequest)
	}
	w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
	io.WriteString(w, html)

	viewEditUser(viewutil.MetaFrom(w, rq), f, u)
}

func handlerAdminUserDelete(w http.ResponseWriter, rq *http.Request) {


@@ 117,26 112,17 @@ func handlerAdminUserDelete(w http.ResponseWriter, rq *http.Request) {
		}
	}

	var lc = l18n.FromRequest(rq)
	html := AdminUserDelete(u, util.NewFormData(), lc)
	html = viewutil.Base(viewutil.MetaFrom(w, rq), fmt.Sprintf(lc.Get("admin.user_title"), u.Name), html)

	if f.HasError() {
		w.WriteHeader(http.StatusBadRequest)
	}
	w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
	io.WriteString(w, html)
	viewDeleteUser(viewutil.MetaFrom(w, rq), f, u)
}

func handlerAdminUserNew(w http.ResponseWriter, rq *http.Request) {
	var lc = l18n.FromRequest(rq)
	if rq.Method == http.MethodGet {
		// New user form
		html := AdminUserNew(util.NewFormData(), lc)
		html = viewutil.Base(viewutil.MetaFrom(w, rq), lc.Get("admin.newuser_title"), html)

		w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
		io.WriteString(w, html)
		viewNewUser(viewutil.MetaFrom(w, rq), util.NewFormData())
	} else if rq.Method == http.MethodPost {
		// Create a user
		f := util.FormDataFromRequest(rq, []string{"name", "password", "group"})


@@ 144,12 130,9 @@ func handlerAdminUserNew(w http.ResponseWriter, rq *http.Request) {
		err := user.Register(f.Get("name"), f.Get("password"), f.Get("group"), "local", true)

		if err != nil {
			html := AdminUserNew(f.WithError(err), lc)
			html = viewutil.Base(viewutil.MetaFrom(w, rq), lc.Get("admin.newuser_title"), html)

			w.WriteHeader(http.StatusBadRequest)
			w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
			io.WriteString(w, html)
			viewNewUser(viewutil.MetaFrom(w, rq), f.WithError(err))
		} else {
			http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
		}

D admin/admin.qtpl => admin/admin.qtpl +0 -105
@@ 1,105 0,0 @@
{% import "fmt" %}
{% import "github.com/bouncepaw/mycorrhiza/l18n" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}

{% func AdminUserNew(f util.FormData, lc *l18n.Localizer) %}
<main class="main-width form-wrap">
	<h1>{%s lc.Get("admin.newuser_title") %}</h1>

	{% if f.HasError() %}
	<div class="notice notice--error">
		<strong>{%s lc.Get("ui.error") %}:</strong>
		{%s f.Error() %}
	</div>
	{% endif %}

	<form class="form--double" action="" method="post">
		<div class="form-field">
			<label for="name">{%s lc.Get("admin.users_name") %}:</label>
			<input type="text" name="name" id="name" value="{%s f.Get("name") %}" autofocus>
		</div>

		<div class="form-field">
			<label for="password">{%s lc.Get("admin.users_password") %}:</label>
			<input type="password" name="password" id="password" value="{%s f.Get("password") %}">
		</div>

		<div class="form-field">
			<label for="group">{%s lc.Get("admin.users_group") %}:</label>
			<select id="group" name="group">
				<option{% if f.Get("group") == "anon" %} selected{% endif %}>anon</option>
				<option{% if f.Get("group") == "editor" %} selected{% endif %}>editor</option>
				<option{% if f.Get("group") == "trusted" %} selected{% endif %}>trusted</option>
				<option{% if f.Get("group") == "moderator" %} selected{% endif %}>moderator</option>
				<option{% if f.Get("group") == "admin" %} selected{% endif %}>admin</option>
			</select>
		</div>

		<div class="form-field">
			<div class="form-field__input">
				<button class="btn" type="submit">{%s lc.Get("admin.newuser_create") %}</button>
				<a class="btn btn_weak" href="/admin/users/">{%s lc.Get("ui.cancel") %}</a>
			</div>
		</div>
	</form>
</main>
{% endfunc %}

{% func AdminUserEdit(u *user.User, f util.FormData, lc *l18n.Localizer) %}
<main class="main-width form-wrap">
	<h1>
		<a href="/admin/users/">&larr;</a>
		{%s u.Name %}
	</h1>

	<h2>{%s lc.Get("admin.user_group_heading") %}</h2>

	{% if f.HasError() %}
	<div class="notice notice--error">
		<strong>{%s lc.Get("ui.error") %}:</strong>
		{%s f.Error() %}
	</div>
	{% endif %}

	<form action="" method="post">
		<div class="form-field">
			<select id="group" name="group" aria-label="{%s lc.Get("admin.users_group") %}">
				<option{% if f.Get("group") == "anon" %} selected{% endif %}>anon</option>
				<option{% if f.Get("group") == "editor" %} selected{% endif %}>editor</option>
				<option{% if f.Get("group") == "trusted" %} selected{% endif %}>trusted</option>
				<option{% if f.Get("group") == "moderator" %} selected{% endif %}>moderator</option>
				<option{% if f.Get("group") == "admin" %} selected{% endif %}>admin</option>
			</select>
		</div>

		<div class="form-field">
			<button class="btn" type="submit">{%s lc.Get("admin.user_update") %}</button>
		</div>
	</form>

	<h2>{%s lc.Get("admin.user_delete_heading") %}</h2>
	<p>{%s lc.Get("admin.user_delete_tip") %}</p>
	<a class="btn btn_destructive" href="/admin/users/{%u u.Name %}/delete">{%s lc.Get("admin.user_delete") %}</a>
</main>
{% endfunc %}

{% func AdminUserDelete(u *user.User, f util.FormData, lc *l18n.Localizer) %}
<main class="main-width form-wrap">
	<h1>{%s lc.Get("admin.user_delete_heading") %}</h1>

	{% if f.HasError() %}
	<div class="notice notice--error">
		<strong>{%s lc.Get("ui.error") %}:</strong>
		{%s f.Error() %}
	</div>
	{% endif %}

	<p>{%s= lc.Get("admin.user_delete_warn", &l18n.Replacements{"name": fmt.Sprintf("<strong>%s</strong>", u.Name)}) %}</p>

	<form action="" method="post">
		<button class="btn btn_destructive" type="submit">{%s lc.Get("admin.user_delete") %}</button>
		<a class="btn btn_weak" href="/admin/users/{%u u.Name %}/edit">{%s lc.Get("ui.cancel") %}</a>
	</form>
</main>
{% endfunc %}

D admin/admin.qtpl.go => admin/admin.qtpl.go +0 -436
@@ 1,436 0,0 @@
// Code generated by qtc from "admin.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.

//line admin/admin.qtpl:1
package admin

//line admin/admin.qtpl:1
import "fmt"

//line admin/admin.qtpl:2
import "github.com/bouncepaw/mycorrhiza/l18n"

//line admin/admin.qtpl:3
import "github.com/bouncepaw/mycorrhiza/user"

//line admin/admin.qtpl:4
import "github.com/bouncepaw/mycorrhiza/util"

//line admin/admin.qtpl:6
import (
	qtio422016 "io"

	qt422016 "github.com/valyala/quicktemplate"
)

//line admin/admin.qtpl:6
var (
	_ = qtio422016.Copy
	_ = qt422016.AcquireByteBuffer
)

//line admin/admin.qtpl:6
func StreamAdminUserNew(qw422016 *qt422016.Writer, f util.FormData, lc *l18n.Localizer) {
//line admin/admin.qtpl:6
	qw422016.N().S(`
<main class="main-width form-wrap">
	<h1>`)
//line admin/admin.qtpl:8
	qw422016.E().S(lc.Get("admin.newuser_title"))
//line admin/admin.qtpl:8
	qw422016.N().S(`</h1>

	`)
//line admin/admin.qtpl:10
	if f.HasError() {
//line admin/admin.qtpl:10
		qw422016.N().S(`
	<div class="notice notice--error">
		<strong>`)
//line admin/admin.qtpl:12
		qw422016.E().S(lc.Get("ui.error"))
//line admin/admin.qtpl:12
		qw422016.N().S(`:</strong>
		`)
//line admin/admin.qtpl:13
		qw422016.E().S(f.Error())
//line admin/admin.qtpl:13
		qw422016.N().S(`
	</div>
	`)
//line admin/admin.qtpl:15
	}
//line admin/admin.qtpl:15
	qw422016.N().S(`

	<form class="form--double" action="" method="post">
		<div class="form-field">
			<label for="name">`)
//line admin/admin.qtpl:19
	qw422016.E().S(lc.Get("admin.users_name"))
//line admin/admin.qtpl:19
	qw422016.N().S(`:</label>
			<input type="text" name="name" id="name" value="`)
//line admin/admin.qtpl:20
	qw422016.E().S(f.Get("name"))
//line admin/admin.qtpl:20
	qw422016.N().S(`" autofocus>
		</div>

		<div class="form-field">
			<label for="password">`)
//line admin/admin.qtpl:24
	qw422016.E().S(lc.Get("admin.users_password"))
//line admin/admin.qtpl:24
	qw422016.N().S(`:</label>
			<input type="password" name="password" id="password" value="`)
//line admin/admin.qtpl:25
	qw422016.E().S(f.Get("password"))
//line admin/admin.qtpl:25
	qw422016.N().S(`">
		</div>

		<div class="form-field">
			<label for="group">`)
//line admin/admin.qtpl:29
	qw422016.E().S(lc.Get("admin.users_group"))
//line admin/admin.qtpl:29
	qw422016.N().S(`:</label>
			<select id="group" name="group">
				<option`)
//line admin/admin.qtpl:31
	if f.Get("group") == "anon" {
//line admin/admin.qtpl:31
		qw422016.N().S(` selected`)
//line admin/admin.qtpl:31
	}
//line admin/admin.qtpl:31
	qw422016.N().S(`>anon</option>
				<option`)
//line admin/admin.qtpl:32
	if f.Get("group") == "editor" {
//line admin/admin.qtpl:32
		qw422016.N().S(` selected`)
//line admin/admin.qtpl:32
	}
//line admin/admin.qtpl:32
	qw422016.N().S(`>editor</option>
				<option`)
//line admin/admin.qtpl:33
	if f.Get("group") == "trusted" {
//line admin/admin.qtpl:33
		qw422016.N().S(` selected`)
//line admin/admin.qtpl:33
	}
//line admin/admin.qtpl:33
	qw422016.N().S(`>trusted</option>
				<option`)
//line admin/admin.qtpl:34
	if f.Get("group") == "moderator" {
//line admin/admin.qtpl:34
		qw422016.N().S(` selected`)
//line admin/admin.qtpl:34
	}
//line admin/admin.qtpl:34
	qw422016.N().S(`>moderator</option>
				<option`)
//line admin/admin.qtpl:35
	if f.Get("group") == "admin" {
//line admin/admin.qtpl:35
		qw422016.N().S(` selected`)
//line admin/admin.qtpl:35
	}
//line admin/admin.qtpl:35
	qw422016.N().S(`>admin</option>
			</select>
		</div>

		<div class="form-field">
			<div class="form-field__input">
				<button class="btn" type="submit">`)
//line admin/admin.qtpl:41
	qw422016.E().S(lc.Get("admin.newuser_create"))
//line admin/admin.qtpl:41
	qw422016.N().S(`</button>
				<a class="btn btn_weak" href="/admin/users/">`)
//line admin/admin.qtpl:42
	qw422016.E().S(lc.Get("ui.cancel"))
//line admin/admin.qtpl:42
	qw422016.N().S(`</a>
			</div>
		</div>
	</form>
</main>
`)
//line admin/admin.qtpl:47
}

//line admin/admin.qtpl:47
func WriteAdminUserNew(qq422016 qtio422016.Writer, f util.FormData, lc *l18n.Localizer) {
//line admin/admin.qtpl:47
	qw422016 := qt422016.AcquireWriter(qq422016)
//line admin/admin.qtpl:47
	StreamAdminUserNew(qw422016, f, lc)
//line admin/admin.qtpl:47
	qt422016.ReleaseWriter(qw422016)
//line admin/admin.qtpl:47
}

//line admin/admin.qtpl:47
func AdminUserNew(f util.FormData, lc *l18n.Localizer) string {
//line admin/admin.qtpl:47
	qb422016 := qt422016.AcquireByteBuffer()
//line admin/admin.qtpl:47
	WriteAdminUserNew(qb422016, f, lc)
//line admin/admin.qtpl:47
	qs422016 := string(qb422016.B)
//line admin/admin.qtpl:47
	qt422016.ReleaseByteBuffer(qb422016)
//line admin/admin.qtpl:47
	return qs422016
//line admin/admin.qtpl:47
}

//line admin/admin.qtpl:49
func StreamAdminUserEdit(qw422016 *qt422016.Writer, u *user.User, f util.FormData, lc *l18n.Localizer) {
//line admin/admin.qtpl:49
	qw422016.N().S(`
<main class="main-width form-wrap">
	<h1>
		<a href="/admin/users/">&larr;</a>
		`)
//line admin/admin.qtpl:53
	qw422016.E().S(u.Name)
//line admin/admin.qtpl:53
	qw422016.N().S(`
	</h1>

	<h2>`)
//line admin/admin.qtpl:56
	qw422016.E().S(lc.Get("admin.user_group_heading"))
//line admin/admin.qtpl:56
	qw422016.N().S(`</h2>

	`)
//line admin/admin.qtpl:58
	if f.HasError() {
//line admin/admin.qtpl:58
		qw422016.N().S(`
	<div class="notice notice--error">
		<strong>`)
//line admin/admin.qtpl:60
		qw422016.E().S(lc.Get("ui.error"))
//line admin/admin.qtpl:60
		qw422016.N().S(`:</strong>
		`)
//line admin/admin.qtpl:61
		qw422016.E().S(f.Error())
//line admin/admin.qtpl:61
		qw422016.N().S(`
	</div>
	`)
//line admin/admin.qtpl:63
	}
//line admin/admin.qtpl:63
	qw422016.N().S(`

	<form action="" method="post">
		<div class="form-field">
			<select id="group" name="group" aria-label="`)
//line admin/admin.qtpl:67
	qw422016.E().S(lc.Get("admin.users_group"))
//line admin/admin.qtpl:67
	qw422016.N().S(`">
				<option`)
//line admin/admin.qtpl:68
	if f.Get("group") == "anon" {
//line admin/admin.qtpl:68
		qw422016.N().S(` selected`)
//line admin/admin.qtpl:68
	}
//line admin/admin.qtpl:68
	qw422016.N().S(`>anon</option>
				<option`)
//line admin/admin.qtpl:69
	if f.Get("group") == "editor" {
//line admin/admin.qtpl:69
		qw422016.N().S(` selected`)
//line admin/admin.qtpl:69
	}
//line admin/admin.qtpl:69
	qw422016.N().S(`>editor</option>
				<option`)
//line admin/admin.qtpl:70
	if f.Get("group") == "trusted" {
//line admin/admin.qtpl:70
		qw422016.N().S(` selected`)
//line admin/admin.qtpl:70
	}
//line admin/admin.qtpl:70
	qw422016.N().S(`>trusted</option>
				<option`)
//line admin/admin.qtpl:71
	if f.Get("group") == "moderator" {
//line admin/admin.qtpl:71
		qw422016.N().S(` selected`)
//line admin/admin.qtpl:71
	}
//line admin/admin.qtpl:71
	qw422016.N().S(`>moderator</option>
				<option`)
//line admin/admin.qtpl:72
	if f.Get("group") == "admin" {
//line admin/admin.qtpl:72
		qw422016.N().S(` selected`)
//line admin/admin.qtpl:72
	}
//line admin/admin.qtpl:72
	qw422016.N().S(`>admin</option>
			</select>
		</div>

		<div class="form-field">
			<button class="btn" type="submit">`)
//line admin/admin.qtpl:77
	qw422016.E().S(lc.Get("admin.user_update"))
//line admin/admin.qtpl:77
	qw422016.N().S(`</button>
		</div>
	</form>

	<h2>`)
//line admin/admin.qtpl:81
	qw422016.E().S(lc.Get("admin.user_delete_heading"))
//line admin/admin.qtpl:81
	qw422016.N().S(`</h2>
	<p>`)
//line admin/admin.qtpl:82
	qw422016.E().S(lc.Get("admin.user_delete_tip"))
//line admin/admin.qtpl:82
	qw422016.N().S(`</p>
	<a class="btn btn_destructive" href="/admin/users/`)
//line admin/admin.qtpl:83
	qw422016.N().U(u.Name)
//line admin/admin.qtpl:83
	qw422016.N().S(`/delete">`)
//line admin/admin.qtpl:83
	qw422016.E().S(lc.Get("admin.user_delete"))
//line admin/admin.qtpl:83
	qw422016.N().S(`</a>
</main>
`)
//line admin/admin.qtpl:85
}

//line admin/admin.qtpl:85
func WriteAdminUserEdit(qq422016 qtio422016.Writer, u *user.User, f util.FormData, lc *l18n.Localizer) {
//line admin/admin.qtpl:85
	qw422016 := qt422016.AcquireWriter(qq422016)
//line admin/admin.qtpl:85
	StreamAdminUserEdit(qw422016, u, f, lc)
//line admin/admin.qtpl:85
	qt422016.ReleaseWriter(qw422016)
//line admin/admin.qtpl:85
}

//line admin/admin.qtpl:85
func AdminUserEdit(u *user.User, f util.FormData, lc *l18n.Localizer) string {
//line admin/admin.qtpl:85
	qb422016 := qt422016.AcquireByteBuffer()
//line admin/admin.qtpl:85
	WriteAdminUserEdit(qb422016, u, f, lc)
//line admin/admin.qtpl:85
	qs422016 := string(qb422016.B)
//line admin/admin.qtpl:85
	qt422016.ReleaseByteBuffer(qb422016)
//line admin/admin.qtpl:85
	return qs422016
//line admin/admin.qtpl:85
}

//line admin/admin.qtpl:87
func StreamAdminUserDelete(qw422016 *qt422016.Writer, u *user.User, f util.FormData, lc *l18n.Localizer) {
//line admin/admin.qtpl:87
	qw422016.N().S(`
<main class="main-width form-wrap">
	<h1>`)
//line admin/admin.qtpl:89
	qw422016.E().S(lc.Get("admin.user_delete_heading"))
//line admin/admin.qtpl:89
	qw422016.N().S(`</h1>

	`)
//line admin/admin.qtpl:91
	if f.HasError() {
//line admin/admin.qtpl:91
		qw422016.N().S(`
	<div class="notice notice--error">
		<strong>`)
//line admin/admin.qtpl:93
		qw422016.E().S(lc.Get("ui.error"))
//line admin/admin.qtpl:93
		qw422016.N().S(`:</strong>
		`)
//line admin/admin.qtpl:94
		qw422016.E().S(f.Error())
//line admin/admin.qtpl:94
		qw422016.N().S(`
	</div>
	`)
//line admin/admin.qtpl:96
	}
//line admin/admin.qtpl:96
	qw422016.N().S(`

	<p>`)
//line admin/admin.qtpl:98
	qw422016.N().S(lc.Get("admin.user_delete_warn", &l18n.Replacements{"name": fmt.Sprintf("<strong>%s</strong>", u.Name)}))
//line admin/admin.qtpl:98
	qw422016.N().S(`</p>

	<form action="" method="post">
		<button class="btn btn_destructive" type="submit">`)
//line admin/admin.qtpl:101
	qw422016.E().S(lc.Get("admin.user_delete"))
//line admin/admin.qtpl:101
	qw422016.N().S(`</button>
		<a class="btn btn_weak" href="/admin/users/`)
//line admin/admin.qtpl:102
	qw422016.N().U(u.Name)
//line admin/admin.qtpl:102
	qw422016.N().S(`/edit">`)
//line admin/admin.qtpl:102
	qw422016.E().S(lc.Get("ui.cancel"))
//line admin/admin.qtpl:102
	qw422016.N().S(`</a>
	</form>
</main>
`)
//line admin/admin.qtpl:105
}

//line admin/admin.qtpl:105
func WriteAdminUserDelete(qq422016 qtio422016.Writer, u *user.User, f util.FormData, lc *l18n.Localizer) {
//line admin/admin.qtpl:105
	qw422016 := qt422016.AcquireWriter(qq422016)
//line admin/admin.qtpl:105
	StreamAdminUserDelete(qw422016, u, f, lc)
//line admin/admin.qtpl:105
	qt422016.ReleaseWriter(qw422016)
//line admin/admin.qtpl:105
}

//line admin/admin.qtpl:105
func AdminUserDelete(u *user.User, f util.FormData, lc *l18n.Localizer) string {
//line admin/admin.qtpl:105
	qb422016 := qt422016.AcquireByteBuffer()
//line admin/admin.qtpl:105
	WriteAdminUserDelete(qb422016, u, f, lc)
//line admin/admin.qtpl:105
	qs422016 := string(qb422016.B)
//line admin/admin.qtpl:105
	qt422016.ReleaseByteBuffer(qb422016)
//line admin/admin.qtpl:105
	return qs422016
//line admin/admin.qtpl:105
}

M admin/view.go => admin/view.go +53 -3
@@ 4,6 4,7 @@ import (
	"embed"
	"github.com/bouncepaw/mycorrhiza/cfg"
	"github.com/bouncepaw/mycorrhiza/user"
	"github.com/bouncepaw/mycorrhiza/util"
	"github.com/bouncepaw/mycorrhiza/viewutil"
	"github.com/gorilla/mux"
	"net/http"


@@ 28,13 29,25 @@ const adminTranslationRu = `
{{define "registered at"}}Зарегистрирован{{end}}
{{define "actions"}}Действия{{end}}
{{define "edit"}}Изменить{{end}}

{{define "new user"}}Новый пользователь{{end}}
{{define "password"}}Пароль{{end}}
{{define "create"}}Создать{{end}}

{{define "change group"}}Изменить группу{{end}}
{{define "user x"}}Пользователь {{.}}{{end}}
{{define "update"}}Обновить{{end}}
{{define "delete user"}}Удалить пользователя{{end}}
{{define "delete user tip"}}Удаляет пользователя из базы данных. Правки пользователя будут сохранены. Имя пользователя освободится для повторной регистрации.{{end}}

{{define "delete user?"}}Удалить пользователя {{.}}?{{end}}
{{define "delete user warning"}}Вы уверены, что хотите удалить этого пользователя из базы данных? Это действие нельзя отменить.{{end}}
`

var (
	//go:embed *.html
	fs         embed.FS
	panelChain viewutil.Chain
	listChain  viewutil.Chain
	fs                                                                  embed.FS
	panelChain, listChain, newUserChain, editUserChain, deleteUserChain viewutil.Chain
)

func Init(rtr *mux.Router) {


@@ 50,6 63,9 @@ func Init(rtr *mux.Router) {

	panelChain = viewutil.CopyEnRuWith(fs, "view_panel.html", adminTranslationRu)
	listChain = viewutil.CopyEnRuWith(fs, "view_user_list.html", adminTranslationRu)
	newUserChain = viewutil.CopyEnRuWith(fs, "view_new_user.html", adminTranslationRu)
	editUserChain = viewutil.CopyEnRuWith(fs, "view_edit_user.html", adminTranslationRu)
	deleteUserChain = viewutil.CopyEnRuWith(fs, "view_delete_user.html", adminTranslationRu)
}

func viewPanel(meta viewutil.Meta) {


@@ 69,3 85,37 @@ func viewList(meta viewutil.Meta, users []*user.User) {
		Users:     users,
	})
}

type newUserData struct {
	*viewutil.BaseData
	Form util.FormData
}

func viewNewUser(meta viewutil.Meta, form util.FormData) {
	viewutil.ExecutePage(meta, newUserChain, newUserData{
		BaseData: &viewutil.BaseData{},
		Form:     form,
	})
}

type editDeleteUserData struct {
	*viewutil.BaseData
	Form util.FormData
	U    *user.User
}

func viewEditUser(meta viewutil.Meta, form util.FormData, u *user.User) {
	viewutil.ExecutePage(meta, editUserChain, editDeleteUserData{
		BaseData: &viewutil.BaseData{},
		Form:     form,
		U:        u,
	})
}

func viewDeleteUser(meta viewutil.Meta, form util.FormData, u *user.User) {
	viewutil.ExecutePage(meta, deleteUserChain, editDeleteUserData{
		BaseData: &viewutil.BaseData{},
		Form:     form,
		U:        u,
	})
}

A admin/view_delete_user.html => admin/view_delete_user.html +21 -0
@@ 0,0 1,21 @@
{{define "delete user?"}}Delete user {{.}}?{{end}}
{{define "title"}}{{template "delete user?" .U.Name}}{{end}}
{{define "body"}}
	<main class="main-width form-wrap">
		<h1>{{template "delete user?" .U.Name}}</h1>

		{{if .Form.HasError}}
			<div class="notice notice--error">
				<strong>{{template "error"}}:</strong>
				{{.Form.Error}}
			</div>
		{{end}}

		<p>{{block "delete user warning" .}}Are you sure you want to delete them from the database? This action is irreversible.{{end}}</p>

		<form action="" method="post">
			<input class="btn btn_destructive" type="submit" value="{{template "delete"}}">
			<a class="btn btn_weak" href="/admin/users/{{.U.Name}}/edit">{{template "cancel"}}</a>
		</form>
	</main>
{{end}}
\ No newline at end of file

A admin/view_edit_user.html => admin/view_edit_user.html +39 -0
@@ 0,0 1,39 @@
{{define "user x"}}User {{.}}{{end}}
{{define "title"}}{{template "user x" .U.Name}}{{end}}
{{define "body"}}
	<main class="main-width form-wrap">
		<h1>
			<a href="/admin/users/">&larr;</a>
			{{.U.Name}}
		</h1>

		<h2>{{block "change group" .}}Change group{{end}}</h2>

		{{if .Form.HasError}}
		<div class="notice notice--error">
			<strong>{{template "error"}}:</strong>
			{{.Form.Error}}
		</div>
		{{end}}

		<form action="" method="post">
			<div class="form-field">
				<select id="group" name="group" aria-label="{{block "group" .}}Group{{end}}">
					<option {{if .Form.Get "group" | eq "anon" }}selected{{end}}>anon</option>
					<option {{if .Form.Get "group" | eq "editor" }}selected{{end}}>editor</option>
					<option {{if .Form.Get "group" | eq "trusted" }}selected{{end}}>trusted</option>
					<option {{if .Form.Get "group" | eq "moderator" }}selected{{end}}>moderator</option>
					<option {{if .Form.Get "group" | eq "admin" }}selected{{end}}>admin</option>
				</select>
			</div>

			<div class="form-field">
				<button class="btn" type="submit">{{block "update" .}}Update{{end}}</button>
			</div>
		</form>

		<h2>{{block "delete user" .}}Delete user{{end}}</h2>
		<p>{{block "delete user tip" .}}Remove the user from the database. Changes made by the user will be preserved. It will be possible to take this username later.{{end}}</p>
		<a class="btn btn_destructive" href="/admin/users/{{.U.Name}}/delete">{{template "delete"}}</a>
	</main>
{{end}}
\ No newline at end of file

A admin/view_new_user.html => admin/view_new_user.html +43 -0
@@ 0,0 1,43 @@
{{define "title"}}{{block "create user" .}}Create a new user{{end}}{{end}}
{{define "body"}}
	<main class="main-width form-wrap">
		<h1>{{block "new user" .}}New user{{end}}</h1>

		{{if .Form.HasError}}
		<div class="notice notice--error">
			<strong>{{template "error"}}:</strong>
			{{.Form.Error}}
		</div>
		{{end}}

		<form class="form--double" action="" method="post">
			<div class="form-field">
				<label for="name">{{block "name" .}}Name{{end}}:</label>
				<input type="text" name="name" id="name" value="{{.Form.Get `name`}}" autofocus>
			</div>

			<div class="form-field">
				<label for="password">{{block "password" .}}Password{{end}}:</label>
				<input type="password" name="password" id="password" value="{{.Form.Get `password`}}">
			</div>

			<div class="form-field">
				<label for="group">{{block "group" .}}Group{{end}}:</label>
				<select id="group" name="group">
					<option {{if .Form.Get "group" | eq "anon" }}selected{{end}}>anon</option>
					<option {{if .Form.Get "group" | eq "editor" }}selected{{end}}>editor</option>
					<option {{if .Form.Get "group" | eq "trusted" }}selected{{end}}>trusted</option>
					<option {{if .Form.Get "group" | eq "moderator" }}selected{{end}}>moderator</option>
					<option {{if .Form.Get "group" | eq "admin" }}selected{{end}}>admin</option>
				</select>
			</div>

			<div class="form-field">
				<div class="form-field__input">
					<button class="btn" type="submit">{{block "create" .}}Create{{end}}</button>
					<a class="btn btn_weak" href="/admin/users/">{{template "cancel"}}</a>
				</div>
			</div>
		</form>
	</main>
{{end}}
\ No newline at end of file

D l18n/en/admin.json => l18n/en/admin.json +0 -23
@@ 1,23 0,0 @@
{
  "users_title": "Manage users",
  "users_create": "Create a new user",
  "users_reindex": "Reindex users",
  "users_name": "Name",
  "users_password": "Password",
  "users_group": "Group",
  "users_registered": "Registered at",
  "users_actions": "Actions",
  "users_notime": "unknown",
  "users_edit": "Edit",

  "user_title": "User %s",
  "user_group_heading": "Change group",
  "user_update": "Update",
  "user_delete_heading": "Delete user",
  "user_delete_tip": "Remove the user from the database. Changes made by the user will be preserved. It will be possible to take this username later.",
  "user_delete_warn": "Are you sure you want to delete {{.name}} from the database? This action is irreversible.",
  "user_delete": "Delete",
  
  "newuser_title": "New user",
  "newuser_create": "Create"
}

D l18n/ru/admin.json => l18n/ru/admin.json +0 -23
@@ 1,23 0,0 @@
{
  "users_title": "Менеджер пользователей",
  "users_create": "Создать пользователя",
  "users_reindex": "Переиндексировать пользователей",
  "users_name": "Имя",
  "users_password": "Пароль",
  "users_group": "Группа",
  "users_registered": "Время создания",
  "users_actions": "Действия",
  "users_notime": "неизвестно",
  "users_edit": "Изменить",

  "user_title": "Пользователь %s",
  "user_group_heading": "Изменить группу",
  "user_update": "Обновить",
  "user_delete_heading": "Удалить пользователя",
  "user_delete_tip": "Удаляет пользователя из базы данных. Правки пользователя будут сохранены. Имя пользователя освободится для повторной регистрации.",
  "user_delete_warn": "Вы уверены, что хотите удалить {{.name}} из базы данных? Это действие нельзя отменить.",
  "user_delete": "Удалить",
  
  "newuser_title": "Новый пользователь",
  "newuser_create": "Создать"
}

M main.go => main.go +0 -1
@@ 3,7 3,6 @@
//go:generate go run github.com/valyala/quicktemplate/qtc -dir=mycoopts
//go:generate go run github.com/valyala/quicktemplate/qtc -dir=auth
//go:generate go run github.com/valyala/quicktemplate/qtc -dir=hypview
//go:generate go run github.com/valyala/quicktemplate/qtc -dir=admin
// Command mycorrhiza is a program that runs a mycorrhiza wiki.
package main


M static/default.css => static/default.css +2 -0
@@ 502,6 502,8 @@ kbd {
	border: 1px dashed #999;
}

a.btn_destructive,
a.btn_destructive:visited,
.btn_destructive {
	border-color: #aa1818;
	background-color: #ee4343;

M viewutil/base.html => viewutil/base.html +2 -0
@@ 1,6 1,8 @@
{{define "confirm"}}Confirm{{end}}
{{define "cancel"}}Cancel{{end}}
{{define "save"}}Save{{end}}
{{define "error"}}Error{{end}}
{{define "delete"}}Delete{{end}}
{{define "page"}}
<!doctype html>
<html lang="{{.Meta.Locale}}">

M viewutil/viewutil.go => viewutil/viewutil.go +2 -0
@@ 28,6 28,8 @@ const ruText = `
{{define "confirm"}}Подтвердить{{end}}
{{define "cancel"}}Отмена{{end}}
{{define "save"}}Сохранить{{end}}
{{define "error"}}Ошибка{{end}}
{{define "delete"}}Удалить{{end}}
`

func Init() {