diff --git a/admin/view.go b/admin/view.go
deleted file mode 100644
index 26c1ce2..0000000
--- a/admin/view.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package admin
-
-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"
-)
-
-const adminTranslationRu = `
-{{define "panel title"}}Панель админстратора{{end}}
-{{define "panel safe section title"}}Безопасная секция{{end}}
-{{define "panel link about"}}Об этой вики{{end}}
-{{define "panel update header"}}Обновить ссылки в верхней панели{{end}}
-{{define "panel link user list"}}Список пользователей{{end}}
-{{define "panel users"}}Управление пользователями{{end}}
-{{define "panel unsafe section title"}}Опасная секция{{end}}
-{{define "panel shutdown"}}Выключить вики{{end}}
-{{define "panel reindex hyphae"}}Переиндексировать гифы{{end}}
-{{define "panel interwiki"}}Интервики{{end}}
-
-{{define "manage users"}}Управление пользователями{{end}}
-{{define "create user"}}Создать пользователя{{end}}
-{{define "reindex users"}}Переиндексировать пользователей{{end}}
-{{define "name"}}Имя{{end}}
-{{define "group"}}Группа{{end}}
-{{define "registered at"}}Зарегистрирован{{end}}
-{{define "actions"}}Действия{{end}}
-{{define "edit"}}Изменить{{end}}
-
-{{define "new user"}}Новый пользователь{{end}}
-{{define "password"}}Пароль{{end}}
-{{define "confirm password"}}Подтвердить пароль{{end}}
-{{define "change password"}}Изменить пароль{{end}}
-{{define "non local password change"}}Поменять пароль можно только у локальных пользователей.{{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, listChain, newUserChain, editUserChain, deleteUserChain viewutil.Chain
-)
-
-func Init(rtr *mux.Router) {
- rtr.HandleFunc("/shutdown", handlerAdminShutdown).Methods(http.MethodPost)
- rtr.HandleFunc("/reindex-users", handlerAdminReindexUsers).Methods(http.MethodPost)
-
- rtr.HandleFunc("/new-user", handlerAdminUserNew).Methods(http.MethodGet, http.MethodPost)
- rtr.HandleFunc("/users/{username}/edit", handlerAdminUserEdit).Methods(http.MethodGet, http.MethodPost)
- rtr.HandleFunc("/users/{username}/change-password", handlerAdminUserChangePassword).Methods(http.MethodPost)
- rtr.HandleFunc("/users/{username}/delete", handlerAdminUserDelete).Methods(http.MethodGet, http.MethodPost)
- rtr.HandleFunc("/users", handlerAdminUsers)
-
- rtr.HandleFunc("/", handlerAdmin)
-
- 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) {
- viewutil.ExecutePage(meta, panelChain, &viewutil.BaseData{})
-}
-
-type listData struct {
- *viewutil.BaseData
- UserHypha string
- Users []*user.User
-}
-
-func viewList(meta viewutil.Meta, users []*user.User) {
- viewutil.ExecutePage(meta, listChain, listData{
- BaseData: &viewutil.BaseData{},
- UserHypha: cfg.UserHypha,
- 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,
- })
-}
diff --git a/auth/auth.qtpl b/auth/auth.qtpl
deleted file mode 100644
index 2e34332..0000000
--- a/auth/auth.qtpl
+++ /dev/null
@@ -1,213 +0,0 @@
-{% import "net/http" %}
-{% import "sort" %}
-{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
-{% import "github.com/bouncepaw/mycorrhiza/l18n" %}
-{% import "github.com/bouncepaw/mycorrhiza/user" %}
-
-{% func Register(rq *http.Request) %}
-{% code
- lc := l18n.FromRequest(rq)
-%}
-
-
-
-{% endfunc %}
-
-{% func Login(lc *l18n.Localizer) %}
-
-
-
-{% endfunc %}
-
-Telegram auth widget was requested by Yogurt. As you can see, we don't offer user administrators control over it. Of course we don't.
-{% func telegramWidget(lc *l18n.Localizer) %}
-{% if cfg.TelegramEnabled %}
-
{%s lc.Get("auth.telegram_tip") %}
-
-{% endif %}
-{% endfunc %}
-
-{% func LoginError(err string, lc *l18n.Localizer) %}
-
-
- {% switch err %}
- {% case "unknown username" %}
- {%s lc.Get("auth.error_username") %}
- {% case "wrong password" %}
- {%s lc.Get("auth.error_password") %}
- {% default %}
- {%s err %}
- {% endswitch %}
- ← {%s lc.Get("auth.try_again") %}
-
-
-{% endfunc %}
-
-{% func Logout(can bool, lc *l18n.Localizer) %}
-
-
-
-{% endfunc %}
-
-{% func Lock(lc *l18n.Localizer) %}
-
-
-
-
-
- 🔒 {%s lc.Get("auth.lock_title") %}
-
-
-
-
-
-
- 🔒
- {%s lc.Get("auth.lock_title") %}
-
- {%= telegramWidget(lc) %}
-
-
-
-
-{% endfunc %}
-
-{% code
-var userListL10n = map[string]L10nEntry{
- "heading": En("List of users").Ru("Список пользователей"),
- "administrators": En("Administrators").Ru("Администраторы"),
- "moderators": En("Moderators").Ru("Модераторы"),
- "editors": En("Editors").Ru("Редакторы"),
- "readers": En("Readers").Ru("Читатели"),
-}
-%}
-
-{% func UserList(lc *l18n.Localizer) %}
-
-{% code
-var get = func(key string) string {
- return userListL10n[key].Get(lc.Locale)
-}
-
-var (
- admins = make([]string, 0)
- moderators = make([]string, 0)
- editors = make([]string, 0)
- readers = make([]string, 0)
-)
-for u := range user.YieldUsers() {
- switch u.Group {
- // What if we place the users into sorted slices?
- case "admin":
- admins = append(admins, u.Name)
- case "moderator":
- moderators = append(moderators, u.Name)
- case "editor", "trusted":
- editors = append(editors, u.Name)
- case "reader":
- readers = append(readers, u.Name)
- }
-}
-sort.Strings(admins)
-sort.Strings(moderators)
-sort.Strings(editors)
-sort.Strings(readers)
-%}
- {%s get("heading") %}
-
- {%s get("administrators") %}
- {% for _, name := range admins %}
- {%s name %}
- {% endfor %}
-
-
- {%s get("moderators") %}
- {% for _, name := range moderators %}
- {%s name %}
- {% endfor %}
-
-
- {%s get("editors") %}
- {% for _, name := range editors %}
- {%s name %}
- {% endfor %}
-
-
- {%s get("readers") %}
- {% for _, name := range readers %}
- {%s name %}
- {% endfor %}
-
-
-{% endfunc %}
diff --git a/auth/auth.qtpl.go b/auth/auth.qtpl.go
deleted file mode 100644
index dfbbd48..0000000
--- a/auth/auth.qtpl.go
+++ /dev/null
@@ -1,805 +0,0 @@
-// Code generated by qtc from "auth.qtpl". DO NOT EDIT.
-// See https://github.com/valyala/quicktemplate for details.
-
-//line auth/auth.qtpl:1
-package auth
-
-//line auth/auth.qtpl:1
-import "net/http"
-
-//line auth/auth.qtpl:2
-import "sort"
-
-//line auth/auth.qtpl:3
-import "github.com/bouncepaw/mycorrhiza/cfg"
-
-//line auth/auth.qtpl:4
-import "github.com/bouncepaw/mycorrhiza/l18n"
-
-//line auth/auth.qtpl:5
-import "github.com/bouncepaw/mycorrhiza/user"
-
-//line auth/auth.qtpl:7
-import (
- qtio422016 "io"
-
- qt422016 "github.com/valyala/quicktemplate"
-)
-
-//line auth/auth.qtpl:7
-var (
- _ = qtio422016.Copy
- _ = qt422016.AcquireByteBuffer
-)
-
-//line auth/auth.qtpl:7
-func StreamRegister(qw422016 *qt422016.Writer, rq *http.Request) {
-//line auth/auth.qtpl:7
- qw422016.N().S(`
-`)
-//line auth/auth.qtpl:9
- lc := l18n.FromRequest(rq)
-
-//line auth/auth.qtpl:10
- qw422016.N().S(`
-
-
-
-`)
-//line auth/auth.qtpl:41
-}
-
-//line auth/auth.qtpl:41
-func WriteRegister(qq422016 qtio422016.Writer, rq *http.Request) {
-//line auth/auth.qtpl:41
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line auth/auth.qtpl:41
- StreamRegister(qw422016, rq)
-//line auth/auth.qtpl:41
- qt422016.ReleaseWriter(qw422016)
-//line auth/auth.qtpl:41
-}
-
-//line auth/auth.qtpl:41
-func Register(rq *http.Request) string {
-//line auth/auth.qtpl:41
- qb422016 := qt422016.AcquireByteBuffer()
-//line auth/auth.qtpl:41
- WriteRegister(qb422016, rq)
-//line auth/auth.qtpl:41
- qs422016 := string(qb422016.B)
-//line auth/auth.qtpl:41
- qt422016.ReleaseByteBuffer(qb422016)
-//line auth/auth.qtpl:41
- return qs422016
-//line auth/auth.qtpl:41
-}
-
-//line auth/auth.qtpl:43
-func StreamLogin(qw422016 *qt422016.Writer, lc *l18n.Localizer) {
-//line auth/auth.qtpl:43
- qw422016.N().S(`
-
-
- `)
-//line auth/auth.qtpl:46
- if cfg.UseAuth {
-//line auth/auth.qtpl:46
- qw422016.N().S(`
-
- `)
-//line auth/auth.qtpl:62
- streamtelegramWidget(qw422016, lc)
-//line auth/auth.qtpl:62
- qw422016.N().S(`
- `)
-//line auth/auth.qtpl:63
- } else {
-//line auth/auth.qtpl:63
- qw422016.N().S(`
- `)
-//line auth/auth.qtpl:64
- qw422016.E().S(lc.Get("auth.noauth"))
-//line auth/auth.qtpl:64
- qw422016.N().S(`
- ← `)
-//line auth/auth.qtpl:65
- qw422016.E().S(lc.Get("auth.go_home"))
-//line auth/auth.qtpl:65
- qw422016.N().S(`
- `)
-//line auth/auth.qtpl:66
- }
-//line auth/auth.qtpl:66
- qw422016.N().S(`
-
-
-`)
-//line auth/auth.qtpl:69
-}
-
-//line auth/auth.qtpl:69
-func WriteLogin(qq422016 qtio422016.Writer, lc *l18n.Localizer) {
-//line auth/auth.qtpl:69
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line auth/auth.qtpl:69
- StreamLogin(qw422016, lc)
-//line auth/auth.qtpl:69
- qt422016.ReleaseWriter(qw422016)
-//line auth/auth.qtpl:69
-}
-
-//line auth/auth.qtpl:69
-func Login(lc *l18n.Localizer) string {
-//line auth/auth.qtpl:69
- qb422016 := qt422016.AcquireByteBuffer()
-//line auth/auth.qtpl:69
- WriteLogin(qb422016, lc)
-//line auth/auth.qtpl:69
- qs422016 := string(qb422016.B)
-//line auth/auth.qtpl:69
- qt422016.ReleaseByteBuffer(qb422016)
-//line auth/auth.qtpl:69
- return qs422016
-//line auth/auth.qtpl:69
-}
-
-// Telegram auth widget was requested by Yogurt. As you can see, we don't offer user administrators control over it. Of course we don't.
-
-//line auth/auth.qtpl:72
-func streamtelegramWidget(qw422016 *qt422016.Writer, lc *l18n.Localizer) {
-//line auth/auth.qtpl:72
- qw422016.N().S(`
-`)
-//line auth/auth.qtpl:73
- if cfg.TelegramEnabled {
-//line auth/auth.qtpl:73
- qw422016.N().S(`
-`)
-//line auth/auth.qtpl:74
- qw422016.E().S(lc.Get("auth.telegram_tip"))
-//line auth/auth.qtpl:74
- qw422016.N().S(`
-
-`)
-//line auth/auth.qtpl:76
- }
-//line auth/auth.qtpl:76
- qw422016.N().S(`
-`)
-//line auth/auth.qtpl:77
-}
-
-//line auth/auth.qtpl:77
-func writetelegramWidget(qq422016 qtio422016.Writer, lc *l18n.Localizer) {
-//line auth/auth.qtpl:77
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line auth/auth.qtpl:77
- streamtelegramWidget(qw422016, lc)
-//line auth/auth.qtpl:77
- qt422016.ReleaseWriter(qw422016)
-//line auth/auth.qtpl:77
-}
-
-//line auth/auth.qtpl:77
-func telegramWidget(lc *l18n.Localizer) string {
-//line auth/auth.qtpl:77
- qb422016 := qt422016.AcquireByteBuffer()
-//line auth/auth.qtpl:77
- writetelegramWidget(qb422016, lc)
-//line auth/auth.qtpl:77
- qs422016 := string(qb422016.B)
-//line auth/auth.qtpl:77
- qt422016.ReleaseByteBuffer(qb422016)
-//line auth/auth.qtpl:77
- return qs422016
-//line auth/auth.qtpl:77
-}
-
-//line auth/auth.qtpl:79
-func StreamLoginError(qw422016 *qt422016.Writer, err string, lc *l18n.Localizer) {
-//line auth/auth.qtpl:79
- qw422016.N().S(`
-
-
- `)
-//line auth/auth.qtpl:82
- switch err {
-//line auth/auth.qtpl:83
- case "unknown username":
-//line auth/auth.qtpl:83
- qw422016.N().S(`
- `)
-//line auth/auth.qtpl:84
- qw422016.E().S(lc.Get("auth.error_username"))
-//line auth/auth.qtpl:84
- qw422016.N().S(`
- `)
-//line auth/auth.qtpl:85
- case "wrong password":
-//line auth/auth.qtpl:85
- qw422016.N().S(`
- `)
-//line auth/auth.qtpl:86
- qw422016.E().S(lc.Get("auth.error_password"))
-//line auth/auth.qtpl:86
- qw422016.N().S(`
- `)
-//line auth/auth.qtpl:87
- default:
-//line auth/auth.qtpl:87
- qw422016.N().S(`
- `)
-//line auth/auth.qtpl:88
- qw422016.E().S(err)
-//line auth/auth.qtpl:88
- qw422016.N().S(`
- `)
-//line auth/auth.qtpl:89
- }
-//line auth/auth.qtpl:89
- qw422016.N().S(`
- ← `)
-//line auth/auth.qtpl:90
- qw422016.E().S(lc.Get("auth.try_again"))
-//line auth/auth.qtpl:90
- qw422016.N().S(`
-
-
-`)
-//line auth/auth.qtpl:93
-}
-
-//line auth/auth.qtpl:93
-func WriteLoginError(qq422016 qtio422016.Writer, err string, lc *l18n.Localizer) {
-//line auth/auth.qtpl:93
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line auth/auth.qtpl:93
- StreamLoginError(qw422016, err, lc)
-//line auth/auth.qtpl:93
- qt422016.ReleaseWriter(qw422016)
-//line auth/auth.qtpl:93
-}
-
-//line auth/auth.qtpl:93
-func LoginError(err string, lc *l18n.Localizer) string {
-//line auth/auth.qtpl:93
- qb422016 := qt422016.AcquireByteBuffer()
-//line auth/auth.qtpl:93
- WriteLoginError(qb422016, err, lc)
-//line auth/auth.qtpl:93
- qs422016 := string(qb422016.B)
-//line auth/auth.qtpl:93
- qt422016.ReleaseByteBuffer(qb422016)
-//line auth/auth.qtpl:93
- return qs422016
-//line auth/auth.qtpl:93
-}
-
-//line auth/auth.qtpl:95
-func StreamLogout(qw422016 *qt422016.Writer, can bool, lc *l18n.Localizer) {
-//line auth/auth.qtpl:95
- qw422016.N().S(`
-
-
-
-`)
-//line auth/auth.qtpl:111
-}
-
-//line auth/auth.qtpl:111
-func WriteLogout(qq422016 qtio422016.Writer, can bool, lc *l18n.Localizer) {
-//line auth/auth.qtpl:111
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line auth/auth.qtpl:111
- StreamLogout(qw422016, can, lc)
-//line auth/auth.qtpl:111
- qt422016.ReleaseWriter(qw422016)
-//line auth/auth.qtpl:111
-}
-
-//line auth/auth.qtpl:111
-func Logout(can bool, lc *l18n.Localizer) string {
-//line auth/auth.qtpl:111
- qb422016 := qt422016.AcquireByteBuffer()
-//line auth/auth.qtpl:111
- WriteLogout(qb422016, can, lc)
-//line auth/auth.qtpl:111
- qs422016 := string(qb422016.B)
-//line auth/auth.qtpl:111
- qt422016.ReleaseByteBuffer(qb422016)
-//line auth/auth.qtpl:111
- return qs422016
-//line auth/auth.qtpl:111
-}
-
-//line auth/auth.qtpl:113
-func StreamLock(qw422016 *qt422016.Writer, lc *l18n.Localizer) {
-//line auth/auth.qtpl:113
- qw422016.N().S(`
-
-
-
-
-
- 🔒 `)
-//line auth/auth.qtpl:119
- qw422016.E().S(lc.Get("auth.lock_title"))
-//line auth/auth.qtpl:119
- qw422016.N().S(`
-
-
-
-
-
-
- 🔒
- `)
-//line auth/auth.qtpl:127
- qw422016.E().S(lc.Get("auth.lock_title"))
-//line auth/auth.qtpl:127
- qw422016.N().S(`
-
- `)
-//line auth/auth.qtpl:139
- streamtelegramWidget(qw422016, lc)
-//line auth/auth.qtpl:139
- qw422016.N().S(`
-
-
-
-
-`)
-//line auth/auth.qtpl:144
-}
-
-//line auth/auth.qtpl:144
-func WriteLock(qq422016 qtio422016.Writer, lc *l18n.Localizer) {
-//line auth/auth.qtpl:144
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line auth/auth.qtpl:144
- StreamLock(qw422016, lc)
-//line auth/auth.qtpl:144
- qt422016.ReleaseWriter(qw422016)
-//line auth/auth.qtpl:144
-}
-
-//line auth/auth.qtpl:144
-func Lock(lc *l18n.Localizer) string {
-//line auth/auth.qtpl:144
- qb422016 := qt422016.AcquireByteBuffer()
-//line auth/auth.qtpl:144
- WriteLock(qb422016, lc)
-//line auth/auth.qtpl:144
- qs422016 := string(qb422016.B)
-//line auth/auth.qtpl:144
- qt422016.ReleaseByteBuffer(qb422016)
-//line auth/auth.qtpl:144
- return qs422016
-//line auth/auth.qtpl:144
-}
-
-//line auth/auth.qtpl:147
-var userListL10n = map[string]L10nEntry{
- "heading": En("List of users").Ru("Список пользователей"),
- "administrators": En("Administrators").Ru("Администраторы"),
- "moderators": En("Moderators").Ru("Модераторы"),
- "editors": En("Editors").Ru("Редакторы"),
- "readers": En("Readers").Ru("Читатели"),
-}
-
-//line auth/auth.qtpl:156
-func StreamUserList(qw422016 *qt422016.Writer, lc *l18n.Localizer) {
-//line auth/auth.qtpl:156
- qw422016.N().S(`
-
-`)
-//line auth/auth.qtpl:159
- var get = func(key string) string {
- return userListL10n[key].Get(lc.Locale)
- }
-
- var (
- admins = make([]string, 0)
- moderators = make([]string, 0)
- editors = make([]string, 0)
- readers = make([]string, 0)
- )
- for u := range user.YieldUsers() {
- switch u.Group {
- // What if we place the users into sorted slices?
- case "admin":
- admins = append(admins, u.Name)
- case "moderator":
- moderators = append(moderators, u.Name)
- case "editor", "trusted":
- editors = append(editors, u.Name)
- case "reader":
- readers = append(readers, u.Name)
- }
- }
- sort.Strings(admins)
- sort.Strings(moderators)
- sort.Strings(editors)
- sort.Strings(readers)
-
-//line auth/auth.qtpl:186
- qw422016.N().S(`
- `)
-//line auth/auth.qtpl:187
- qw422016.E().S(get("heading"))
-//line auth/auth.qtpl:187
- qw422016.N().S(`
-
-
-
-
-
-`)
-//line auth/auth.qtpl:213
-}
-
-//line auth/auth.qtpl:213
-func WriteUserList(qq422016 qtio422016.Writer, lc *l18n.Localizer) {
-//line auth/auth.qtpl:213
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line auth/auth.qtpl:213
- StreamUserList(qw422016, lc)
-//line auth/auth.qtpl:213
- qt422016.ReleaseWriter(qw422016)
-//line auth/auth.qtpl:213
-}
-
-//line auth/auth.qtpl:213
-func UserList(lc *l18n.Localizer) string {
-//line auth/auth.qtpl:213
- qb422016 := qt422016.AcquireByteBuffer()
-//line auth/auth.qtpl:213
- WriteUserList(qb422016, lc)
-//line auth/auth.qtpl:213
- qs422016 := string(qb422016.B)
-//line auth/auth.qtpl:213
- qt422016.ReleaseByteBuffer(qb422016)
-//line auth/auth.qtpl:213
- return qs422016
-//line auth/auth.qtpl:213
-}
diff --git a/auth/get_rid_of_it.go b/auth/get_rid_of_it.go
deleted file mode 100644
index a7b0c91..0000000
--- a/auth/get_rid_of_it.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package auth
-
-type L10nEntry struct {
- _en string
- _ru string
-}
-
-func En(v string) L10nEntry {
- return L10nEntry{_en: v}
-}
-
-func (e L10nEntry) Ru(v string) L10nEntry {
- e._ru = v
- return e
-}
-
-func (e L10nEntry) Get(lang string) string {
- if lang == "ru" && e._ru != "" {
- return e._ru
- }
- return e._en
-}
diff --git a/auth/web.go b/auth/web.go
deleted file mode 100644
index ee93bc9..0000000
--- a/auth/web.go
+++ /dev/null
@@ -1,225 +0,0 @@
-package auth
-
-import (
- "errors"
- "fmt"
- "io"
- "log"
- "mime"
- "net/http"
- "strings"
-
- "github.com/bouncepaw/mycorrhiza/viewutil"
-
- "github.com/gorilla/mux"
-
- "github.com/bouncepaw/mycorrhiza/cfg"
- "github.com/bouncepaw/mycorrhiza/l18n"
- "github.com/bouncepaw/mycorrhiza/user"
- "github.com/bouncepaw/mycorrhiza/util"
-)
-
-func InitAuth(r *mux.Router) {
- r.HandleFunc("/user-list", handlerUserList)
- r.HandleFunc("/lock", handlerLock)
- // The check below saves a lot of extra checks and lines of codes in other places in this file.
- if !cfg.UseAuth {
- return
- }
- if cfg.AllowRegistration {
- r.HandleFunc("/register", handlerRegister).Methods(http.MethodPost, http.MethodGet)
- }
- if cfg.TelegramEnabled {
- r.HandleFunc("/telegram-login", handlerTelegramLogin)
- }
- r.HandleFunc("/login", handlerLogin)
- r.HandleFunc("/logout", handlerLogout)
-}
-
-func handlerUserList(w http.ResponseWriter, rq *http.Request) {
- lc := l18n.FromRequest(rq)
- w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
- w.WriteHeader(http.StatusOK)
- w.Write([]byte(viewutil.Base(viewutil.MetaFrom(w, rq), lc.Get("ui.users_title"), UserList(lc), map[string]string{})))
-}
-
-func handlerLock(w http.ResponseWriter, rq *http.Request) {
- _, _ = io.WriteString(w, Lock(l18n.FromRequest(rq)))
-}
-
-// handlerRegister displays the register form (GET) or registers the user (POST).
-func handlerRegister(w http.ResponseWriter, rq *http.Request) {
- lc := l18n.FromRequest(rq)
- util.PrepareRq(rq)
- if rq.Method == http.MethodGet {
- _, _ = io.WriteString(
- w,
- viewutil.Base(
- viewutil.MetaFrom(w, rq),
- lc.Get("auth.register_title"),
- Register(rq),
- map[string]string{},
- ),
- )
- return
- }
-
- var (
- username = rq.PostFormValue("username")
- password = rq.PostFormValue("password")
- err = user.Register(username, password, "editor", "local", false)
- )
- if err != nil {
- log.Printf("Failed to register ‘%s’: %s", username, err.Error())
- w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
- w.WriteHeader(http.StatusBadRequest)
- _, _ = io.WriteString(
- w,
- viewutil.Base(
- viewutil.MetaFrom(w, rq),
- lc.Get("auth.register_title"),
- fmt.Sprintf(
- `%s
%s
`,
- err.Error(),
- lc.Get("auth.try_again"),
- ),
- map[string]string{},
- ),
- )
- return
- }
-
- log.Printf("Successfully registered ‘%s’", username)
- if err := user.LoginDataHTTP(w, username, password); err != nil {
- return
- }
- http.Redirect(w, rq, "/"+rq.URL.RawQuery, http.StatusSeeOther)
-}
-
-// handlerLogout shows the logout form (GET) or logs the user out (POST).
-func handlerLogout(w http.ResponseWriter, rq *http.Request) {
- if rq.Method == http.MethodGet {
- var (
- u = user.FromRequest(rq)
- can = u != nil
- lc = l18n.FromRequest(rq)
- )
- w.Header().Set("Content-Type", "text/html;charset=utf-8")
- if can {
- log.Println("User", u.Name, "tries to log out")
- w.WriteHeader(http.StatusOK)
- } else {
- log.Println("Unknown user tries to log out")
- w.WriteHeader(http.StatusForbidden)
- }
- _, _ = io.WriteString(
- w,
- viewutil.Base(viewutil.MetaFrom(w, rq), lc.Get("auth.logout_title"), Logout(can, lc), map[string]string{}),
- )
- } else if rq.Method == http.MethodPost {
- user.LogoutFromRequest(w, rq)
- http.Redirect(w, rq, "/", http.StatusSeeOther)
- }
-}
-
-// handlerLogin shows the login form (GET) or logs the user in (POST).
-func handlerLogin(w http.ResponseWriter, rq *http.Request) {
- lc := l18n.FromRequest(rq)
- if rq.Method == http.MethodGet {
- w.Header().Set("Content-Type", "text/html;charset=utf-8")
- w.WriteHeader(http.StatusOK)
- _, _ = io.WriteString(
- w,
- viewutil.Base(
- viewutil.MetaFrom(w, rq),
- lc.Get("auth.login_title"),
- Login(lc),
- map[string]string{},
- ),
- )
- } else if rq.Method == http.MethodPost {
- var (
- username = util.CanonicalName(rq.PostFormValue("username"))
- password = rq.PostFormValue("password")
- err = user.LoginDataHTTP(w, username, password)
- )
- if err != nil {
- w.Header().Set("Content-Type", "text/html;charset=utf-8")
- w.WriteHeader(http.StatusInternalServerError)
- _, _ = io.WriteString(w, viewutil.Base(viewutil.MetaFrom(w, rq), err.Error(), LoginError(err.Error(), lc), map[string]string{}))
- return
- }
- http.Redirect(w, rq, "/", http.StatusSeeOther)
- }
-}
-
-func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) {
- // Note there is no lock here.
- lc := l18n.FromRequest(rq)
- w.Header().Set("Content-Type", "text/html;charset=utf-8")
- rq.ParseForm()
- var (
- values = rq.URL.Query()
- username = strings.ToLower(values.Get("username"))
- seemsValid = user.TelegramAuthParamsAreValid(values)
- err = user.Register(
- username,
- "", // Password matters not
- "editor",
- "telegram",
- false,
- )
- )
- // If registering a user via Telegram failed, because a Telegram user with this name
- // has already registered, then everything is actually ok!
- if user.HasUsername(username) && user.ByName(username).Source == "telegram" {
- err = nil
- }
-
- if !seemsValid {
- err = errors.New("Wrong parameters")
- }
-
- if err != nil {
- log.Printf("Failed to register ‘%s’ using Telegram: %s", username, err.Error())
- w.WriteHeader(http.StatusBadRequest)
- _, _ = io.WriteString(
- w,
- viewutil.Base(
- viewutil.MetaFrom(w, rq),
- lc.Get("ui.error"),
- fmt.Sprintf(
- `%s
%s
%s
`,
- lc.Get("auth.error_telegram"),
- err.Error(),
- lc.Get("auth.go_login"),
- ),
- map[string]string{},
- ),
- )
- return
- }
-
- errmsg := user.LoginDataHTTP(w, username, "")
- if errmsg != nil {
- log.Printf("Failed to login ‘%s’ using Telegram: %s", username, err.Error())
- w.WriteHeader(http.StatusBadRequest)
- _, _ = io.WriteString(
- w,
- viewutil.Base(
- viewutil.MetaFrom(w, rq),
- "Error",
- fmt.Sprintf(
- `%s
%s
%s
`,
- lc.Get("auth.error_telegram"),
- err.Error(),
- lc.Get("auth.go_login"),
- ),
- map[string]string{},
- ),
- )
- return
- }
- log.Printf("Authorize ‘%s’ from Telegram", username)
- http.Redirect(w, rq, "/", http.StatusSeeOther)
-}
diff --git a/backlinks/web.go b/backlinks/web.go
deleted file mode 100644
index ff5cb75..0000000
--- a/backlinks/web.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package backlinks
-
-import (
- "embed"
- "github.com/bouncepaw/mycorrhiza/hyphae"
- "github.com/bouncepaw/mycorrhiza/util"
- "github.com/bouncepaw/mycorrhiza/viewutil"
- "github.com/gorilla/mux"
- "net/http"
- "sort"
-)
-
-func InitHandlers(rtr *mux.Router) {
- rtr.PathPrefix("/backlinks/").HandlerFunc(handlerBacklinks)
- rtr.PathPrefix("/orphans").HandlerFunc(handlerOrphans)
- chainBacklinks = viewutil.CopyEnRuWith(fs, "view_backlinks.html", ruTranslation)
- chainOrphans = viewutil.CopyEnRuWith(fs, "view_orphans.html", ruTranslation)
-}
-
-// handlerBacklinks lists all backlinks to a hypha.
-func handlerBacklinks(w http.ResponseWriter, rq *http.Request) {
- var (
- hyphaName = util.HyphaNameFromRq(rq, "backlinks")
- backlinks []string
- )
- for b := range yieldHyphaBacklinks(hyphaName) {
- backlinks = append(backlinks, b)
- }
- viewBacklinks(viewutil.MetaFrom(w, rq), hyphaName, backlinks)
-}
-
-func handlerOrphans(w http.ResponseWriter, rq *http.Request) {
- var orphans []string
- for h := range hyphae.YieldExistingHyphae() {
- if BacklinksCount(h.CanonicalName()) == 0 {
- orphans = append(orphans, h.CanonicalName())
- }
- }
- sort.Strings(orphans)
- viewOrphans(viewutil.MetaFrom(w, rq), orphans)
-}
-
-var (
- //go:embed *.html
- fs embed.FS
- ruTranslation = `
-{{define "backlinks to text"}}Обратные ссылки на {{.}}{{end}}
-{{define "backlinks to link"}}Обратные ссылки на {{beautifulName .}} {{end}}
-{{define "description"}}Ниже перечислены гифы, на которых есть ссылка на эту гифу, трансклюзия этой гифы или эта гифа вставлена как изображение.{{end}}
-{{define "orphaned hyphae"}}Гифы-сироты{{end}}
-{{define "orphan description"}}Ниже перечислены гифы без ссылок на них.{{end}}
-`
- chainBacklinks viewutil.Chain
- chainOrphans viewutil.Chain
-)
-
-type backlinksData struct {
- *viewutil.BaseData
- HyphaName string
- Backlinks []string
-}
-
-func viewBacklinks(meta viewutil.Meta, hyphaName string, backlinks []string) {
- viewutil.ExecutePage(meta, chainBacklinks, backlinksData{
- BaseData: &viewutil.BaseData{
- Addr: "/backlinks/" + hyphaName,
- },
- HyphaName: hyphaName,
- Backlinks: backlinks,
- })
-}
-
-type orphansData struct {
- *viewutil.BaseData
- Orphans []string
-}
-
-func viewOrphans(meta viewutil.Meta, orphans []string) {
- viewutil.ExecutePage(meta, chainOrphans, orphansData{
- BaseData: &viewutil.BaseData{
- Addr: "/orphans",
- },
- Orphans: orphans,
- })
-}
diff --git a/categories/view_card.html b/categories/view_card.html
deleted file mode 100644
index 3262154..0000000
--- a/categories/view_card.html
+++ /dev/null
@@ -1,37 +0,0 @@
-{{define "category card"}}
-{{if or .GivenPermissionToModify (len .Categories)}}
- {{$hyphaName := .HyphaName}}
- {{$givenPermission := .GivenPermissionToModify}}
-
- {{block `categories` .}}Categories{{end}}
-
- {{range .Categories}}
-
- {{beautifulName .}}
-
-
- {{end}}
- {{if .GivenPermissionToModify}}
-
-
-
- {{end}}
-
-
-{{end}}{{end}}
\ No newline at end of file
diff --git a/categories/view_edit.html b/categories/view_edit.html
deleted file mode 100644
index b8e93d6..0000000
--- a/categories/view_edit.html
+++ /dev/null
@@ -1,37 +0,0 @@
-{{define "edit category x"}}Edit category {{beautifulName .}}{{end}}
-{{define "title"}}{{template "edit category x" .CatName}}{{end}}
-{{define "body"}}
-
- {{block "edit category heading" .CatName}}Edit category {{beautifulName .}} {{end}}
- {{if len .Hyphae | not}}
- {{block "empty cat" .}}This category is empty{{end}}
- {{end}}
-
- {{if .GivenPermissionToModify}}
- {{block "add to category title" .}}Add a hypha to the category{{end}}
-
-
- {{if len .Hyphae}}
- {{block "remove hyphae" .}}Remove hyphae from the category{{end}}
-
- {{end}}{{end}}
-
-{{end}}
diff --git a/categories/views.go b/categories/views.go
deleted file mode 100644
index 9292ede..0000000
--- a/categories/views.go
+++ /dev/null
@@ -1,107 +0,0 @@
-package categories
-
-import (
- "embed"
- "github.com/bouncepaw/mycorrhiza/viewutil"
- "log"
- "sort"
- "strings"
-)
-
-const ruTranslation = `
-{{define "empty cat"}}Эта категория пуста.{{end}}
-{{define "cat"}}Категория{{end}}
-{{define "hypha name"}}Название гифы{{end}}
-{{define "categories"}}Категории{{end}}
-{{define "placeholder"}}Название категории...{{end}}
-{{define "remove from category title"}}Убрать гифу из этой категории{{end}}
-{{define "add to category title"}}Добавить гифу в эту категорию{{end}}
-{{define "category list"}}Список категорий{{end}}
-{{define "no categories"}}В этой вики нет категорий.{{end}}
-{{define "category x"}}Категория {{. | beautifulName}}{{end}}
-
-{{define "edit category x"}}Редактирование категории {{beautifulName .}}{{end}}
-{{define "edit category heading"}}Редактирование категории {{beautifulName .}} {{end}}
-{{define "add"}}Добавить{{end}}
-{{define "remove hyphae"}}Убрать гифы из этой категории{{end}}
-{{define "remove"}}Убрать{{end}}
-{{define "edit"}}Редактировать{{end}}
-`
-
-var (
- //go:embed *.html
- fs embed.FS
- viewListChain, viewPageChain, viewCardChain, viewEditChain viewutil.Chain
-)
-
-func prepareViews() {
- viewCardChain = viewutil.CopyEnRuWith(fs, "view_card.html", ruTranslation)
- viewListChain = viewutil.CopyEnRuWith(fs, "view_list.html", ruTranslation)
- viewPageChain = viewutil.CopyEnRuWith(fs, "view_page.html", ruTranslation)
- viewEditChain = viewutil.CopyEnRuWith(fs, "view_edit.html", ruTranslation)
-}
-
-type cardData struct {
- HyphaName string
- Categories []string
- GivenPermissionToModify bool
-}
-
-// CategoryCard is the little sidebar that is shown nearby the hypha view.
-func CategoryCard(meta viewutil.Meta, hyphaName string) string {
- var buf strings.Builder
- err := viewCardChain.Get(meta).ExecuteTemplate(&buf, "category card", cardData{
- HyphaName: hyphaName,
- Categories: CategoriesWithHypha(hyphaName),
- GivenPermissionToModify: meta.U.CanProceed("add-to-category"),
- })
- if err != nil {
- log.Println(err)
- }
- return buf.String()
-}
-
-type catData struct {
- *viewutil.BaseData
- CatName string
- Hyphae []string
- GivenPermissionToModify bool
-}
-
-func categoryEdit(meta viewutil.Meta, catName string) {
- viewutil.ExecutePage(meta, viewEditChain, catData{
- BaseData: &viewutil.BaseData{
- Addr: "/edit-category/" + catName,
- },
- CatName: catName,
- Hyphae: hyphaeInCategory(catName),
- GivenPermissionToModify: meta.U.CanProceed("add-to-category"),
- })
-}
-
-func categoryPage(meta viewutil.Meta, catName string) {
- viewutil.ExecutePage(meta, viewPageChain, catData{
- BaseData: &viewutil.BaseData{
- Addr: "/category/" + catName,
- },
- CatName: catName,
- Hyphae: hyphaeInCategory(catName),
- GivenPermissionToModify: meta.U.CanProceed("add-to-category"),
- })
-}
-
-type listData struct {
- *viewutil.BaseData
- Categories []string
-}
-
-func categoryList(meta viewutil.Meta) {
- cats := listOfCategories()
- sort.Strings(cats)
- viewutil.ExecutePage(meta, viewListChain, listData{
- BaseData: &viewutil.BaseData{
- Addr: "/category",
- },
- Categories: cats,
- })
-}
diff --git a/flag.go b/flag.go
index 8253b84..70646bf 100644
--- a/flag.go
+++ b/flag.go
@@ -12,17 +12,17 @@ import (
"golang.org/x/term"
- "github.com/bouncepaw/mycorrhiza/cfg"
- "github.com/bouncepaw/mycorrhiza/files"
- "github.com/bouncepaw/mycorrhiza/user"
- "github.com/bouncepaw/mycorrhiza/version"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ "github.com/bouncepaw/mycorrhiza/internal/files"
+ user2 "github.com/bouncepaw/mycorrhiza/internal/user"
+ "github.com/bouncepaw/mycorrhiza/internal/version"
)
// CLI options are read and parsed here.
// printHelp prints the help message.
func printHelp() {
- fmt.Fprintf(
+ _, _ = fmt.Fprintf(
flag.CommandLine.Output(),
"Usage: %s WIKI_PATH\n",
os.Args[0],
@@ -70,13 +70,13 @@ func createAdminCommand(name string) {
}
cfg.UseAuth = true
cfg.AllowRegistration = true
- user.InitUserDatabase()
+ user2.InitUserDatabase()
password, err := askPass("Password")
if err != nil {
log.Fatal(err)
}
- if err := user.Register(name, password, "admin", "local", true); err != nil {
+ if err := user2.Register(name, password, "admin", "local", true); err != nil {
log.Fatal(err)
}
}
diff --git a/help/web.go b/help/web.go
index c6b3516..2259fac 100644
--- a/help/web.go
+++ b/help/web.go
@@ -2,15 +2,17 @@ package help
// stuff.go is used for meta stuff about the wiki or all hyphae at once.
import (
- "git.sr.ht/~bouncepaw/mycomarkup/v5"
- "github.com/bouncepaw/mycorrhiza/mycoopts"
- "github.com/bouncepaw/mycorrhiza/viewutil"
- "github.com/gorilla/mux"
"io"
"net/http"
"strings"
+ "github.com/bouncepaw/mycorrhiza/mycoopts"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
+
+ "git.sr.ht/~bouncepaw/mycomarkup/v5"
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
+
+ "github.com/gorilla/mux"
)
var (
diff --git a/history/feed.go b/history/feed.go
index 65a88e7..4906816 100644
--- a/history/feed.go
+++ b/history/feed.go
@@ -3,12 +3,11 @@ package history
import (
"errors"
"fmt"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
"net/url"
"strings"
"time"
- "github.com/bouncepaw/mycorrhiza/cfg"
-
"github.com/gorilla/feeds"
)
diff --git a/history/history.go b/history/history.go
index 571477c..b889e80 100644
--- a/history/history.go
+++ b/history/history.go
@@ -9,7 +9,7 @@ import (
"path/filepath"
"regexp"
- "github.com/bouncepaw/mycorrhiza/files"
+ "github.com/bouncepaw/mycorrhiza/internal/files"
"github.com/bouncepaw/mycorrhiza/util"
)
diff --git a/history/histweb/histview.go b/history/histweb/histview.go
index a761d43..196b998 100644
--- a/history/histweb/histview.go
+++ b/history/histweb/histview.go
@@ -4,12 +4,12 @@ package histweb
import (
"embed"
"fmt"
- "github.com/bouncepaw/mycorrhiza/cfg"
- "github.com/bouncepaw/mycorrhiza/files"
"github.com/bouncepaw/mycorrhiza/history"
- "github.com/bouncepaw/mycorrhiza/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ "github.com/bouncepaw/mycorrhiza/internal/files"
+ hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/util"
- "github.com/bouncepaw/mycorrhiza/viewutil"
+ viewutil2 "github.com/bouncepaw/mycorrhiza/web/viewutil"
"github.com/gorilla/mux"
"html/template"
"log"
@@ -30,9 +30,9 @@ func InitHandlers(rtr *mux.Router) {
rtr.HandleFunc("/recent-changes-atom", handlerRecentChangesAtom)
rtr.HandleFunc("/recent-changes-json", handlerRecentChangesJSON)
- chainPrimitiveDiff = viewutil.CopyEnRuWith(fs, "view_primitive_diff.html", ruTranslation)
- chainRecentChanges = viewutil.CopyEnRuWith(fs, "view_recent_changes.html", ruTranslation)
- chainHistory = viewutil.CopyEnRuWith(fs, "view_history.html", ruTranslation)
+ chainPrimitiveDiff = viewutil2.CopyEnRuWith(fs, "view_primitive_diff.html", ruTranslation)
+ chainRecentChanges = viewutil2.CopyEnRuWith(fs, "view_recent_changes.html", ruTranslation)
+ chainHistory = viewutil2.CopyEnRuWith(fs, "view_history.html", ruTranslation)
}
func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
@@ -45,12 +45,12 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
}
var (
mycoFilePath string
- h = hyphae.ByName(util.CanonicalName(slug))
+ h = hyphae2.ByName(util.CanonicalName(slug))
)
switch h := h.(type) {
- case hyphae.ExistingHypha:
+ case hyphae2.ExistingHypha:
mycoFilePath = h.TextFilePath()
- case *hyphae.EmptyHypha:
+ case *hyphae2.EmptyHypha:
mycoFilePath = filepath.Join(files.HyphaeDir(), h.CanonicalName()+".myco")
}
text, err := history.PrimitiveDiffAtRevision(mycoFilePath, revHash)
@@ -58,7 +58,7 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- primitiveDiff(viewutil.MetaFrom(w, rq), h, revHash, text)
+ primitiveDiff(viewutil2.MetaFrom(w, rq), h, revHash, text)
}
// handlerRecentChanges displays the /recent-changes/ page.
@@ -68,7 +68,7 @@ func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) {
if editCount > 100 {
return
}
- recentChanges(viewutil.MetaFrom(w, rq), editCount, history.RecentChanges(editCount))
+ recentChanges(viewutil2.MetaFrom(w, rq), editCount, history.RecentChanges(editCount))
}
// handlerHistory lists all revisions of a hypha.
@@ -83,7 +83,7 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) {
}
log.Println("Found", len(revs), "revisions for", hyphaName)
- historyView(viewutil.MetaFrom(w, rq), hyphaName, list)
+ historyView(viewutil2.MetaFrom(w, rq), hyphaName, list)
}
// genericHandlerOfFeeds is a helper function for the web feed handlers.
@@ -135,20 +135,20 @@ var (
{{define "n recent changes"}}{{.}} свеж{{if eq . 1}}ая правка{{else if le . 4}}их правок{{else}}их правок{{end}}{{end}}
{{define "recent empty"}}Правки не найдены.{{end}}
`
- chainPrimitiveDiff, chainRecentChanges, chainHistory viewutil.Chain
+ chainPrimitiveDiff, chainRecentChanges, chainHistory viewutil2.Chain
)
type recentChangesData struct {
- *viewutil.BaseData
+ *viewutil2.BaseData
EditCount int
Changes []history.Revision
UserHypha string
Stops []int
}
-func recentChanges(meta viewutil.Meta, editCount int, changes []history.Revision) {
- viewutil.ExecutePage(meta, chainRecentChanges, recentChangesData{
- BaseData: &viewutil.BaseData{},
+func recentChanges(meta viewutil2.Meta, editCount int, changes []history.Revision) {
+ viewutil2.ExecutePage(meta, chainRecentChanges, recentChangesData{
+ BaseData: &viewutil2.BaseData{},
EditCount: editCount,
Changes: changes,
UserHypha: cfg.UserHypha,
@@ -157,13 +157,13 @@ func recentChanges(meta viewutil.Meta, editCount int, changes []history.Revision
}
type primitiveDiffData struct {
- *viewutil.BaseData
+ *viewutil2.BaseData
HyphaName string
Hash string
Text template.HTML
}
-func primitiveDiff(meta viewutil.Meta, h hyphae.Hypha, hash, text string) {
+func primitiveDiff(meta viewutil2.Meta, h hyphae2.Hypha, hash, text string) {
hunks := history.SplitPrimitiveDiff(text)
if len(hunks) > 0 {
var buf strings.Builder
@@ -198,8 +198,8 @@ func primitiveDiff(meta viewutil.Meta, h hyphae.Hypha, hash, text string) {
text = fmt.Sprintf(
`%s
`, text)
}
- viewutil.ExecutePage(meta, chainPrimitiveDiff, primitiveDiffData{
- BaseData: &viewutil.BaseData{},
+ viewutil2.ExecutePage(meta, chainPrimitiveDiff, primitiveDiffData{
+ BaseData: &viewutil2.BaseData{},
HyphaName: h.CanonicalName(),
Hash: hash,
Text: template.HTML(text),
@@ -207,14 +207,14 @@ func primitiveDiff(meta viewutil.Meta, h hyphae.Hypha, hash, text string) {
}
type historyData struct {
- *viewutil.BaseData
+ *viewutil2.BaseData
HyphaName string
Contents string
}
-func historyView(meta viewutil.Meta, hyphaName, contents string) {
- viewutil.ExecutePage(meta, chainHistory, historyData{
- BaseData: &viewutil.BaseData{
+func historyView(meta viewutil2.Meta, hyphaName, contents string) {
+ viewutil2.ExecutePage(meta, chainHistory, historyData{
+ BaseData: &viewutil2.BaseData{
Addr: "/history/" + util.CanonicalName(hyphaName),
},
HyphaName: hyphaName,
diff --git a/history/operations.go b/history/operations.go
index 66aa21f..0f9eb80 100644
--- a/history/operations.go
+++ b/history/operations.go
@@ -4,11 +4,11 @@ package history
// Things related to writing history.
import (
"fmt"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
"os"
"path/filepath"
"sync"
- "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
)
diff --git a/history/revision.go b/history/revision.go
index e3aa853..7f04309 100644
--- a/history/revision.go
+++ b/history/revision.go
@@ -8,7 +8,7 @@ import (
"strings"
"time"
- "github.com/bouncepaw/mycorrhiza/files"
+ "github.com/bouncepaw/mycorrhiza/internal/files"
)
// Revision represents a revision, duh. Hash is usually short. Username is extracted from email.
diff --git a/history/view.qtpl b/history/view.qtpl
index 150ba9f..dfba661 100644
--- a/history/view.qtpl
+++ b/history/view.qtpl
@@ -1,5 +1,5 @@
{% import "fmt" %}
-{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
+{% import "github.com/bouncepaw/mycorrhiza/internal/cfg" %}
HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string.
{% func (rev Revision) HyphaeLinksHTML() %}
@@ -56,22 +56,18 @@ WithRevisions returns an html representation of `revs` that is meant to be inser
{% for _, rev := range grp %}
- {%= rev.asHistoryEntry(hyphaName) %}
+
+
+ {%s rev.timeToDisplay() %}
+
+ {%s rev.Hash %}
+ {%s rev.Message %}
+ {% if rev.Username != "anon" %}
+ by {%s rev.Username %}
+ {% endif %}
+
{% endfor %}
{% endfor %}
{% endfunc %}
-
-{% func (rev *Revision) asHistoryEntry(hyphaName string) %}
-
-
- {%s rev.timeToDisplay() %}
-
- {%s rev.Hash %}
- {%s rev.Message %}
- {% if rev.Username != "anon" %}
- by {%s rev.Username %}
- {% endif %}
-
-{% endfunc %}
\ No newline at end of file
diff --git a/history/view.qtpl.go b/history/view.qtpl.go
index 796a527..7708c28 100644
--- a/history/view.qtpl.go
+++ b/history/view.qtpl.go
@@ -8,7 +8,7 @@ package history
import "fmt"
//line history/view.qtpl:2
-import "github.com/bouncepaw/mycorrhiza/cfg"
+import "github.com/bouncepaw/mycorrhiza/internal/cfg"
// HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string.
@@ -283,141 +283,102 @@ func StreamWithRevisions(qw422016 *qt422016.Writer, hyphaName string, revs []Rev
for _, rev := range grp {
//line history/view.qtpl:58
qw422016.N().S(`
- `)
-//line history/view.qtpl:59
- rev.streamasHistoryEntry(qw422016, hyphaName)
-//line history/view.qtpl:59
+
+
+ `)
+//line history/view.qtpl:61
+ qw422016.E().S(rev.timeToDisplay())
+//line history/view.qtpl:61
+ qw422016.N().S(`
+
+ `)
+//line history/view.qtpl:63
+ qw422016.E().S(rev.Hash)
+//line history/view.qtpl:63
+ qw422016.N().S(`
+ `)
+//line history/view.qtpl:64
+ qw422016.E().S(rev.Message)
+//line history/view.qtpl:64
+ qw422016.N().S(`
+ `)
+//line history/view.qtpl:65
+ if rev.Username != "anon" {
+//line history/view.qtpl:65
+ qw422016.N().S(`
+ by `)
+//line history/view.qtpl:66
+ qw422016.E().S(rev.Username)
+//line history/view.qtpl:66
+ qw422016.N().S(`
+ `)
+//line history/view.qtpl:67
+ }
+//line history/view.qtpl:67
qw422016.N().S(`
+
`)
-//line history/view.qtpl:60
+//line history/view.qtpl:69
}
-//line history/view.qtpl:60
+//line history/view.qtpl:69
qw422016.N().S(`
`)
-//line history/view.qtpl:63
+//line history/view.qtpl:72
}
-//line history/view.qtpl:63
+//line history/view.qtpl:72
qw422016.N().S(`
`)
-//line history/view.qtpl:64
+//line history/view.qtpl:73
}
-//line history/view.qtpl:64
+//line history/view.qtpl:73
func WriteWithRevisions(qq422016 qtio422016.Writer, hyphaName string, revs []Revision) {
-//line history/view.qtpl:64
+//line history/view.qtpl:73
qw422016 := qt422016.AcquireWriter(qq422016)
-//line history/view.qtpl:64
+//line history/view.qtpl:73
StreamWithRevisions(qw422016, hyphaName, revs)
-//line history/view.qtpl:64
+//line history/view.qtpl:73
qt422016.ReleaseWriter(qw422016)
-//line history/view.qtpl:64
+//line history/view.qtpl:73
}
-//line history/view.qtpl:64
+//line history/view.qtpl:73
func WithRevisions(hyphaName string, revs []Revision) string {
-//line history/view.qtpl:64
+//line history/view.qtpl:73
qb422016 := qt422016.AcquireByteBuffer()
-//line history/view.qtpl:64
+//line history/view.qtpl:73
WriteWithRevisions(qb422016, hyphaName, revs)
-//line history/view.qtpl:64
- qs422016 := string(qb422016.B)
-//line history/view.qtpl:64
- qt422016.ReleaseByteBuffer(qb422016)
-//line history/view.qtpl:64
- return qs422016
-//line history/view.qtpl:64
-}
-
-//line history/view.qtpl:66
-func (rev *Revision) streamasHistoryEntry(qw422016 *qt422016.Writer, hyphaName string) {
-//line history/view.qtpl:66
- qw422016.N().S(`
-
-
- `)
-//line history/view.qtpl:69
- qw422016.E().S(rev.timeToDisplay())
-//line history/view.qtpl:69
- qw422016.N().S(`
-
- `)
-//line history/view.qtpl:71
- qw422016.E().S(rev.Hash)
-//line history/view.qtpl:71
- qw422016.N().S(`
- `)
-//line history/view.qtpl:72
- qw422016.E().S(rev.Message)
-//line history/view.qtpl:72
- qw422016.N().S(`
- `)
//line history/view.qtpl:73
- if rev.Username != "anon" {
-//line history/view.qtpl:73
- qw422016.N().S(`
- by `)
-//line history/view.qtpl:74
- qw422016.E().S(rev.Username)
-//line history/view.qtpl:74
- qw422016.N().S(`
- `)
-//line history/view.qtpl:75
- }
-//line history/view.qtpl:75
- qw422016.N().S(`
-
-`)
-//line history/view.qtpl:77
-}
-
-//line history/view.qtpl:77
-func (rev *Revision) writeasHistoryEntry(qq422016 qtio422016.Writer, hyphaName string) {
-//line history/view.qtpl:77
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line history/view.qtpl:77
- rev.streamasHistoryEntry(qw422016, hyphaName)
-//line history/view.qtpl:77
- qt422016.ReleaseWriter(qw422016)
-//line history/view.qtpl:77
-}
-
-//line history/view.qtpl:77
-func (rev *Revision) asHistoryEntry(hyphaName string) string {
-//line history/view.qtpl:77
- qb422016 := qt422016.AcquireByteBuffer()
-//line history/view.qtpl:77
- rev.writeasHistoryEntry(qb422016, hyphaName)
-//line history/view.qtpl:77
qs422016 := string(qb422016.B)
-//line history/view.qtpl:77
+//line history/view.qtpl:73
qt422016.ReleaseByteBuffer(qb422016)
-//line history/view.qtpl:77
+//line history/view.qtpl:73
return qs422016
-//line history/view.qtpl:77
+//line history/view.qtpl:73
}
diff --git a/httpd.go b/httpd.go
index 7ee56e7..ee2af1e 100644
--- a/httpd.go
+++ b/httpd.go
@@ -1,14 +1,13 @@
package main
import (
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
"log"
"net"
"net/http"
"os"
"strings"
"time"
-
- "github.com/bouncepaw/mycorrhiza/cfg"
)
func serveHTTP(handler http.Handler) {
diff --git a/hypview/hypview.go b/hypview/hypview.go
index 337b054..6881768 100644
--- a/hypview/hypview.go
+++ b/hypview/hypview.go
@@ -2,72 +2,19 @@ package hypview
import (
"embed"
- "github.com/bouncepaw/mycorrhiza/backlinks"
"html/template"
"log"
"strings"
- "github.com/bouncepaw/mycorrhiza/cfg"
- "github.com/bouncepaw/mycorrhiza/viewutil"
+ "github.com/bouncepaw/mycorrhiza/internal/backlinks"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
)
var (
//go:embed *.html
fs embed.FS
ruTranslation = `
-{{define "editing hypha"}}Редактирование {{beautifulName .}}{{end}}
-{{define "editing [[hypha]]"}}Редактирование {{beautifulName .}} {{end}}
-{{define "creating [[hypha]]"}}Создание {{beautifulName .}} {{end}}
-{{define "you're creating a new hypha"}}Вы создаёте новую гифу.{{end}}
-{{define "describe your changes"}}Опишите ваши правки{{end}}
-{{define "save"}}Сохранить{{end}}
-{{define "preview"}}Предпросмотр{{end}}
-{{define "previewing hypha"}}Предпросмотр «{{beautifulName .}}»{{end}}
-{{define "preview tip"}}Заметьте, эта гифа ещё не сохранена. Вот её предпросмотр:{{end}}
-
-{{define "markup"}}Разметка{{end}}
-{{define "link"}}Ссылка{{end}}
-{{define "link title"}}Текст{{end}}
-{{define "heading"}}Заголовок{{end}}
-{{define "bold"}}Жирный{{end}}
-{{define "italic"}}Курсив{{end}}
-{{define "highlight"}}Выделение{{end}}
-{{define "underline"}}Подчеркивание{{end}}
-{{define "mono"}}Моноширинный{{end}}
-{{define "super"}}Надстрочный{{end}}
-{{define "sub"}}Подстрочный{{end}}
-{{define "strike"}}Зачёркнутый{{end}}
-{{define "rocket"}}Ссылка-ракета{{end}}
-{{define "transclude"}}Трансклюзия{{end}}
-{{define "hr"}}Гориз. черта{{end}}
-{{define "code"}}Код-блок{{end}}
-{{define "bullets"}}Маркир. список{{end}}
-{{define "numbers"}}Нумер. список{{end}}
-{{define "mycomarkup help"}}Подробнее о Микоразметке{{end}}
-{{define "actions"}}Действия{{end}}
-{{define "current date utc"}}Дата UTC{{end}}
-{{define "current time utc"}}Время UTC{{end}}
-{{define "current date local"}}Местная дата{{end}}
-{{define "current time local"}}Местное время{{end}}
-{{define "selflink"}}Ссылка на вас{{end}}
-
-{{define "empty heading"}}Эта гифа не существует{{end}}
-{{define "empty no rights"}}У вас нет прав для создания новых гиф. Вы можете:{{end}}
-{{define "empty log in"}}Войти в свою учётную запись, если она у вас есть{{end}}
-{{define "empty register"}}Создать новую учётную запись{{end}}
-{{define "write a text"}}Написать текст{{end}}
-{{define "write a text tip"}}Напишите заметку, дневник, статью, рассказ или иной текст с помощью Микоразметки . Сохраняется полная история правок документа.{{end}}
-{{define "write a text writing conventions"}}Не забывайте следовать правилам оформления этой вики, если они имеются.{{end}}
-{{define "write a text btn"}}Создать{{end}}
-{{define "upload a media"}}Загрузить медиа{{end}}
-{{define "upload a media tip"}}Загрузите изображение, видео или аудио. Распространённые форматы можно просматривать из браузера, остальные можно только скачать и просмотреть локально. Позже вы можете дописать пояснение к этому медиа.{{end}}
-{{define "upload a media btn"}}Загрузить{{end}}
-
-{{define "delete hypha?"}}Удалить {{beautifulName .}}?{{end}}
-{{define "delete [[hypha]]?"}}Удалить {{beautifulName .}} ?{{end}}
-{{define "want to delete?"}}Вы действительно хотите удалить эту гифу?{{end}}
-{{define "delete tip"}}Нельзя отменить удаление гифы, но её история останется доступной.{{end}}
-
{{define "rename hypha?"}}Переименовать {{beautifulName .}}?{{end}}
{{define "rename [[hypha]]?"}}Переименовать {{beautifulName .}} ?{{end}}
{{define "new name"}}Новое название:{{end}}
@@ -75,48 +22,15 @@ var (
{{define "rename tip"}}Переименовывайте аккуратно. Документация на английском. {{end}}
{{define "leave redirection"}}Оставить перенаправление{{end}}
-{{define "remove media from x?"}}Убрать медиа у {{beautifulName .}}?{{end}}
-{{define "remove media from [[x]]?"}}Убрать медиа у {{beautifulName .MatchedHyphaName}} ?{{end}}
-{{define "remove media for real?"}}Вы точно хотите убрать медиа у гифы «{{beautifulName .MatchedHyphaName}}»?{{end}}
+
`
chainNaviTitle viewutil.Chain
- chainEditHypha viewutil.Chain
- chainEmptyHypha viewutil.Chain
- chainDeleteHypha viewutil.Chain
chainRenameHypha viewutil.Chain
- chainRemoveMedia viewutil.Chain
)
func Init() {
chainNaviTitle = viewutil.CopyEnRuWith(fs, "view_navititle.html", "")
- chainEditHypha = viewutil.CopyEnRuWith(fs, "view_edit.html", ruTranslation)
- chainEmptyHypha = viewutil.CopyEnRuWith(fs, "view_empty_hypha.html", ruTranslation)
- chainDeleteHypha = viewutil.CopyEnRuWith(fs, "view_delete.html", ruTranslation)
chainRenameHypha = viewutil.CopyEnRuWith(fs, "view_rename.html", ruTranslation)
- chainRemoveMedia = viewutil.CopyEnRuWith(fs, "view_remove_media.html", ruTranslation)
-}
-
-type editData struct {
- *viewutil.BaseData
- HyphaName string
- IsNew bool
- Content string
- Message string
- Preview template.HTML
-}
-
-func EditHypha(meta viewutil.Meta, hyphaName string, isNew bool, content string, message string, preview template.HTML) {
- viewutil.ExecutePage(meta, chainEditHypha, editData{
- BaseData: &viewutil.BaseData{
- Addr: "/edit/" + hyphaName,
- EditScripts: cfg.EditScripts,
- },
- HyphaName: hyphaName,
- IsNew: isNew,
- Content: content,
- Message: message,
- Preview: preview,
- })
}
type renameData struct {
@@ -135,49 +49,6 @@ func RenameHypha(meta viewutil.Meta, hyphaName string) {
})
}
-type deleteRemoveMediaData struct {
- *viewutil.BaseData
- HyphaName string
-}
-
-func DeleteHypha(meta viewutil.Meta, hyphaName string) {
- viewutil.ExecutePage(meta, chainDeleteHypha, deleteRemoveMediaData{
- BaseData: &viewutil.BaseData{
- Addr: "/delete/" + hyphaName,
- },
- HyphaName: hyphaName,
- })
-}
-
-func RemoveMedia(meta viewutil.Meta, hyphaName string) {
- viewutil.ExecutePage(meta, chainRemoveMedia, deleteRemoveMediaData{
- BaseData: &viewutil.BaseData{
- Addr: "/remove-media/" + hyphaName,
- },
- HyphaName: hyphaName,
- })
-}
-
-type emptyHyphaData struct {
- Meta viewutil.Meta
- HyphaName string
- AllowRegistration bool
- UseAuth bool
-}
-
-func EmptyHypha(meta viewutil.Meta, hyphaName string) string {
- var buf strings.Builder
- if err := chainEmptyHypha.Get(meta).ExecuteTemplate(&buf, "empty hypha card", emptyHyphaData{
- Meta: meta,
- HyphaName: hyphaName,
- AllowRegistration: cfg.AllowRegistration,
- UseAuth: cfg.UseAuth,
- }); err != nil {
- log.Println(err)
- }
- return buf.String()
-}
-
type naviTitleData struct {
HyphaNameParts []string
HyphaNamePartsWithParents []string
@@ -185,7 +56,7 @@ type naviTitleData struct {
HomeHypha string
}
-func NaviTitle(meta viewutil.Meta, hyphaName string) string {
+func NaviTitle(meta viewutil.Meta, hyphaName string) template.HTML {
parts, partsWithParents := naviTitleify(hyphaName)
var buf strings.Builder
err := chainNaviTitle.Get(meta).ExecuteTemplate(&buf, "navititle", naviTitleData{
@@ -197,7 +68,7 @@ func NaviTitle(meta viewutil.Meta, hyphaName string) string {
if err != nil {
log.Println(err)
}
- return buf.String()
+ return template.HTML(buf.String())
}
func naviTitleify(hyphaName string) ([]string, []string) {
diff --git a/hypview/nav.qtpl b/hypview/nav.qtpl
deleted file mode 100644
index 3f6a39d..0000000
--- a/hypview/nav.qtpl
+++ /dev/null
@@ -1,50 +0,0 @@
-{% import "github.com/bouncepaw/mycorrhiza/backlinks" %}
-{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
-{% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
-{% import "github.com/bouncepaw/mycorrhiza/user" %}
-{% import "github.com/bouncepaw/mycorrhiza/util" %}
-{% import "github.com/bouncepaw/mycorrhiza/viewutil" %}
-
-{% func hyphaInfoEntry(h hyphae.Hypha, u *user.User, action string, hasToExist bool, displayText string) %}
-{% code flag := true %}
-{% switch h.(type) %}
-{% case *hyphae.EmptyHypha %}
- {% code flag = !hasToExist %}
-{% endswitch %}
-{% if u.CanProceed(action) && flag %}
-
- {%s displayText %}
-
-{% endif %}
-{% endfunc %}
-
-{% func hyphaInfo(meta viewutil.Meta, h hyphae.Hypha) %}
-{% code
- u := meta.U
- lc := meta.Lc
- backs := backlinks.BacklinksCount(h.CanonicalName())
-%}
-
-
- {%= hyphaInfoEntry(h, u, "history", false, lc.Get("ui.history_link")) %}
- {%= hyphaInfoEntry(h, u, "rename", true, lc.Get("ui.rename_link")) %}
- {%= hyphaInfoEntry(h, u, "delete", true, lc.Get("ui.delete_link")) %}
- {%= hyphaInfoEntry(h, u, "text", true, lc.Get("ui.text_link")) %}
- {% switch h := h.(type) %}
- {% case *hyphae.TextualHypha %}
- {%= hyphaInfoEntry(h, u, "media", true, lc.Get("ui.media_link_for_textual")) %}
- {% default %}
- {%= hyphaInfoEntry(h, u, "media", true, lc.Get("ui.media_link")) %}
- {% endswitch %}
- {%= hyphaInfoEntry(h, u, "backlinks", false, lc.GetPlural("ui.backlinks_link", backs)) %}
-
-
-{% endfunc %}
-
-{% func commonScripts() %}
-{% for _, scriptPath := range cfg.CommonScripts %}
-
-{% endfor %}
-{% endfunc %}
-
-{% func beautifulLink(hyphaName string) %}{%s util.BeautifulName(hyphaName) %} {% endfunc %}
diff --git a/hypview/nav.qtpl.go b/hypview/nav.qtpl.go
deleted file mode 100644
index d808df9..0000000
--- a/hypview/nav.qtpl.go
+++ /dev/null
@@ -1,311 +0,0 @@
-// Code generated by qtc from "nav.qtpl". DO NOT EDIT.
-// See https://github.com/valyala/quicktemplate for details.
-
-//line hypview/nav.qtpl:1
-package hypview
-
-//line hypview/nav.qtpl:1
-import "github.com/bouncepaw/mycorrhiza/backlinks"
-
-//line hypview/nav.qtpl:2
-import "github.com/bouncepaw/mycorrhiza/cfg"
-
-//line hypview/nav.qtpl:3
-import "github.com/bouncepaw/mycorrhiza/hyphae"
-
-//line hypview/nav.qtpl:4
-import "github.com/bouncepaw/mycorrhiza/user"
-
-//line hypview/nav.qtpl:5
-import "github.com/bouncepaw/mycorrhiza/util"
-
-//line hypview/nav.qtpl:6
-import "github.com/bouncepaw/mycorrhiza/viewutil"
-
-//line hypview/nav.qtpl:8
-import (
- qtio422016 "io"
-
- qt422016 "github.com/valyala/quicktemplate"
-)
-
-//line hypview/nav.qtpl:8
-var (
- _ = qtio422016.Copy
- _ = qt422016.AcquireByteBuffer
-)
-
-//line hypview/nav.qtpl:8
-func streamhyphaInfoEntry(qw422016 *qt422016.Writer, h hyphae.Hypha, u *user.User, action string, hasToExist bool, displayText string) {
-//line hypview/nav.qtpl:8
- qw422016.N().S(`
-`)
-//line hypview/nav.qtpl:9
- flag := true
-
-//line hypview/nav.qtpl:9
- qw422016.N().S(`
-`)
-//line hypview/nav.qtpl:10
- switch h.(type) {
-//line hypview/nav.qtpl:11
- case *hyphae.EmptyHypha:
-//line hypview/nav.qtpl:11
- qw422016.N().S(`
- `)
-//line hypview/nav.qtpl:12
- flag = !hasToExist
-
-//line hypview/nav.qtpl:12
- qw422016.N().S(`
-`)
-//line hypview/nav.qtpl:13
- }
-//line hypview/nav.qtpl:13
- qw422016.N().S(`
-`)
-//line hypview/nav.qtpl:14
- if u.CanProceed(action) && flag {
-//line hypview/nav.qtpl:14
- qw422016.N().S(`
-
- `)
-//line hypview/nav.qtpl:16
- qw422016.E().S(displayText)
-//line hypview/nav.qtpl:16
- qw422016.N().S(`
-
-`)
-//line hypview/nav.qtpl:18
- }
-//line hypview/nav.qtpl:18
- qw422016.N().S(`
-`)
-//line hypview/nav.qtpl:19
-}
-
-//line hypview/nav.qtpl:19
-func writehyphaInfoEntry(qq422016 qtio422016.Writer, h hyphae.Hypha, u *user.User, action string, hasToExist bool, displayText string) {
-//line hypview/nav.qtpl:19
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line hypview/nav.qtpl:19
- streamhyphaInfoEntry(qw422016, h, u, action, hasToExist, displayText)
-//line hypview/nav.qtpl:19
- qt422016.ReleaseWriter(qw422016)
-//line hypview/nav.qtpl:19
-}
-
-//line hypview/nav.qtpl:19
-func hyphaInfoEntry(h hyphae.Hypha, u *user.User, action string, hasToExist bool, displayText string) string {
-//line hypview/nav.qtpl:19
- qb422016 := qt422016.AcquireByteBuffer()
-//line hypview/nav.qtpl:19
- writehyphaInfoEntry(qb422016, h, u, action, hasToExist, displayText)
-//line hypview/nav.qtpl:19
- qs422016 := string(qb422016.B)
-//line hypview/nav.qtpl:19
- qt422016.ReleaseByteBuffer(qb422016)
-//line hypview/nav.qtpl:19
- return qs422016
-//line hypview/nav.qtpl:19
-}
-
-//line hypview/nav.qtpl:21
-func streamhyphaInfo(qw422016 *qt422016.Writer, meta viewutil.Meta, h hyphae.Hypha) {
-//line hypview/nav.qtpl:21
- qw422016.N().S(`
-`)
-//line hypview/nav.qtpl:23
- u := meta.U
- lc := meta.Lc
- backs := backlinks.BacklinksCount(h.CanonicalName())
-
-//line hypview/nav.qtpl:26
- qw422016.N().S(`
-
-
- `)
-//line hypview/nav.qtpl:29
- streamhyphaInfoEntry(qw422016, h, u, "history", false, lc.Get("ui.history_link"))
-//line hypview/nav.qtpl:29
- qw422016.N().S(`
- `)
-//line hypview/nav.qtpl:30
- streamhyphaInfoEntry(qw422016, h, u, "rename", true, lc.Get("ui.rename_link"))
-//line hypview/nav.qtpl:30
- qw422016.N().S(`
- `)
-//line hypview/nav.qtpl:31
- streamhyphaInfoEntry(qw422016, h, u, "delete", true, lc.Get("ui.delete_link"))
-//line hypview/nav.qtpl:31
- qw422016.N().S(`
- `)
-//line hypview/nav.qtpl:32
- streamhyphaInfoEntry(qw422016, h, u, "text", true, lc.Get("ui.text_link"))
-//line hypview/nav.qtpl:32
- qw422016.N().S(`
- `)
-//line hypview/nav.qtpl:33
- switch h := h.(type) {
-//line hypview/nav.qtpl:34
- case *hyphae.TextualHypha:
-//line hypview/nav.qtpl:34
- qw422016.N().S(`
- `)
-//line hypview/nav.qtpl:35
- streamhyphaInfoEntry(qw422016, h, u, "media", true, lc.Get("ui.media_link_for_textual"))
-//line hypview/nav.qtpl:35
- qw422016.N().S(`
- `)
-//line hypview/nav.qtpl:36
- default:
-//line hypview/nav.qtpl:36
- qw422016.N().S(`
- `)
-//line hypview/nav.qtpl:37
- streamhyphaInfoEntry(qw422016, h, u, "media", true, lc.Get("ui.media_link"))
-//line hypview/nav.qtpl:37
- qw422016.N().S(`
- `)
-//line hypview/nav.qtpl:38
- }
-//line hypview/nav.qtpl:38
- qw422016.N().S(`
- `)
-//line hypview/nav.qtpl:39
- streamhyphaInfoEntry(qw422016, h, u, "backlinks", false, lc.GetPlural("ui.backlinks_link", backs))
-//line hypview/nav.qtpl:39
- qw422016.N().S(`
-
-
-`)
-//line hypview/nav.qtpl:42
-}
-
-//line hypview/nav.qtpl:42
-func writehyphaInfo(qq422016 qtio422016.Writer, meta viewutil.Meta, h hyphae.Hypha) {
-//line hypview/nav.qtpl:42
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line hypview/nav.qtpl:42
- streamhyphaInfo(qw422016, meta, h)
-//line hypview/nav.qtpl:42
- qt422016.ReleaseWriter(qw422016)
-//line hypview/nav.qtpl:42
-}
-
-//line hypview/nav.qtpl:42
-func hyphaInfo(meta viewutil.Meta, h hyphae.Hypha) string {
-//line hypview/nav.qtpl:42
- qb422016 := qt422016.AcquireByteBuffer()
-//line hypview/nav.qtpl:42
- writehyphaInfo(qb422016, meta, h)
-//line hypview/nav.qtpl:42
- qs422016 := string(qb422016.B)
-//line hypview/nav.qtpl:42
- qt422016.ReleaseByteBuffer(qb422016)
-//line hypview/nav.qtpl:42
- return qs422016
-//line hypview/nav.qtpl:42
-}
-
-//line hypview/nav.qtpl:44
-func streamcommonScripts(qw422016 *qt422016.Writer) {
-//line hypview/nav.qtpl:44
- qw422016.N().S(`
-`)
-//line hypview/nav.qtpl:45
- for _, scriptPath := range cfg.CommonScripts {
-//line hypview/nav.qtpl:45
- qw422016.N().S(`
-
-`)
-//line hypview/nav.qtpl:47
- }
-//line hypview/nav.qtpl:47
- qw422016.N().S(`
-`)
-//line hypview/nav.qtpl:48
-}
-
-//line hypview/nav.qtpl:48
-func writecommonScripts(qq422016 qtio422016.Writer) {
-//line hypview/nav.qtpl:48
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line hypview/nav.qtpl:48
- streamcommonScripts(qw422016)
-//line hypview/nav.qtpl:48
- qt422016.ReleaseWriter(qw422016)
-//line hypview/nav.qtpl:48
-}
-
-//line hypview/nav.qtpl:48
-func commonScripts() string {
-//line hypview/nav.qtpl:48
- qb422016 := qt422016.AcquireByteBuffer()
-//line hypview/nav.qtpl:48
- writecommonScripts(qb422016)
-//line hypview/nav.qtpl:48
- qs422016 := string(qb422016.B)
-//line hypview/nav.qtpl:48
- qt422016.ReleaseByteBuffer(qb422016)
-//line hypview/nav.qtpl:48
- return qs422016
-//line hypview/nav.qtpl:48
-}
-
-//line hypview/nav.qtpl:50
-func streambeautifulLink(qw422016 *qt422016.Writer, hyphaName string) {
-//line hypview/nav.qtpl:50
- qw422016.N().S(``)
-//line hypview/nav.qtpl:50
- qw422016.E().S(util.BeautifulName(hyphaName))
-//line hypview/nav.qtpl:50
- qw422016.N().S(` `)
-//line hypview/nav.qtpl:50
-}
-
-//line hypview/nav.qtpl:50
-func writebeautifulLink(qq422016 qtio422016.Writer, hyphaName string) {
-//line hypview/nav.qtpl:50
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line hypview/nav.qtpl:50
- streambeautifulLink(qw422016, hyphaName)
-//line hypview/nav.qtpl:50
- qt422016.ReleaseWriter(qw422016)
-//line hypview/nav.qtpl:50
-}
-
-//line hypview/nav.qtpl:50
-func beautifulLink(hyphaName string) string {
-//line hypview/nav.qtpl:50
- qb422016 := qt422016.AcquireByteBuffer()
-//line hypview/nav.qtpl:50
- writebeautifulLink(qb422016, hyphaName)
-//line hypview/nav.qtpl:50
- qs422016 := string(qb422016.B)
-//line hypview/nav.qtpl:50
- qt422016.ReleaseByteBuffer(qb422016)
-//line hypview/nav.qtpl:50
- return qs422016
-//line hypview/nav.qtpl:50
-}
diff --git a/hypview/readers.qtpl b/hypview/readers.qtpl
deleted file mode 100644
index 7662730..0000000
--- a/hypview/readers.qtpl
+++ /dev/null
@@ -1,161 +0,0 @@
-{% import "net/http" %}
-{% import "strings" %}
-{% import "path" %}
-{% import "os" %}
-
-{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
-{% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
-{% import "github.com/bouncepaw/mycorrhiza/categories" %}
-{% import "github.com/bouncepaw/mycorrhiza/l18n" %}
-{% import "github.com/bouncepaw/mycorrhiza/mimetype" %}
-{% import "github.com/bouncepaw/mycorrhiza/tree" %}
-{% import "github.com/bouncepaw/mycorrhiza/user" %}
-{% import "github.com/bouncepaw/mycorrhiza/util" %}
-{% import "github.com/bouncepaw/mycorrhiza/viewutil" %}
-
-{% func MediaMenu(rq *http.Request, h hyphae.Hypha, u *user.User) %}
-{% code
- lc := l18n.FromRequest(rq)
-%}
-
- {%s= lc.Get("ui.media_title", &l18n.Replacements{"name": beautifulLink(h.CanonicalName())}) %}
- {% switch h.(type) %}
- {% case *hyphae.MediaHypha %}
- {%s lc.Get("ui.media_tip") %} {%s lc.Get("ui.media_what_is") %}
- {% default %}
- {%s lc.Get("ui.media_empty") %} {%s lc.Get("ui.media_what_is") %}
- {% endswitch %}
-
-
- {% switch h := h.(type) %}
- {% case *hyphae.MediaHypha %}
- {% code
- mime := mimetype.FromExtension(path.Ext(h.MediaFilePath()))
- fileinfo, err := os.Stat(h.MediaFilePath()) %}
- {% if err == nil %}
-
- {% endif %}
-
- {% if strings.HasPrefix(mime, "image/") %}
-
- {% endif %}
- {% endswitch %}
-
- {% if u.CanProceed("upload-binary") %}
-
- {% endif %}
-
-
- {% switch h := h.(type) %}
- {% case *hyphae.MediaHypha %}
- {% if u.CanProceed("remove-media") %}
-
- {% endif %}
- {% endswitch %}
-
-
-
-{% endfunc %}
-
-If `contents` == "", a helpful message is shown instead.
-
-If you rename .prevnext, change the docs too.
-{% func Hypha(meta viewutil.Meta, h hyphae.Hypha, contents string) %}
-{% code
- subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.CanonicalName())
- lc := meta.Lc
-%}
-
-
- {% if meta.U.CanProceed("edit") %}
-
- {% endif %}
-
- {% if cfg.UseAuth && util.IsProfileName(h.CanonicalName()) && meta.U.Name == strings.TrimPrefix(h.CanonicalName(), cfg.UserHypha + "/") %}
-
- {% if meta.U.Group == "admin" %}
-
- {% endif %}
- {% endif %}
-
- {%s= NaviTitle(meta, h.CanonicalName()) %}
- {% switch h.(type) %}
- {% case *hyphae.EmptyHypha %}
- {%s= EmptyHypha(meta, h.CanonicalName()) %}
- {% default %}
- {%s= contents %}
- {% endswitch %}
-
-
-{% if strings.TrimSpace(subhyphae) != "" %}
-
- {%s lc.Get("ui.subhyphae") %}
-
-
-
-
-{% endif %}
-
- {%= hyphaInfo(meta, h) %}
-
-
-{%s= categories.CategoryCard(meta, h.CanonicalName()) %}
-{%= viewScripts() %}
-{% endfunc %}
-
-{% func Revision(meta viewutil.Meta, h hyphae.Hypha, contents, revHash string) %}
-
-
-
-{%= viewScripts() %}
-{% endfunc %}
-
-{% func viewScripts() %}
-{% for _, scriptPath := range cfg.ViewScripts %}
-
-{% endfor %}
-{% endfunc %}
diff --git a/hypview/readers.qtpl.go b/hypview/readers.qtpl.go
deleted file mode 100644
index 5c12150..0000000
--- a/hypview/readers.qtpl.go
+++ /dev/null
@@ -1,651 +0,0 @@
-// Code generated by qtc from "readers.qtpl". DO NOT EDIT.
-// See https://github.com/valyala/quicktemplate for details.
-
-//line hypview/readers.qtpl:1
-package hypview
-
-//line hypview/readers.qtpl:1
-import "net/http"
-
-//line hypview/readers.qtpl:2
-import "strings"
-
-//line hypview/readers.qtpl:3
-import "path"
-
-//line hypview/readers.qtpl:4
-import "os"
-
-//line hypview/readers.qtpl:6
-import "github.com/bouncepaw/mycorrhiza/cfg"
-
-//line hypview/readers.qtpl:7
-import "github.com/bouncepaw/mycorrhiza/hyphae"
-
-//line hypview/readers.qtpl:8
-import "github.com/bouncepaw/mycorrhiza/categories"
-
-//line hypview/readers.qtpl:9
-import "github.com/bouncepaw/mycorrhiza/l18n"
-
-//line hypview/readers.qtpl:10
-import "github.com/bouncepaw/mycorrhiza/mimetype"
-
-//line hypview/readers.qtpl:11
-import "github.com/bouncepaw/mycorrhiza/tree"
-
-//line hypview/readers.qtpl:12
-import "github.com/bouncepaw/mycorrhiza/user"
-
-//line hypview/readers.qtpl:13
-import "github.com/bouncepaw/mycorrhiza/util"
-
-//line hypview/readers.qtpl:14
-import "github.com/bouncepaw/mycorrhiza/viewutil"
-
-//line hypview/readers.qtpl:16
-import (
- qtio422016 "io"
-
- qt422016 "github.com/valyala/quicktemplate"
-)
-
-//line hypview/readers.qtpl:16
-var (
- _ = qtio422016.Copy
- _ = qt422016.AcquireByteBuffer
-)
-
-//line hypview/readers.qtpl:16
-func StreamMediaMenu(qw422016 *qt422016.Writer, rq *http.Request, h hyphae.Hypha, u *user.User) {
-//line hypview/readers.qtpl:16
- qw422016.N().S(`
-`)
-//line hypview/readers.qtpl:18
- lc := l18n.FromRequest(rq)
-
-//line hypview/readers.qtpl:19
- qw422016.N().S(`
-
- `)
-//line hypview/readers.qtpl:21
- qw422016.N().S(lc.Get("ui.media_title", &l18n.Replacements{"name": beautifulLink(h.CanonicalName())}))
-//line hypview/readers.qtpl:21
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:22
- switch h.(type) {
-//line hypview/readers.qtpl:23
- case *hyphae.MediaHypha:
-//line hypview/readers.qtpl:23
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:24
- qw422016.E().S(lc.Get("ui.media_tip"))
-//line hypview/readers.qtpl:24
- qw422016.N().S(` `)
-//line hypview/readers.qtpl:24
- qw422016.E().S(lc.Get("ui.media_what_is"))
-//line hypview/readers.qtpl:24
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:25
- default:
-//line hypview/readers.qtpl:25
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:26
- qw422016.E().S(lc.Get("ui.media_empty"))
-//line hypview/readers.qtpl:26
- qw422016.N().S(` `)
-//line hypview/readers.qtpl:26
- qw422016.E().S(lc.Get("ui.media_what_is"))
-//line hypview/readers.qtpl:26
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:27
- }
-//line hypview/readers.qtpl:27
- qw422016.N().S(`
-
-
- `)
-//line hypview/readers.qtpl:30
- switch h := h.(type) {
-//line hypview/readers.qtpl:31
- case *hyphae.MediaHypha:
-//line hypview/readers.qtpl:31
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:33
- mime := mimetype.FromExtension(path.Ext(h.MediaFilePath()))
- fileinfo, err := os.Stat(h.MediaFilePath())
-
-//line hypview/readers.qtpl:34
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:35
- if err == nil {
-//line hypview/readers.qtpl:35
- qw422016.N().S(`
-
- `)
-//line hypview/readers.qtpl:41
- }
-//line hypview/readers.qtpl:41
- qw422016.N().S(`
-
- `)
-//line hypview/readers.qtpl:43
- if strings.HasPrefix(mime, "image/") {
-//line hypview/readers.qtpl:43
- qw422016.N().S(`
-
- `)
-//line hypview/readers.qtpl:49
- }
-//line hypview/readers.qtpl:49
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:50
- }
-//line hypview/readers.qtpl:50
- qw422016.N().S(`
-
- `)
-//line hypview/readers.qtpl:52
- if u.CanProceed("upload-binary") {
-//line hypview/readers.qtpl:52
- qw422016.N().S(`
-
- `)
-//line hypview/readers.qtpl:65
- }
-//line hypview/readers.qtpl:65
- qw422016.N().S(`
-
-
- `)
-//line hypview/readers.qtpl:68
- switch h := h.(type) {
-//line hypview/readers.qtpl:69
- case *hyphae.MediaHypha:
-//line hypview/readers.qtpl:69
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:70
- if u.CanProceed("remove-media") {
-//line hypview/readers.qtpl:70
- qw422016.N().S(`
-
- `)
-//line hypview/readers.qtpl:78
- }
-//line hypview/readers.qtpl:78
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:79
- }
-//line hypview/readers.qtpl:79
- qw422016.N().S(`
-
-
-
-`)
-//line hypview/readers.qtpl:83
-}
-
-//line hypview/readers.qtpl:83
-func WriteMediaMenu(qq422016 qtio422016.Writer, rq *http.Request, h hyphae.Hypha, u *user.User) {
-//line hypview/readers.qtpl:83
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line hypview/readers.qtpl:83
- StreamMediaMenu(qw422016, rq, h, u)
-//line hypview/readers.qtpl:83
- qt422016.ReleaseWriter(qw422016)
-//line hypview/readers.qtpl:83
-}
-
-//line hypview/readers.qtpl:83
-func MediaMenu(rq *http.Request, h hyphae.Hypha, u *user.User) string {
-//line hypview/readers.qtpl:83
- qb422016 := qt422016.AcquireByteBuffer()
-//line hypview/readers.qtpl:83
- WriteMediaMenu(qb422016, rq, h, u)
-//line hypview/readers.qtpl:83
- qs422016 := string(qb422016.B)
-//line hypview/readers.qtpl:83
- qt422016.ReleaseByteBuffer(qb422016)
-//line hypview/readers.qtpl:83
- return qs422016
-//line hypview/readers.qtpl:83
-}
-
-// If `contents` == "", a helpful message is shown instead.
-//
-// If you rename .prevnext, change the docs too.
-
-//line hypview/readers.qtpl:88
-func StreamHypha(qw422016 *qt422016.Writer, meta viewutil.Meta, h hyphae.Hypha, contents string) {
-//line hypview/readers.qtpl:88
- qw422016.N().S(`
-`)
-//line hypview/readers.qtpl:90
- subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.CanonicalName())
- lc := meta.Lc
-
-//line hypview/readers.qtpl:92
- qw422016.N().S(`
-
-
- `)
-//line hypview/readers.qtpl:95
- if meta.U.CanProceed("edit") {
-//line hypview/readers.qtpl:95
- qw422016.N().S(`
-
- `)
-//line hypview/readers.qtpl:99
- }
-//line hypview/readers.qtpl:99
- qw422016.N().S(`
-
- `)
-//line hypview/readers.qtpl:101
- if cfg.UseAuth && util.IsProfileName(h.CanonicalName()) && meta.U.Name == strings.TrimPrefix(h.CanonicalName(), cfg.UserHypha+"/") {
-//line hypview/readers.qtpl:101
- qw422016.N().S(`
-
- `)
-//line hypview/readers.qtpl:105
- if meta.U.Group == "admin" {
-//line hypview/readers.qtpl:105
- qw422016.N().S(`
-
- `)
-//line hypview/readers.qtpl:109
- }
-//line hypview/readers.qtpl:109
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:110
- }
-//line hypview/readers.qtpl:110
- qw422016.N().S(`
-
- `)
-//line hypview/readers.qtpl:112
- qw422016.N().S(NaviTitle(meta, h.CanonicalName()))
-//line hypview/readers.qtpl:112
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:113
- switch h.(type) {
-//line hypview/readers.qtpl:114
- case *hyphae.EmptyHypha:
-//line hypview/readers.qtpl:114
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:115
- qw422016.N().S(EmptyHypha(meta, h.CanonicalName()))
-//line hypview/readers.qtpl:115
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:116
- default:
-//line hypview/readers.qtpl:116
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:117
- qw422016.N().S(contents)
-//line hypview/readers.qtpl:117
- qw422016.N().S(`
- `)
-//line hypview/readers.qtpl:118
- }
-//line hypview/readers.qtpl:118
- qw422016.N().S(`
-
-
-`)
-//line hypview/readers.qtpl:128
- if strings.TrimSpace(subhyphae) != "" {
-//line hypview/readers.qtpl:128
- qw422016.N().S(`
-
- `)
-//line hypview/readers.qtpl:130
- qw422016.E().S(lc.Get("ui.subhyphae"))
-//line hypview/readers.qtpl:130
- qw422016.N().S(`
-
-
- `)
-//line hypview/readers.qtpl:133
- qw422016.N().S(subhyphae)
-//line hypview/readers.qtpl:133
- qw422016.N().S(`
-
-
-
-`)
-//line hypview/readers.qtpl:137
- }
-//line hypview/readers.qtpl:137
- qw422016.N().S(`
-
- `)
-//line hypview/readers.qtpl:139
- streamhyphaInfo(qw422016, meta, h)
-//line hypview/readers.qtpl:139
- qw422016.N().S(`
-
-
-`)
-//line hypview/readers.qtpl:142
- qw422016.N().S(categories.CategoryCard(meta, h.CanonicalName()))
-//line hypview/readers.qtpl:142
- qw422016.N().S(`
-`)
-//line hypview/readers.qtpl:143
- streamviewScripts(qw422016)
-//line hypview/readers.qtpl:143
- qw422016.N().S(`
-`)
-//line hypview/readers.qtpl:144
-}
-
-//line hypview/readers.qtpl:144
-func WriteHypha(qq422016 qtio422016.Writer, meta viewutil.Meta, h hyphae.Hypha, contents string) {
-//line hypview/readers.qtpl:144
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line hypview/readers.qtpl:144
- StreamHypha(qw422016, meta, h, contents)
-//line hypview/readers.qtpl:144
- qt422016.ReleaseWriter(qw422016)
-//line hypview/readers.qtpl:144
-}
-
-//line hypview/readers.qtpl:144
-func Hypha(meta viewutil.Meta, h hyphae.Hypha, contents string) string {
-//line hypview/readers.qtpl:144
- qb422016 := qt422016.AcquireByteBuffer()
-//line hypview/readers.qtpl:144
- WriteHypha(qb422016, meta, h, contents)
-//line hypview/readers.qtpl:144
- qs422016 := string(qb422016.B)
-//line hypview/readers.qtpl:144
- qt422016.ReleaseByteBuffer(qb422016)
-//line hypview/readers.qtpl:144
- return qs422016
-//line hypview/readers.qtpl:144
-}
-
-//line hypview/readers.qtpl:146
-func StreamRevision(qw422016 *qt422016.Writer, meta viewutil.Meta, h hyphae.Hypha, contents, revHash string) {
-//line hypview/readers.qtpl:146
- qw422016.N().S(`
-
-
-
-`)
-//line hypview/readers.qtpl:154
- streamviewScripts(qw422016)
-//line hypview/readers.qtpl:154
- qw422016.N().S(`
-`)
-//line hypview/readers.qtpl:155
-}
-
-//line hypview/readers.qtpl:155
-func WriteRevision(qq422016 qtio422016.Writer, meta viewutil.Meta, h hyphae.Hypha, contents, revHash string) {
-//line hypview/readers.qtpl:155
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line hypview/readers.qtpl:155
- StreamRevision(qw422016, meta, h, contents, revHash)
-//line hypview/readers.qtpl:155
- qt422016.ReleaseWriter(qw422016)
-//line hypview/readers.qtpl:155
-}
-
-//line hypview/readers.qtpl:155
-func Revision(meta viewutil.Meta, h hyphae.Hypha, contents, revHash string) string {
-//line hypview/readers.qtpl:155
- qb422016 := qt422016.AcquireByteBuffer()
-//line hypview/readers.qtpl:155
- WriteRevision(qb422016, meta, h, contents, revHash)
-//line hypview/readers.qtpl:155
- qs422016 := string(qb422016.B)
-//line hypview/readers.qtpl:155
- qt422016.ReleaseByteBuffer(qb422016)
-//line hypview/readers.qtpl:155
- return qs422016
-//line hypview/readers.qtpl:155
-}
-
-//line hypview/readers.qtpl:157
-func streamviewScripts(qw422016 *qt422016.Writer) {
-//line hypview/readers.qtpl:157
- qw422016.N().S(`
-`)
-//line hypview/readers.qtpl:158
- for _, scriptPath := range cfg.ViewScripts {
-//line hypview/readers.qtpl:158
- qw422016.N().S(`
-
-`)
-//line hypview/readers.qtpl:160
- }
-//line hypview/readers.qtpl:160
- qw422016.N().S(`
-`)
-//line hypview/readers.qtpl:161
-}
-
-//line hypview/readers.qtpl:161
-func writeviewScripts(qq422016 qtio422016.Writer) {
-//line hypview/readers.qtpl:161
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line hypview/readers.qtpl:161
- streamviewScripts(qw422016)
-//line hypview/readers.qtpl:161
- qt422016.ReleaseWriter(qw422016)
-//line hypview/readers.qtpl:161
-}
-
-//line hypview/readers.qtpl:161
-func viewScripts() string {
-//line hypview/readers.qtpl:161
- qb422016 := qt422016.AcquireByteBuffer()
-//line hypview/readers.qtpl:161
- writeviewScripts(qb422016)
-//line hypview/readers.qtpl:161
- qs422016 := string(qb422016.B)
-//line hypview/readers.qtpl:161
- qt422016.ReleaseByteBuffer(qb422016)
-//line hypview/readers.qtpl:161
- return qs422016
-//line hypview/readers.qtpl:161
-}
diff --git a/hypview/view_empty_hypha.html b/hypview/view_empty_hypha.html
deleted file mode 100644
index 2af3638..0000000
--- a/hypview/view_empty_hypha.html
+++ /dev/null
@@ -1,32 +0,0 @@
-{{define "empty hypha card"}}
-
- {{block "empty heading" .}}This hypha does not exist{{end}}
- {{if and .UseAuth (eq .Meta.U.Group "anon")}}
- {{block "empty no rights" .}}You are not authorized to create new hyphae. Here is what you can do:{{end}}
-
- {{else}}
-
-
- 📝 {{block "write a text" .}}Write a text{{end}}
- {{block "write a text tip" .}}Write a note, a diary, an article, a story or anything textual using Mycomarkup . Full history of edits to the document will be saved.{{end}}
- {{block "write a text writing conventions" .}}Make sure to follow this wiki's writing conventions if there are any.{{end}}
- {{block "write a text btn" .}}Create{{end}}
-
-
-
- 🖼 {{block "upload a media" .}}Upload a media{{end}}
- {{block "upload a media tip" .}}Upload a picture, a video or an audio. Most common formats can be viewed from the browser, others can only be downloaded and viewed locally. You can write a description for the media later.{{end}}
-
-
-
- {{end}}
-
-{{end}}
\ No newline at end of file
diff --git a/hypview/view_remove_media.html b/hypview/view_remove_media.html
deleted file mode 100644
index 3edd8d8..0000000
--- a/hypview/view_remove_media.html
+++ /dev/null
@@ -1,22 +0,0 @@
-{{define "remove media from x?"}}Remove media from {{beautifulName .}}?{{end}}
-{{define "title"}}{{template "remove media from x?" .HyphaName}}{{end}}
-{{define "body"}}
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/backlinks/backlinks.go b/internal/backlinks/backlinks.go
similarity index 84%
rename from backlinks/backlinks.go
rename to internal/backlinks/backlinks.go
index 3660580..f2e235a 100644
--- a/backlinks/backlinks.go
+++ b/internal/backlinks/backlinks.go
@@ -2,10 +2,11 @@
package backlinks
import (
+ hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"log"
"os"
+ "sort"
- "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/util"
)
@@ -13,7 +14,7 @@ import (
func yieldHyphaBacklinks(hyphaName string) <-chan string {
hyphaName = util.CanonicalName(hyphaName)
out := make(chan string)
- sorted := hyphae.PathographicSort(out)
+ sorted := hyphae2.PathographicSort(out)
go func() {
backlinks, exists := backlinkIndex[hyphaName]
if exists {
@@ -42,7 +43,7 @@ var backlinkIndex = make(map[string]linkSet)
// IndexBacklinks traverses all text hyphae, extracts links from them and forms an initial index. Call it when indexing and reindexing hyphae.
func IndexBacklinks() {
// It is safe to ignore the mutex, because there is only one worker.
- for h := range hyphae.FilterHyphaeWithText(hyphae.YieldExistingHyphae()) {
+ for h := range hyphae2.FilterHyphaeWithText(hyphae2.YieldExistingHyphae()) {
foundLinks := extractHyphaLinksFromContent(h.CanonicalName(), fetchText(h))
for _, link := range foundLinks {
if _, exists := backlinkIndex[link]; !exists {
@@ -61,6 +62,25 @@ func BacklinksCount(hyphaName string) int {
return 0
}
+func BacklinksFor(hyphaName string) []string {
+ var backlinks []string
+ for b := range yieldHyphaBacklinks(hyphaName) {
+ backlinks = append(backlinks, b)
+ }
+ return backlinks
+}
+
+func Orphans() []string {
+ var orphans []string
+ for h := range hyphae2.YieldExistingHyphae() {
+ if BacklinksCount(h.CanonicalName()) == 0 {
+ orphans = append(orphans, h.CanonicalName())
+ }
+ }
+ sort.Strings(orphans)
+ return orphans
+}
+
// Using set here seems like the most appropriate solution
type linkSet map[string]struct{}
@@ -72,14 +92,14 @@ func toLinkSet(xs []string) linkSet {
return result
}
-func fetchText(h hyphae.Hypha) string {
+func fetchText(h hyphae2.Hypha) string {
var path string
switch h := h.(type) {
- case *hyphae.EmptyHypha:
+ case *hyphae2.EmptyHypha:
return ""
- case *hyphae.TextualHypha:
+ case *hyphae2.TextualHypha:
path = h.TextFilePath()
- case *hyphae.MediaHypha:
+ case *hyphae2.MediaHypha:
if !h.HasTextFile() {
return ""
}
diff --git a/backlinks/hooks.go b/internal/backlinks/hooks.go
similarity index 97%
rename from backlinks/hooks.go
rename to internal/backlinks/hooks.go
index 41084f6..ef65952 100644
--- a/backlinks/hooks.go
+++ b/internal/backlinks/hooks.go
@@ -5,7 +5,7 @@ import (
"git.sr.ht/~bouncepaw/mycomarkup/v5/links"
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
- "github.com/bouncepaw/mycorrhiza/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/mycoopts"
)
diff --git a/categories/categories.go b/internal/categories/categories.go
similarity index 91%
rename from categories/categories.go
rename to internal/categories/categories.go
index 75d3482..86faa01 100644
--- a/categories/categories.go
+++ b/internal/categories/categories.go
@@ -23,8 +23,8 @@ package categories
import "sync"
-// listOfCategories returns unsorted names of all categories.
-func listOfCategories() (categoryList []string) {
+// ListOfCategories returns unsorted names of all categories.
+func ListOfCategories() (categoryList []string) {
mutex.RLock()
for cat, _ := range categoryToHyphae {
categoryList = append(categoryList, cat)
@@ -44,8 +44,8 @@ func CategoriesWithHypha(hyphaName string) (categoryList []string) {
}
}
-// hyphaeInCategory returns what hyphae are in the category. If the returned slice is empty, the category does not exist, and vice versa. The category name must be canonical.
-func hyphaeInCategory(catName string) (hyphaList []string) {
+// HyphaeInCategory returns what hyphae are in the category. If the returned slice is empty, the category does not exist, and vice versa. The category name must be canonical.
+func HyphaeInCategory(catName string) (hyphaList []string) {
mutex.RLock()
defer mutex.RUnlock()
if node, ok := categoryToHyphae[catName]; ok {
@@ -75,8 +75,8 @@ func AddHyphaToCategory(hyphaName, catName string) {
go saveToDisk()
}
-// removeHyphaFromCategory removes the hypha from the category and updates the records on the disk. If the hypha is not in the category, nothing happens. Pass canonical names.
-func removeHyphaFromCategory(hyphaName, catName string) {
+// RemoveHyphaFromCategory removes the hypha from the category and updates the records on the disk. If the hypha is not in the category, nothing happens. Pass canonical names.
+func RemoveHyphaFromCategory(hyphaName, catName string) {
mutex.Lock()
if node, ok := hyphaToCategories[hyphaName]; ok {
node.removeCategory(catName)
diff --git a/categories/files.go b/internal/categories/files.go
similarity index 98%
rename from categories/files.go
rename to internal/categories/files.go
index 9bac8ac..237a5fd 100644
--- a/categories/files.go
+++ b/internal/categories/files.go
@@ -2,13 +2,14 @@ package categories
import (
"encoding/json"
- "github.com/bouncepaw/mycorrhiza/files"
- "github.com/bouncepaw/mycorrhiza/util"
"golang.org/x/exp/slices"
"log"
"os"
"sort"
"sync"
+
+ "github.com/bouncepaw/mycorrhiza/internal/files"
+ "github.com/bouncepaw/mycorrhiza/util"
)
var categoryToHyphae = map[string]*categoryNode{}
diff --git a/cfg/config.go b/internal/cfg/config.go
similarity index 100%
rename from cfg/config.go
rename to internal/cfg/config.go
diff --git a/files/files.go b/internal/files/files.go
similarity index 97%
rename from files/files.go
rename to internal/files/files.go
index b549e83..bb82cff 100644
--- a/files/files.go
+++ b/internal/files/files.go
@@ -2,12 +2,11 @@
package files
import (
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ "github.com/bouncepaw/mycorrhiza/web/static"
"io"
"os"
"path/filepath"
-
- "github.com/bouncepaw/mycorrhiza/cfg"
- "github.com/bouncepaw/mycorrhiza/static"
)
var paths struct {
diff --git a/hyphae/count.go b/internal/hyphae/count.go
similarity index 100%
rename from hyphae/count.go
rename to internal/hyphae/count.go
diff --git a/hyphae/deprecated.go b/internal/hyphae/deprecated.go
similarity index 100%
rename from hyphae/deprecated.go
rename to internal/hyphae/deprecated.go
diff --git a/hyphae/empty_hypha.go b/internal/hyphae/empty_hypha.go
similarity index 100%
rename from hyphae/empty_hypha.go
rename to internal/hyphae/empty_hypha.go
diff --git a/hyphae/existing_hypha.go b/internal/hyphae/existing_hypha.go
similarity index 100%
rename from hyphae/existing_hypha.go
rename to internal/hyphae/existing_hypha.go
diff --git a/hyphae/files.go b/internal/hyphae/files.go
similarity index 97%
rename from hyphae/files.go
rename to internal/hyphae/files.go
index a0ee9cc..9be35a2 100644
--- a/hyphae/files.go
+++ b/internal/hyphae/files.go
@@ -1,12 +1,11 @@
package hyphae
import (
+ "github.com/bouncepaw/mycorrhiza/internal/mimetype"
"log"
"log/slog"
"os"
"path/filepath"
-
- "github.com/bouncepaw/mycorrhiza/mimetype"
)
// Index finds all hypha files in the full `path` and saves them to the hypha storage.
diff --git a/hyphae/hypha.go b/internal/hyphae/hypha.go
similarity index 100%
rename from hyphae/hypha.go
rename to internal/hyphae/hypha.go
diff --git a/hyphae/iterators.go b/internal/hyphae/iterators.go
similarity index 100%
rename from hyphae/iterators.go
rename to internal/hyphae/iterators.go
diff --git a/hyphae/media_hypha.go b/internal/hyphae/media_hypha.go
similarity index 93%
rename from hyphae/media_hypha.go
rename to internal/hyphae/media_hypha.go
index 60e3505..581d271 100644
--- a/hyphae/media_hypha.go
+++ b/internal/hyphae/media_hypha.go
@@ -1,9 +1,10 @@
package hyphae
import (
- "github.com/bouncepaw/mycorrhiza/files"
"path/filepath"
"sync"
+
+ "github.com/bouncepaw/mycorrhiza/internal/files"
)
type MediaHypha struct {
diff --git a/hyphae/textual_hypha.go b/internal/hyphae/textual_hypha.go
similarity index 100%
rename from hyphae/textual_hypha.go
rename to internal/hyphae/textual_hypha.go
diff --git a/migration/headings.go b/internal/migration/headings.go
similarity index 95%
rename from migration/headings.go
rename to internal/migration/headings.go
index 14fb2aa..fb8f3cd 100644
--- a/migration/headings.go
+++ b/internal/migration/headings.go
@@ -2,7 +2,7 @@ package migration
import (
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
- "github.com/bouncepaw/mycorrhiza/files"
+ "github.com/bouncepaw/mycorrhiza/internal/files"
"io/ioutil"
"log"
"os"
diff --git a/migration/migration.go b/internal/migration/migration.go
similarity index 95%
rename from migration/migration.go
rename to internal/migration/migration.go
index 00bf1ff..d3f945f 100644
--- a/migration/migration.go
+++ b/internal/migration/migration.go
@@ -8,13 +8,14 @@
package migration
import (
- "github.com/bouncepaw/mycorrhiza/history"
- "github.com/bouncepaw/mycorrhiza/hyphae"
- "github.com/bouncepaw/mycorrhiza/user"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
"io"
"log"
"os"
"strings"
+
+ "github.com/bouncepaw/mycorrhiza/history"
+ "github.com/bouncepaw/mycorrhiza/internal/hyphae"
)
func genericLineMigrator(
diff --git a/migration/rockets.go b/internal/migration/rockets.go
similarity index 96%
rename from migration/rockets.go
rename to internal/migration/rockets.go
index 9787eba..5dc050b 100644
--- a/migration/rockets.go
+++ b/internal/migration/rockets.go
@@ -2,7 +2,7 @@ package migration
import (
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
- "github.com/bouncepaw/mycorrhiza/files"
+ "github.com/bouncepaw/mycorrhiza/internal/files"
"io/ioutil"
"log"
"os"
diff --git a/mimetype/mime.go b/internal/mimetype/mime.go
similarity index 100%
rename from mimetype/mime.go
rename to internal/mimetype/mime.go
diff --git a/shroom/can.go b/internal/shroom/can.go
similarity index 73%
rename from shroom/can.go
rename to internal/shroom/can.go
index b3885e7..c9d1f3d 100644
--- a/shroom/can.go
+++ b/internal/shroom/can.go
@@ -2,23 +2,23 @@ package shroom
import (
"errors"
+ hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
- "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/l18n"
- "github.com/bouncepaw/mycorrhiza/user"
)
// TODO: get rid of this abomination
func canFactory(
- rejectLogger func(hyphae.Hypha, *user.User, string),
+ rejectLogger func(hyphae2.Hypha, *user.User, string),
action string,
- dispatcher func(hyphae.Hypha, *user.User, *l18n.Localizer) (string, string),
+ dispatcher func(hyphae2.Hypha, *user.User, *l18n.Localizer) (string, string),
noRightsMsg string,
notExistsMsg string,
mustExist bool,
-) func(*user.User, hyphae.Hypha, *l18n.Localizer) error {
- return func(u *user.User, h hyphae.Hypha, lc *l18n.Localizer) error {
+) func(*user.User, hyphae2.Hypha, *l18n.Localizer) error {
+ return func(u *user.User, h hyphae2.Hypha, lc *l18n.Localizer) error {
if !u.CanProceed(action) {
rejectLogger(h, u, "no rights")
return errors.New(noRightsMsg)
@@ -26,7 +26,7 @@ func canFactory(
if mustExist {
switch h.(type) {
- case *hyphae.EmptyHypha:
+ case *hyphae2.EmptyHypha:
rejectLogger(h, u, "does not exist")
return errors.New(notExistsMsg)
}
diff --git a/shroom/delete.go b/internal/shroom/delete.go
similarity index 62%
rename from shroom/delete.go
rename to internal/shroom/delete.go
index d63703f..701fd0c 100644
--- a/shroom/delete.go
+++ b/internal/shroom/delete.go
@@ -2,29 +2,29 @@ package shroom
import (
"fmt"
- "github.com/bouncepaw/mycorrhiza/backlinks"
- "github.com/bouncepaw/mycorrhiza/categories"
"github.com/bouncepaw/mycorrhiza/history"
- "github.com/bouncepaw/mycorrhiza/hyphae"
- "github.com/bouncepaw/mycorrhiza/user"
+ "github.com/bouncepaw/mycorrhiza/internal/backlinks"
+ "github.com/bouncepaw/mycorrhiza/internal/categories"
+ hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
)
// Delete deletes the hypha and makes a history record about that.
-func Delete(u *user.User, h hyphae.ExistingHypha) error {
+func Delete(u *user.User, h hyphae2.ExistingHypha) error {
hop := history.
Operation(history.TypeDeleteHypha).
WithMsg(fmt.Sprintf("Delete ‘%s’", h.CanonicalName())).
WithUser(u)
- originalText, _ := hyphae.FetchMycomarkupFile(h)
+ originalText, _ := hyphae2.FetchMycomarkupFile(h)
switch h := h.(type) {
- case *hyphae.MediaHypha:
+ case *hyphae2.MediaHypha:
if h.HasTextFile() {
hop.WithFilesRemoved(h.MediaFilePath(), h.TextFilePath())
} else {
hop.WithFilesRemoved(h.MediaFilePath())
}
- case *hyphae.TextualHypha:
+ case *hyphae2.TextualHypha:
hop.WithFilesRemoved(h.TextFilePath())
}
if hop.Apply().HasErrors() {
@@ -32,6 +32,6 @@ func Delete(u *user.User, h hyphae.ExistingHypha) error {
}
backlinks.UpdateBacklinksAfterDelete(h, originalText)
categories.RemoveHyphaFromAllCategories(h.CanonicalName())
- hyphae.DeleteHypha(h)
+ hyphae2.DeleteHypha(h)
return nil
}
diff --git a/shroom/header_links.go b/internal/shroom/header_links.go
similarity index 84%
rename from shroom/header_links.go
rename to internal/shroom/header_links.go
index 0d2be2d..dd3ff1b 100644
--- a/shroom/header_links.go
+++ b/internal/shroom/header_links.go
@@ -4,19 +4,19 @@ import (
"git.sr.ht/~bouncepaw/mycomarkup/v5"
"git.sr.ht/~bouncepaw/mycomarkup/v5/blocks"
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
- "github.com/bouncepaw/mycorrhiza/cfg"
- "github.com/bouncepaw/mycorrhiza/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/mycoopts"
- "github.com/bouncepaw/mycorrhiza/viewutil"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
"os"
)
// SetHeaderLinks initializes header links by reading the configured hypha, if there is any, or resorting to default values.
func SetHeaderLinks() {
- switch userLinksHypha := hyphae.ByName(cfg.HeaderLinksHypha).(type) {
- case *hyphae.EmptyHypha:
+ switch userLinksHypha := hyphae2.ByName(cfg.HeaderLinksHypha).(type) {
+ case *hyphae2.EmptyHypha:
setDefaultHeaderLinks()
- case hyphae.ExistingHypha:
+ case hyphae2.ExistingHypha:
contents, err := os.ReadFile(userLinksHypha.TextFilePath())
if err != nil || len(contents) == 0 {
setDefaultHeaderLinks()
diff --git a/shroom/log.go b/internal/shroom/log.go
similarity index 87%
rename from shroom/log.go
rename to internal/shroom/log.go
index 2be9a70..2aef825 100644
--- a/shroom/log.go
+++ b/internal/shroom/log.go
@@ -1,10 +1,9 @@
package shroom
import (
+ "github.com/bouncepaw/mycorrhiza/internal/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
"log"
-
- "github.com/bouncepaw/mycorrhiza/hyphae"
- "github.com/bouncepaw/mycorrhiza/user"
)
func rejectRenameLog(h hyphae.Hypha, u *user.User, errmsg string) {
diff --git a/shroom/rename.go b/internal/shroom/rename.go
similarity index 73%
rename from shroom/rename.go
rename to internal/shroom/rename.go
index 22bdfb6..3aeeeb7 100644
--- a/shroom/rename.go
+++ b/internal/shroom/rename.go
@@ -3,36 +3,36 @@ package shroom
import (
"errors"
"fmt"
- "github.com/bouncepaw/mycorrhiza/backlinks"
- "github.com/bouncepaw/mycorrhiza/categories"
- "github.com/bouncepaw/mycorrhiza/cfg"
- "github.com/bouncepaw/mycorrhiza/files"
+ "github.com/bouncepaw/mycorrhiza/internal/backlinks"
+ "github.com/bouncepaw/mycorrhiza/internal/categories"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ "github.com/bouncepaw/mycorrhiza/internal/files"
+ hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
"path"
"path/filepath"
"regexp"
"strings"
"github.com/bouncepaw/mycorrhiza/history"
- "github.com/bouncepaw/mycorrhiza/hyphae"
- "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
)
// Rename renames the old hypha to the new name and makes a history record about that. Call if and only if the user has the permission to rename.
-func Rename(oldHypha hyphae.ExistingHypha, newName string, recursive bool, leaveRedirections bool, u *user.User) error {
+func Rename(oldHypha hyphae2.ExistingHypha, newName string, recursive bool, leaveRedirections bool, u *user.User) error {
// * bouncepaw hates this function and related renaming functions
if newName == "" {
rejectRenameLog(oldHypha, u, "no new name given")
return errors.New("ui.rename_noname_tip")
}
- if !hyphae.IsValidName(newName) {
+ if !hyphae2.IsValidName(newName) {
rejectRenameLog(oldHypha, u, fmt.Sprintf("new name ‘%s’ invalid", newName))
return errors.New("ui.rename_badname_tip") // FIXME: There is a bug related to this.
}
- switch targetHypha := hyphae.ByName(newName); targetHypha.(type) {
- case hyphae.ExistingHypha:
+ switch targetHypha := hyphae2.ByName(newName); targetHypha.(type) {
+ case hyphae2.ExistingHypha:
if targetHypha.CanonicalName() == oldHypha.CanonicalName() {
return nil
}
@@ -81,7 +81,7 @@ func Rename(oldHypha hyphae.ExistingHypha, newName string, recursive bool, leave
oldName = h.CanonicalName()
newName = re.ReplaceAllString(oldName, newName)
)
- hyphae.RenameHyphaTo(h, newName, replaceName)
+ hyphae2.RenameHyphaTo(h, newName, replaceName)
backlinks.UpdateBacklinksAfterRename(h, oldName)
categories.RenameHyphaInAllCategories(oldName, newName)
if leaveRedirections {
@@ -104,12 +104,12 @@ const redirectionTemplate = `=> %[1]s | 👁️➡️ %[2]s
func leaveRedirection(oldName, newName string, hop *history.Op) error {
var (
text = fmt.Sprintf(redirectionTemplate, newName, util.BeautifulName(newName))
- emptyHypha = hyphae.ByName(oldName)
+ emptyHypha = hyphae2.ByName(oldName)
)
switch emptyHypha := emptyHypha.(type) {
- case *hyphae.EmptyHypha:
- h := hyphae.ExtendEmptyToTextual(emptyHypha, filepath.Join(files.HyphaeDir(), oldName+".myco"))
- hyphae.Insert(h)
+ case *hyphae2.EmptyHypha:
+ h := hyphae2.ExtendEmptyToTextual(emptyHypha, filepath.Join(files.HyphaeDir(), oldName+".myco"))
+ hyphae2.Insert(h)
categories.AddHyphaToCategory(oldName, cfg.RedirectionCategory)
defer backlinks.UpdateBacklinksAfterEdit(h, "")
return writeTextToDisk(h, []byte(text), hop)
@@ -118,15 +118,15 @@ func leaveRedirection(oldName, newName string, hop *history.Op) error {
}
}
-func findHyphaeToRename(superhypha hyphae.ExistingHypha, recursive bool) []hyphae.ExistingHypha {
- hyphaList := []hyphae.ExistingHypha{superhypha}
+func findHyphaeToRename(superhypha hyphae2.ExistingHypha, recursive bool) []hyphae2.ExistingHypha {
+ hyphaList := []hyphae2.ExistingHypha{superhypha}
if recursive {
- hyphaList = append(hyphaList, hyphae.Subhyphae(superhypha)...)
+ hyphaList = append(hyphaList, hyphae2.Subhyphae(superhypha)...)
}
return hyphaList
}
-func renamingPairs(hyphaeToRename []hyphae.ExistingHypha, replaceName func(string) string) (map[string]string, error) {
+func renamingPairs(hyphaeToRename []hyphae2.ExistingHypha, replaceName func(string) string) (map[string]string, error) {
var (
renameMap = make(map[string]string)
newNames = make([]string, len(hyphaeToRename))
@@ -138,12 +138,12 @@ func renamingPairs(hyphaeToRename []hyphae.ExistingHypha, replaceName func(strin
renameMap[h.TextFilePath()] = replaceName(h.TextFilePath())
}
switch h := h.(type) {
- case *hyphae.MediaHypha:
+ case *hyphae2.MediaHypha:
renameMap[h.MediaFilePath()] = replaceName(h.MediaFilePath())
}
h.Unlock()
}
- if firstFailure, ok := hyphae.AreFreeNames(newNames...); !ok {
+ if firstFailure, ok := hyphae2.AreFreeNames(newNames...); !ok {
return nil, errors.New("Hypha " + firstFailure + " already exists")
}
return renameMap, nil
diff --git a/shroom/search.go b/internal/shroom/search.go
similarity index 94%
rename from shroom/search.go
rename to internal/shroom/search.go
index 4387abf..57efad0 100644
--- a/shroom/search.go
+++ b/internal/shroom/search.go
@@ -1,9 +1,9 @@
package shroom
import (
+ "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"strings"
- "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/util"
)
diff --git a/shroom/shroom.go b/internal/shroom/shroom.go
similarity index 100%
rename from shroom/shroom.go
rename to internal/shroom/shroom.go
diff --git a/shroom/unattach.go b/internal/shroom/unattach.go
similarity index 74%
rename from shroom/unattach.go
rename to internal/shroom/unattach.go
index e62ce02..74b76b4 100644
--- a/shroom/unattach.go
+++ b/internal/shroom/unattach.go
@@ -2,14 +2,14 @@ package shroom
import (
"fmt"
+ hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/history"
- "github.com/bouncepaw/mycorrhiza/hyphae"
- "github.com/bouncepaw/mycorrhiza/user"
)
// RemoveMedia removes media from the media hypha and makes a history record about that. If it only had media, the hypha will be deleted. If it also had text, the hypha will become textual.
-func RemoveMedia(u *user.User, h *hyphae.MediaHypha) error {
+func RemoveMedia(u *user.User, h *hyphae2.MediaHypha) error {
hop := history.
Operation(history.TypeRemoveMedia).
WithFilesRemoved(h.MediaFilePath()).
@@ -24,9 +24,9 @@ func RemoveMedia(u *user.User, h *hyphae.MediaHypha) error {
}
if h.HasTextFile() {
- hyphae.Insert(hyphae.ShrinkMediaToTextual(h))
+ hyphae2.Insert(hyphae2.ShrinkMediaToTextual(h))
} else {
- hyphae.DeleteHypha(h)
+ hyphae2.DeleteHypha(h)
}
return nil
}
diff --git a/shroom/upload.go b/internal/shroom/upload.go
similarity index 95%
rename from shroom/upload.go
rename to internal/shroom/upload.go
index 8750ca9..a63c1ec 100644
--- a/shroom/upload.go
+++ b/internal/shroom/upload.go
@@ -4,18 +4,19 @@ import (
"bytes"
"errors"
"fmt"
- "github.com/bouncepaw/mycorrhiza/backlinks"
- "github.com/bouncepaw/mycorrhiza/files"
- "github.com/bouncepaw/mycorrhiza/history"
- "github.com/bouncepaw/mycorrhiza/hyphae"
- "github.com/bouncepaw/mycorrhiza/mimetype"
- "github.com/bouncepaw/mycorrhiza/user"
"io"
"log"
"mime/multipart"
"os"
"path/filepath"
"strings"
+
+ "github.com/bouncepaw/mycorrhiza/history"
+ "github.com/bouncepaw/mycorrhiza/internal/backlinks"
+ "github.com/bouncepaw/mycorrhiza/internal/files"
+ "github.com/bouncepaw/mycorrhiza/internal/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/mimetype"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
)
func historyMessageForTextUpload(h hyphae.Hypha, userMessage string) string {
diff --git a/tree/tree.go b/internal/tree/tree.go
similarity index 64%
rename from tree/tree.go
rename to internal/tree/tree.go
index 7f00a3c..6dc171e 100644
--- a/tree/tree.go
+++ b/internal/tree/tree.go
@@ -1,14 +1,18 @@
package tree
import (
- "github.com/bouncepaw/mycorrhiza/hyphae"
+ "fmt"
+ "github.com/bouncepaw/mycorrhiza/internal/hyphae"
+ "github.com/bouncepaw/mycorrhiza/util"
+ "html/template"
+ "io"
"path"
"sort"
"strings"
)
// Tree returns the subhypha matrix as HTML and names of the next and previous hyphae (or empty strings).
-func Tree(hyphaName string) (childrenHTML, prev, next string) {
+func Tree(hyphaName string) (childrenHTML template.HTML, prev, next string) {
var (
root = child{hyphaName, true, make([]child, 0)}
descendantPrefix = hyphaName + "/"
@@ -44,6 +48,41 @@ type child struct {
children []child
}
+/*
+Subhyphae links are recursive. It may end up looking like that if drawn with
+pseudographics:
+╔══════════════╗
+║Foo ║ The presented hyphae are ./foo and ./foo/bar
+║╔════════════╗║
+║║Bar ║║
+║╚════════════╝║
+╚══════════════╝
+*/
+func childHTML(c *child, w io.Writer) {
+ sort.Slice(c.children, func(i, j int) bool {
+ return c.children[i].name < c.children[j].name
+ })
+
+ _, _ = io.WriteString(w, "\n%s \n",
+ c.name,
+ util.BeautifulName(path.Base(c.name)),
+ ))
+
+ if len(c.children) > 0 {
+ _, _ = io.WriteString(w, "\n")
+ for _, child := range c.children {
+ childHTML(&child, w)
+ }
+ _, _ = io.WriteString(w, " \n")
+ }
+ _, _ = io.WriteString(w, " \n")
+}
+
func addHyphaToChild(hyphaName, subPath string, child *child) {
// when hyphaName = "root/a/b", subPath = "a/b", and child.name = "root"
// addHyphaToChild("root/a/b", "b", child{"root/a"})
@@ -78,12 +117,13 @@ func findOrCreateSubchild(name string, baseChild *child) *child {
return &baseChild.children[len(baseChild.children)-1]
}
-func subhyphaeMatrix(children []child) (html string) {
+func subhyphaeMatrix(children []child) template.HTML {
sort.Slice(children, func(i, j int) bool {
return children[i].name < children[j].name
})
+ var buf strings.Builder
for _, child := range children {
- html += childHTML(&child)
+ childHTML(&child, &buf)
}
- return html
+ return template.HTML(buf.String())
}
diff --git a/user/files.go b/internal/user/files.go
similarity index 96%
rename from user/files.go
rename to internal/user/files.go
index 2139d83..d48d8a9 100644
--- a/user/files.go
+++ b/internal/user/files.go
@@ -3,11 +3,11 @@ package user
import (
"encoding/json"
"errors"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
"log"
"os"
- "github.com/bouncepaw/mycorrhiza/cfg"
- "github.com/bouncepaw/mycorrhiza/files"
+ "github.com/bouncepaw/mycorrhiza/internal/files"
"github.com/bouncepaw/mycorrhiza/util"
)
diff --git a/user/net.go b/internal/user/net.go
similarity index 95%
rename from user/net.go
rename to internal/user/net.go
index a97503b..00f60c2 100644
--- a/user/net.go
+++ b/internal/user/net.go
@@ -6,6 +6,7 @@ import (
"encoding/hex"
"errors"
"fmt"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
"log"
"net/http"
"sort"
@@ -14,7 +15,6 @@ import (
"golang.org/x/crypto/bcrypt"
- "github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/util"
)
@@ -79,6 +79,11 @@ func Register(username, password, group, source string, force bool) error {
return SaveUserDatabase()
}
+var (
+ ErrUnknownUsername = errors.New("unknown username")
+ ErrWrongPassword = errors.New("wrong password")
+)
+
// LoginDataHTTP logs such user in and returns string representation of an error if there is any.
//
// The HTTP parameters are used for setting header status (bad request, if it is bad) and saving a cookie.
@@ -87,12 +92,12 @@ func LoginDataHTTP(w http.ResponseWriter, username, password string) error {
if !HasUsername(username) {
w.WriteHeader(http.StatusBadRequest)
log.Println("Unknown username", username, "was entered")
- return errors.New("unknown username")
+ return ErrUnknownUsername
}
if !CredentialsOK(username, password) {
w.WriteHeader(http.StatusBadRequest)
log.Println("A wrong password was entered for username", username)
- return errors.New("wrong password")
+ return ErrWrongPassword
}
token, err := AddSession(username)
if err != nil {
diff --git a/user/user.go b/internal/user/user.go
similarity index 97%
rename from user/user.go
rename to internal/user/user.go
index 72a2c25..6aa500a 100644
--- a/user/user.go
+++ b/internal/user/user.go
@@ -2,12 +2,12 @@ package user
import (
"fmt"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
"net/http"
"strings"
"sync"
"time"
- "github.com/bouncepaw/mycorrhiza/cfg"
"golang.org/x/crypto/bcrypt"
)
@@ -37,8 +37,8 @@ var minimalRights = map[string]int{
"upload-binary": 1,
"rename": 1,
"upload-text": 1,
- "add-to-category": 2,
- "remove-from-category": 2,
+ "add-to-category": 1,
+ "remove-from-category": 1,
"remove-media": 2,
"update-header-links": 3,
"delete": 3,
diff --git a/user/users.go b/internal/user/users.go
similarity index 78%
rename from user/users.go
rename to internal/user/users.go
index 41f4135..d2ca5ef 100644
--- a/user/users.go
+++ b/internal/user/users.go
@@ -1,6 +1,9 @@
package user
-import "sync"
+import (
+ "sort"
+ "sync"
+)
var users sync.Map
var tokens sync.Map
@@ -99,3 +102,24 @@ func terminateSession(token string) {
tokens.Delete(token)
dumpTokens()
}
+
+func UsersInGroups() (admins []string, moderators []string, editors []string, readers []string) {
+ for u := range YieldUsers() {
+ switch u.Group {
+ // What if we place the users into sorted slices?
+ case "admin":
+ admins = append(admins, u.Name)
+ case "moderator":
+ moderators = append(moderators, u.Name)
+ case "editor", "trusted":
+ editors = append(editors, u.Name)
+ case "reader":
+ readers = append(readers, u.Name)
+ }
+ }
+ sort.Strings(admins)
+ sort.Strings(moderators)
+ sort.Strings(editors)
+ sort.Strings(readers)
+ return
+}
diff --git a/version/version.go b/internal/version/version.go
similarity index 100%
rename from version/version.go
rename to internal/version/version.go
diff --git a/interwiki/interwiki.go b/interwiki/interwiki.go
index 5925db3..de4a532 100644
--- a/interwiki/interwiki.go
+++ b/interwiki/interwiki.go
@@ -5,7 +5,7 @@ import (
"encoding/json"
"errors"
"git.sr.ht/~bouncepaw/mycomarkup/v5/options"
- "github.com/bouncepaw/mycorrhiza/files"
+ "github.com/bouncepaw/mycorrhiza/internal/files"
"github.com/bouncepaw/mycorrhiza/util"
"log"
"os"
diff --git a/interwiki/web.go b/interwiki/web.go
index 7e2420c..58ba51d 100644
--- a/interwiki/web.go
+++ b/interwiki/web.go
@@ -2,7 +2,7 @@ package interwiki
import (
"embed"
- "github.com/bouncepaw/mycorrhiza/viewutil"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
"github.com/gorilla/mux"
"log"
"net/http"
diff --git a/l18n/en/auth.json b/l18n/en/auth.json
index 307420a..3ead3dd 100644
--- a/l18n/en/auth.json
+++ b/l18n/en/auth.json
@@ -3,33 +3,29 @@
"password": "Password",
"register_title": "Register",
- "register_header": "Register on {{.name}}",
+ "register_header": "",
"register_button": "Register",
-
- "login_title": "Login",
- "login_header": "Log in to {{.name}}",
- "login_button": "Log in",
- "logout_title": "Logout?",
+ "logout_title": "",
"logout_header": "Log out?",
"logout_button": "Confirm",
- "logout_anon": "You cannot log out because you are not logged in.",
+ "logout_anon": "",
"lock_title": "Locked",
- "password_tip": "The server stores your password in an encrypted form; even administrators cannot read it.",
- "cookie_tip": "By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.",
- "telegram_tip": "You can log in using Telegram. It only works if you have set your @username in Telegram and this username is free on this wiki.",
+ "password_tip": "",
+ "cookie_tip": "",
+ "telegram_tip": "",
- "noauth": "Authentication is disabled. You can make edits anonymously.",
+ "noauth": "",
"noregister": "Registrations are currently closed. Administrators can make an account for you by hand; contact them.",
- "error_username": "Unknown username.",
- "error_password": "Wrong password.",
- "error_telegram": "Could not authorize using Telegram.",
+ "error_username": "",
+ "error_password": "",
+ "error_telegram": "",
"go_back": "Go back",
- "go_home": "Go home",
+ "go_home": "",
"go_login": "Go to the login page",
"try_again": "Try again"
}
diff --git a/l18n/en/ui.json b/l18n/en/ui.json
index 8350b05..23f9e55 100644
--- a/l18n/en/ui.json
+++ b/l18n/en/ui.json
@@ -4,18 +4,6 @@
"title_search": "Search by title",
"admin_panel": "Admin panel",
- "edit_link": "Edit text",
- "logout_link": "Log out",
- "history_link": "View history",
- "rename_link": "Rename",
- "delete_link": "Delete",
- "text_link": "View markup",
- "media_link": "Manage media",
- "media_link_for_textual": "Turn to media hypha",
- "backlinks_link": "{{.n}} backlink%s",
- "backlinks_link+one": "",
- "backlinks_link+other": "s",
-
"subhyphae": "Subhyphae",
"random_no_hyphae": "There are no hyphae",
@@ -57,9 +45,9 @@
"diff_title": "Diff of {{.name}} at {{.rev}}",
- "revision_title": "{{.name}} at {{.rev}}",
- "revision_warning": "Please note that viewing media is not supported in history for now.",
- "revision_link": "Get Mycomarkup source of this revision",
+ "revision_title": "",
+ "revision_warning": "",
+ "revision_link": "",
"revision_no_text": "This hypha had no text at this revision.",
"about_title": "About {{.name}}",
@@ -76,25 +64,6 @@
"media_noaudio": "Your browser does not support audio.",
"media_noaudio_link": "Download audio",
- "media_title": "Media of {{.name}}",
- "media_empty": "This hypha has no media, you can upload it here.",
- "media_tip": "You can manage the hypha's media on this page.",
- "media_what_is": "What is media?",
- "media_upload": "Upload",
- "media_stat": "Stat",
- "media_stat_size": "File size:",
- "media_size_value": "{{.n}} byte%s",
- "media_size_value+one": "",
- "media_size_value+other": "s",
- "media_stat_mime": "MIME type:",
- "media_include": "Include",
- "media_include_tip": "This media is an image. To include it in a hypha, use a syntax like this:",
- "media_new": "media",
- "media_new_tip": "You can upload a new media. Please do not upload too big pictures unless you need to because may not want to wait for big pictures to load.",
- "media_remove": "Remove media",
- "media_remove_tip": "Please note that you don't have to remove media before uploading a new media.",
- "media_remove_button": "Remove media",
-
"confirm": "Confirm",
"cancel": "Cancel"
}
diff --git a/l18n/ru/auth.json b/l18n/ru/auth.json
index 008bb35..1b1943a 100644
--- a/l18n/ru/auth.json
+++ b/l18n/ru/auth.json
@@ -2,31 +2,31 @@
"username": "Логин",
"password": "Пароль",
- "register_title": "Регистрация",
- "register_header": "Регистрация на «{{.name}}»",
- "register_button": "Зарегистрироваться",
+ "register_title": "",
+ "register_header": "",
+ "register_button": "",
"login_title": "Вход",
- "login_header": "Вход в «{{.name}}»",
- "login_button": "Войти",
+ "login_header": "",
+ "login_button": "",
- "logout_title": "Выйти?",
- "logout_header": "Выйти?",
+ "logout_title": "",
+ "logout_header": "?",
"logout_button": "Подтвердить",
- "logout_anon": "Вы не можете выйти, потому что ещё не вошли.",
+ "logout_anon": "",
- "lock_title": "Доступ закрыт",
+ "lock_title": "",
- "password_tip": "Сервер хранит ваш пароль в зашифрованном виде, даже администраторы не смогут его прочесть.",
- "cookie_tip": "Отправляя эту форму, вы разрешаете вики хранить cookie в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.",
+ "password_tip": "",
+ "cookie_tip": "",
"telegram_tip": "Вы можете войти с помощью Телеграм. Это сработает, если у вашего профиля есть @имя, и оно не занято в этой вики.",
- "noauth": "Аутентификация отключена. Вы можете делать правки анонимно.",
+ "noauth": "",
"noregister": "Регистрация в текущее время недоступна. Администраторы могут вручную создать вам учётную запись, свяжитесь с ними.",
- "error_username": "Неизвестное имя пользователя.",
+ "error_username": "",
"error_password": "Неверный пароль.",
- "error_telegram": "Не удалось авторизоваться через Телеграм.",
+ "error_telegram": "",
"go_back": "Назад",
"go_home": "Домой",
diff --git a/l18n/ru/ui.json b/l18n/ru/ui.json
index 05b3c3a..c4006ba 100644
--- a/l18n/ru/ui.json
+++ b/l18n/ru/ui.json
@@ -8,14 +8,14 @@
"backlinks_heading": "Обратные ссылки на {{.hypha_link}}",
"backlinks_desc": "Ниже перечислены гифы, на которых есть ссылка на эту гифу, трансклюзия этой гифы или эта гифа вставлена как изображение.",
- "edit_link": "Редактировать",
- "logout_link": "Выйти",
- "history_link": "История",
- "rename_link": "Переименовать",
- "delete_link": "Удалить",
- "text_link": "Посмотреть разметку",
- "media_link": "Медиа",
- "media_link_for_textual": "Превратить в медиа-гифу",
+ "edit_link": "",
+ "logout_link": "",
+ "history_link": "",
+ "rename_link": "",
+ "delete_link": "",
+ "text_link": "",
+ "media_link": "",
+ "media_link_for_textual": "",
"backlinks_link": "{{.n}} %s сюда",
"backlinks_link+one": "ссылка",
"backlinks_link+few": "ссылки",
@@ -59,9 +59,9 @@
"ask_really": "Вы действительно хотите {{.verb}} гифу «{{.name}}»?",
"ask_remove_media_verb": "убрать медиа",
- "revision_title": "{{.name}} из {{.rev}}",
- "revision_warning": "Обратите внимание, просмотр медиа в истории пока что недоступен.",
- "revision_link": "Посмотреть Микоразметку для этой ревизии",
+ "revision_title": "",
+ "revision_warning": "",
+ "revision_link": "",
"revision_no_text": "В этой ревизии гифы не было текста.",
"about_title": "О {{.name}}",
@@ -78,26 +78,6 @@
"media_noaudio": "Ваш браузер не поддерживает аудио.",
"media_noaudio_link": "Скачать аудио",
- "media_title": "Медиа «{{.name}}»",
- "media_empty": "Эта гифа не имеет медиа, здесь вы можете его загрузить.",
- "media_tip": "На этой странице вы можете управлять медиа.",
- "media_what_is": "Что такое медиа?",
- "media_upload": "Загрузить",
- "media_stat": "Свойства",
- "media_stat_size": "Размер файла:",
- "media_size_value": "{{.n}} %s",
- "media_size_value+one": "байт",
- "media_size_value+few": "байта",
- "media_size_value+many": "байт",
- "media_stat_mime": "MIME-тип:",
- "media_include": "Добавление",
- "media_include_tip": "Это медиа – изображение. Чтобы добавить его в текст гифы, используйте синтаксис ниже:",
- "media_new": "Прикрепить",
- "media_new_tip": "Вы можете загрузить новое медиа. Пожалуйста, не загружайте слишком большие изображения без необходимости, чтобы впоследствии не ждать её долгую загрузку.",
- "media_remove": "Открепить",
- "media_remove_tip": "Заметьте, чтобы заменить медиа, вам не нужно его перед этим откреплять.",
- "media_remove_button": "Открепить",
-
"confirm": "Применить",
"cancel": "Отмена"
}
diff --git a/main.go b/main.go
index 5988df3..4028464 100644
--- a/main.go
+++ b/main.go
@@ -1,30 +1,27 @@
// Command mycorrhiza is a program that runs a mycorrhiza wiki.
//
-//go:generate go run github.com/valyala/quicktemplate/qtc -dir=tree
//go:generate go run github.com/valyala/quicktemplate/qtc -dir=history
//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
package main
import (
- "github.com/bouncepaw/mycorrhiza/backlinks"
- "github.com/bouncepaw/mycorrhiza/categories"
- "github.com/bouncepaw/mycorrhiza/interwiki"
- "github.com/bouncepaw/mycorrhiza/migration"
- "github.com/bouncepaw/mycorrhiza/version"
- "github.com/bouncepaw/mycorrhiza/viewutil"
+ "github.com/bouncepaw/mycorrhiza/internal/categories"
"log"
"os"
- "github.com/bouncepaw/mycorrhiza/cfg"
- "github.com/bouncepaw/mycorrhiza/files"
"github.com/bouncepaw/mycorrhiza/history"
- "github.com/bouncepaw/mycorrhiza/hyphae"
- "github.com/bouncepaw/mycorrhiza/shroom"
- "github.com/bouncepaw/mycorrhiza/static"
- "github.com/bouncepaw/mycorrhiza/user"
+ "github.com/bouncepaw/mycorrhiza/internal/backlinks"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ "github.com/bouncepaw/mycorrhiza/internal/files"
+ "github.com/bouncepaw/mycorrhiza/internal/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/migration"
+ "github.com/bouncepaw/mycorrhiza/internal/shroom"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
+ "github.com/bouncepaw/mycorrhiza/internal/version"
+ "github.com/bouncepaw/mycorrhiza/interwiki"
"github.com/bouncepaw/mycorrhiza/web"
+ "github.com/bouncepaw/mycorrhiza/web/static"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
)
func main() {
diff --git a/misc/about.go b/misc/about.go
index cd348f6..36c392a 100644
--- a/misc/about.go
+++ b/misc/about.go
@@ -1,10 +1,10 @@
package misc
import (
- "github.com/bouncepaw/mycorrhiza/cfg"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
+ "github.com/bouncepaw/mycorrhiza/internal/version"
"github.com/bouncepaw/mycorrhiza/l18n"
- "github.com/bouncepaw/mycorrhiza/user"
- "github.com/bouncepaw/mycorrhiza/version"
"log"
"strings"
"text/template" // sic!
diff --git a/misc/handlers.go b/misc/handlers.go
index d097763..e282ee4 100644
--- a/misc/handlers.go
+++ b/misc/handlers.go
@@ -11,16 +11,16 @@ import (
"github.com/gorilla/mux"
- "github.com/bouncepaw/mycorrhiza/backlinks"
- "github.com/bouncepaw/mycorrhiza/cfg"
- "github.com/bouncepaw/mycorrhiza/files"
- "github.com/bouncepaw/mycorrhiza/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/backlinks"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ "github.com/bouncepaw/mycorrhiza/internal/files"
+ "github.com/bouncepaw/mycorrhiza/internal/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/shroom"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/l18n"
- "github.com/bouncepaw/mycorrhiza/shroom"
- "github.com/bouncepaw/mycorrhiza/static"
- "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
- "github.com/bouncepaw/mycorrhiza/viewutil"
+ "github.com/bouncepaw/mycorrhiza/web/static"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
)
func InitAssetHandlers(rtr *mux.Router) {
diff --git a/misc/views.go b/misc/views.go
index e8533dc..99f83ff 100644
--- a/misc/views.go
+++ b/misc/views.go
@@ -2,8 +2,8 @@ package misc
import (
"embed"
- "github.com/bouncepaw/mycorrhiza/hyphae"
- "github.com/bouncepaw/mycorrhiza/viewutil"
+ "github.com/bouncepaw/mycorrhiza/internal/hyphae"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
)
var (
diff --git a/mycoopts/mycoopts.go b/mycoopts/mycoopts.go
index 2d0696f..bd1d9f2 100644
--- a/mycoopts/mycoopts.go
+++ b/mycoopts/mycoopts.go
@@ -3,8 +3,8 @@ package mycoopts
import (
"errors"
"git.sr.ht/~bouncepaw/mycomarkup/v5/options"
- "github.com/bouncepaw/mycorrhiza/cfg"
- "github.com/bouncepaw/mycorrhiza/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/interwiki"
"github.com/bouncepaw/mycorrhiza/util"
)
diff --git a/mycoopts/view.qtpl b/mycoopts/view.qtpl
index 935e4de..362089c 100644
--- a/mycoopts/view.qtpl
+++ b/mycoopts/view.qtpl
@@ -1,6 +1,6 @@
{% import "path/filepath" %}
-{% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
+{% import "github.com/bouncepaw/mycorrhiza/internal/hyphae" %}
{% import "github.com/bouncepaw/mycorrhiza/l18n" %}
{% func mediaRaw(h *hyphae.MediaHypha) %}{%= Media(h, l18n.New("en", "en")) %}{% endfunc %}
diff --git a/mycoopts/view.qtpl.go b/mycoopts/view.qtpl.go
index ddb1450..7cb2926 100644
--- a/mycoopts/view.qtpl.go
+++ b/mycoopts/view.qtpl.go
@@ -8,7 +8,7 @@ package mycoopts
import "path/filepath"
//line mycoopts/view.qtpl:3
-import "github.com/bouncepaw/mycorrhiza/hyphae"
+import "github.com/bouncepaw/mycorrhiza/internal/hyphae"
//line mycoopts/view.qtpl:4
import "github.com/bouncepaw/mycorrhiza/l18n"
diff --git a/settings/view.go b/settings/view.go
deleted file mode 100644
index 338447c..0000000
--- a/settings/view.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package settings
-
-import (
- "embed"
- "github.com/bouncepaw/mycorrhiza/util"
- "github.com/bouncepaw/mycorrhiza/viewutil"
- "github.com/gorilla/mux"
- "net/http"
-
- "github.com/bouncepaw/mycorrhiza/user"
-)
-
-// TODO: translate untranslated strings
-const settingsTranslationRu = `
-{{define "change password"}}Change password{{end}}
-{{define "confirm password"}}Confirm password{{end}}
-{{define "current password"}}Current password{{end}}
-{{define "non local password change"}}Non-local accounts cannot have their passwords changed.{{end}}
-{{define "password"}}Password{{end}}
-{{define "submit"}}Submit{{end}}
-`
-
-var (
- //go:embed *.html
- fs embed.FS
- changePassowrdChain viewutil.Chain
-)
-
-func Init(rtr *mux.Router) {
- rtr.HandleFunc("/change-password", handlerUserChangePassword).Methods(http.MethodGet, http.MethodPost)
-
- changePassowrdChain = viewutil.CopyEnRuWith(fs, "view_change_password.html", settingsTranslationRu)
-}
-
-func changePasswordPage(meta viewutil.Meta, form util.FormData, u *user.User) {
- viewutil.ExecutePage(meta, changePassowrdChain, changePasswordData{
- BaseData: &viewutil.BaseData{},
- Form: form,
- U: u,
- })
-}
-
-type changePasswordData struct {
- *viewutil.BaseData
- Form util.FormData
- U *user.User
-}
diff --git a/tree/view.qtpl b/tree/view.qtpl
deleted file mode 100644
index f6fe3ef..0000000
--- a/tree/view.qtpl
+++ /dev/null
@@ -1,32 +0,0 @@
-{% import "sort" %}
-{% import "path" %}
-{% import "github.com/bouncepaw/mycorrhiza/util" %}
-
-Subhyphae links are recursive. It may end up looking like that if drawn with
-pseudographics:
-╔══════════════╗
-║Foo ║ The presented hyphae are ./foo and ./foo/bar
-║╔════════════╗║
-║║Bar ║║
-║╚════════════╝║
-╚══════════════╝
-{% func childHTML(c *child) %}
-{% code
- sort.Slice(c.children, func(i, j int) bool {
- return c.children[i].name < c.children[j].name
- })
-%}
-
-
- {%s util.BeautifulName(path.Base(c.name)) %}
-
-{% if len(c.children) > 0 %}
-
- {% for _, child := range c.children %}
- {%s= childHTML(&child) %}
- {% endfor %}
-
-{% endif %}
-
-{% endfunc %}
-
diff --git a/tree/view.qtpl.go b/tree/view.qtpl.go
deleted file mode 100644
index 1c1c89a..0000000
--- a/tree/view.qtpl.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Code generated by qtc from "view.qtpl". DO NOT EDIT.
-// See https://github.com/valyala/quicktemplate for details.
-
-//line tree/view.qtpl:1
-package tree
-
-//line tree/view.qtpl:1
-import "sort"
-
-//line tree/view.qtpl:2
-import "path"
-
-//line tree/view.qtpl:3
-import "github.com/bouncepaw/mycorrhiza/util"
-
-// Subhyphae links are recursive. It may end up looking like that if drawn with
-// pseudographics:
-// ╔══════════════╗
-// ║Foo ║ The presented hyphae are ./foo and ./foo/bar
-// ║╔════════════╗║
-// ║║Bar ║║
-// ║╚════════════╝║
-// ╚══════════════╝
-
-//line tree/view.qtpl:13
-import (
- qtio422016 "io"
-
- qt422016 "github.com/valyala/quicktemplate"
-)
-
-//line tree/view.qtpl:13
-var (
- _ = qtio422016.Copy
- _ = qt422016.AcquireByteBuffer
-)
-
-//line tree/view.qtpl:13
-func streamchildHTML(qw422016 *qt422016.Writer, c *child) {
-//line tree/view.qtpl:13
- qw422016.N().S(`
-`)
-//line tree/view.qtpl:15
- sort.Slice(c.children, func(i, j int) bool {
- return c.children[i].name < c.children[j].name
- })
-
-//line tree/view.qtpl:18
- qw422016.N().S(`
-
-
- `)
-//line tree/view.qtpl:21
- qw422016.E().S(util.BeautifulName(path.Base(c.name)))
-//line tree/view.qtpl:21
- qw422016.N().S(`
-
-`)
-//line tree/view.qtpl:23
- if len(c.children) > 0 {
-//line tree/view.qtpl:23
- qw422016.N().S(`
-
- `)
-//line tree/view.qtpl:25
- for _, child := range c.children {
-//line tree/view.qtpl:25
- qw422016.N().S(`
- `)
-//line tree/view.qtpl:26
- qw422016.N().S(childHTML(&child))
-//line tree/view.qtpl:26
- qw422016.N().S(`
- `)
-//line tree/view.qtpl:27
- }
-//line tree/view.qtpl:27
- qw422016.N().S(`
-
-`)
-//line tree/view.qtpl:29
- }
-//line tree/view.qtpl:29
- qw422016.N().S(`
-
-`)
-//line tree/view.qtpl:31
-}
-
-//line tree/view.qtpl:31
-func writechildHTML(qq422016 qtio422016.Writer, c *child) {
-//line tree/view.qtpl:31
- qw422016 := qt422016.AcquireWriter(qq422016)
-//line tree/view.qtpl:31
- streamchildHTML(qw422016, c)
-//line tree/view.qtpl:31
- qt422016.ReleaseWriter(qw422016)
-//line tree/view.qtpl:31
-}
-
-//line tree/view.qtpl:31
-func childHTML(c *child) string {
-//line tree/view.qtpl:31
- qb422016 := qt422016.AcquireByteBuffer()
-//line tree/view.qtpl:31
- writechildHTML(qb422016, c)
-//line tree/view.qtpl:31
- qs422016 := string(qb422016.B)
-//line tree/view.qtpl:31
- qt422016.ReleaseByteBuffer(qb422016)
-//line tree/view.qtpl:31
- return qs422016
-//line tree/view.qtpl:31
-}
diff --git a/util/util.go b/util/util.go
index a00230c..499697b 100644
--- a/util/util.go
+++ b/util/util.go
@@ -3,13 +3,14 @@ package util
import (
"crypto/rand"
"encoding/hex"
- "github.com/bouncepaw/mycorrhiza/files"
"log"
"net/http"
"strings"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ "github.com/bouncepaw/mycorrhiza/internal/files"
+
"git.sr.ht/~bouncepaw/mycomarkup/v5/util"
- "github.com/bouncepaw/mycorrhiza/cfg"
)
// PrepareRq strips the trailing / in rq.URL.Path. In the future it might do more stuff for making all request structs uniform.
@@ -31,6 +32,7 @@ func ShorterPath(path string) string {
}
// HTTP404Page writes a 404 error in the status, needed when no content is found on the page.
+// TODO: demolish
func HTTP404Page(w http.ResponseWriter, page string) {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusNotFound)
@@ -38,6 +40,7 @@ func HTTP404Page(w http.ResponseWriter, page string) {
}
// HTTP200Page wraps some frequently used things for successful 200 responses.
+// TODO: demolish
func HTTP200Page(w http.ResponseWriter, page string) {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK)
diff --git a/admin/admin.go b/web/admin.go
similarity index 54%
rename from admin/admin.go
rename to web/admin.go
index ef5ac3d..c01abce 100644
--- a/admin/admin.go
+++ b/web/admin.go
@@ -1,20 +1,110 @@
-package admin
+package web
import (
"fmt"
- "github.com/bouncepaw/mycorrhiza/viewutil"
- "github.com/gorilla/mux"
"log"
"mime"
"net/http"
"os"
"sort"
- "github.com/bouncepaw/mycorrhiza/cfg"
- "github.com/bouncepaw/mycorrhiza/user"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/util"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
+ "github.com/gorilla/mux"
)
+const adminTranslationRu = `
+{{define "panel title"}}Панель админстратора{{end}}
+{{define "panel safe section title"}}Безопасная секция{{end}}
+{{define "panel link about"}}Об этой вики{{end}}
+{{define "panel update header"}}Обновить ссылки в верхней панели{{end}}
+{{define "panel link user list"}}Список пользователей{{end}}
+{{define "panel users"}}Управление пользователями{{end}}
+{{define "panel unsafe section title"}}Опасная секция{{end}}
+{{define "panel shutdown"}}Выключить вики{{end}}
+{{define "panel reindex hyphae"}}Переиндексировать гифы{{end}}
+{{define "panel interwiki"}}Интервики{{end}}
+
+{{define "manage users"}}Управление пользователями{{end}}
+{{define "create user"}}Создать пользователя{{end}}
+{{define "reindex users"}}Переиндексировать пользователей{{end}}
+{{define "name"}}Имя{{end}}
+{{define "group"}}Группа{{end}}
+{{define "registered at"}}Зарегистрирован{{end}}
+{{define "actions"}}Действия{{end}}
+{{define "edit"}}Изменить{{end}}
+
+{{define "new user"}}Новый пользователь{{end}}
+{{define "password"}}Пароль{{end}}
+{{define "confirm password"}}Подтвердить пароль{{end}}
+{{define "change password"}}Изменить пароль{{end}}
+{{define "non local password change"}}Поменять пароль можно только у локальных пользователей.{{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}}
+`
+
+func viewPanel(meta viewutil.Meta) {
+ viewutil.ExecutePage(meta, panelChain, &viewutil.BaseData{})
+}
+
+type listData struct {
+ *viewutil.BaseData
+ UserHypha string
+ Users []*user.User
+}
+
+func viewList(meta viewutil.Meta, users []*user.User) {
+ viewutil.ExecutePage(meta, listChain, listData{
+ BaseData: &viewutil.BaseData{},
+ UserHypha: cfg.UserHypha,
+ 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,
+ })
+}
+
// handlerAdmin provides the admin panel.
func handlerAdmin(w http.ResponseWriter, rq *http.Request) {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
diff --git a/categories/handlers.go b/web/cats.go
similarity index 70%
rename from categories/handlers.go
rename to web/cats.go
index db016ce..47b1d7f 100644
--- a/categories/handlers.go
+++ b/web/cats.go
@@ -1,40 +1,46 @@
-package categories
+package web
import (
- "github.com/bouncepaw/mycorrhiza/user"
- "github.com/bouncepaw/mycorrhiza/util"
- "github.com/bouncepaw/mycorrhiza/viewutil"
- "github.com/gorilla/mux"
+ "github.com/bouncepaw/mycorrhiza/internal/categories"
"io"
"log"
+ "log/slog"
"net/http"
+ "sort"
"strings"
-)
-// InitHandlers initializes HTTP handlers for the given router. Call somewhere in package web.
-func InitHandlers(r *mux.Router) {
- r.PathPrefix("/add-to-category").HandlerFunc(handlerAddToCategory).Methods("POST")
- r.PathPrefix("/remove-from-category").HandlerFunc(handlerRemoveFromCategory).Methods("POST")
- r.PathPrefix("/category/").HandlerFunc(handlerCategory).Methods("GET")
- r.PathPrefix("/edit-category/").HandlerFunc(handlerEditCategory).Methods("GET")
- r.PathPrefix("/category").HandlerFunc(handlerListCategory).Methods("GET")
- prepareViews()
-}
+ "github.com/bouncepaw/mycorrhiza/internal/user"
+ "github.com/bouncepaw/mycorrhiza/util"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
+)
func handlerEditCategory(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
+ meta := viewutil.MetaFrom(w, rq)
catName := util.CanonicalName(strings.TrimPrefix(strings.TrimPrefix(rq.URL.Path, "/edit-category"), "/"))
if catName == "" {
- http.Error(w, "No category name", http.StatusBadRequest)
+ viewutil.HandlerNotFound(w, rq)
return
}
- log.Println("Editing category", catName)
- categoryEdit(viewutil.MetaFrom(w, rq), catName)
+
+ slog.Info("Editing category", "name", catName)
+ _ = pageCatEdit.RenderTo(meta, map[string]any{
+ "Addr": "/edit-category/" + catName,
+ "CatName": catName,
+ "Hyphae": categories.HyphaeInCategory(catName),
+ "GivenPermissionToModify": meta.U.CanProceed("add-to-category"),
+ })
}
func handlerListCategory(w http.ResponseWriter, rq *http.Request) {
- log.Println("Viewing list of categories")
- categoryList(viewutil.MetaFrom(w, rq))
+ slog.Info("Viewing list of categories")
+ cats := categories.ListOfCategories()
+ sort.Strings(cats)
+
+ _ = pageCatList.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
+ "Addr": "/category",
+ "Categories": cats,
+ })
}
func handlerCategory(w http.ResponseWriter, rq *http.Request) {
@@ -44,8 +50,15 @@ func handlerCategory(w http.ResponseWriter, rq *http.Request) {
handlerListCategory(w, rq)
return
}
- log.Println("Viewing category", catName)
- categoryPage(viewutil.MetaFrom(w, rq), catName)
+
+ meta := viewutil.MetaFrom(w, rq)
+ slog.Info("Viewing category", "name", catName)
+ _ = pageCatPage.RenderTo(meta, map[string]any{
+ "Addr": "/category/" + catName,
+ "CatName": catName,
+ "Hyphae": categories.HyphaeInCategory(catName),
+ "GivenPermissionToModify": meta.U.CanProceed("add-to-category"),
+ })
}
// A request for removal of hyphae can either remove one hypha (used in the card on /hypha) or many hyphae (used in /edit-category). Both approaches are handled by /remove-from-category. This function finds all passed hyphae.
@@ -93,7 +106,7 @@ func handlerRemoveFromCategory(w http.ResponseWriter, rq *http.Request) {
}
for _, hyphaName := range hyphaNames {
// TODO: Make it more effective.
- removeHyphaFromCategory(hyphaName, catName)
+ categories.RemoveHyphaFromCategory(hyphaName, catName)
}
log.Printf("%s removed %q from category %s\n", u.Name, hyphaNames, catName)
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
@@ -115,7 +128,7 @@ func handlerAddToCategory(w http.ResponseWriter, rq *http.Request) {
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
return
}
- log.Println(user.FromRequest(rq).Name, "added", hyphaName, "to", catName)
- AddHyphaToCategory(hyphaName, catName)
+ slog.Info(user.FromRequest(rq).Name, "added", hyphaName, "to", catName)
+ categories.AddHyphaToCategory(hyphaName, catName)
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
}
diff --git a/web/mutators.go b/web/mutators.go
index e85ddda..75e13ae 100644
--- a/web/mutators.go
+++ b/web/mutators.go
@@ -2,22 +2,21 @@ package web
import (
"git.sr.ht/~bouncepaw/mycomarkup/v5"
+ "github.com/bouncepaw/mycorrhiza/internal/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/shroom"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
"html/template"
"log"
"net/http"
+ "git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
"github.com/bouncepaw/mycorrhiza/hypview"
"github.com/bouncepaw/mycorrhiza/mycoopts"
- "github.com/bouncepaw/mycorrhiza/viewutil"
-
- "git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
"github.com/gorilla/mux"
- "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/l18n"
- "github.com/bouncepaw/mycorrhiza/shroom"
- "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
)
@@ -25,7 +24,7 @@ func initMutators(r *mux.Router) {
r.PathPrefix("/edit/").HandlerFunc(handlerEdit)
r.PathPrefix("/rename/").HandlerFunc(handlerRename).Methods("GET", "POST")
r.PathPrefix("/delete/").HandlerFunc(handlerDelete).Methods("GET", "POST")
- r.PathPrefix("/remove-media/").HandlerFunc(handlerRemoveMedia).Methods("GET", "POST")
+ r.PathPrefix("/remove-media/").HandlerFunc(handlerRemoveMedia).Methods("POST")
r.PathPrefix("/upload-binary/").HandlerFunc(handlerUploadBinary)
r.PathPrefix("/upload-text/").HandlerFunc(handlerUploadText)
}
@@ -43,10 +42,6 @@ func handlerRemoveMedia(w http.ResponseWriter, rq *http.Request) {
viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "no rights")
return
}
- if rq.Method == "GET" {
- hypview.RemoveMedia(viewutil.MetaFrom(w, rq), h.CanonicalName())
- return
- }
switch h := h.(type) {
case *hyphae.EmptyHypha, *hyphae.TextualHypha:
viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "no media to remove")
@@ -83,7 +78,11 @@ func handlerDelete(w http.ResponseWriter, rq *http.Request) {
}
if rq.Method == "GET" {
- hypview.DeleteHypha(meta, h.CanonicalName())
+ _ = pageHyphaDelete.RenderTo(
+ viewutil.MetaFrom(w, rq),
+ map[string]any{
+ "HyphaName": h.CanonicalName(),
+ })
return
}
@@ -169,7 +168,15 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
return
}
}
- hypview.EditHypha(meta, hyphaName, isNew, content, "", "")
+ _ = pageHyphaEdit.RenderTo(
+ viewutil.MetaFrom(w, rq),
+ map[string]any{
+ "HyphaName": hyphaName,
+ "Content": content,
+ "IsNew": isNew,
+ "Message": "",
+ "Preview": "",
+ })
}
// handlerUploadText uploads a new text part for the hypha.
@@ -191,7 +198,16 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
if action == "preview" {
ctx, _ := mycocontext.ContextFromStringInput(textData, mycoopts.MarkupOptions(hyphaName))
preview := template.HTML(mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx)))
- hypview.EditHypha(meta, hyphaName, isNew, textData, message, preview)
+
+ _ = pageHyphaEdit.RenderTo(
+ viewutil.MetaFrom(w, rq),
+ map[string]any{
+ "HyphaName": hyphaName,
+ "Content": textData,
+ "IsNew": isNew,
+ "Message": message,
+ "Preview": preview,
+ })
return
}
diff --git a/viewutil/base.html b/web/newtmpl/base.html
similarity index 100%
rename from viewutil/base.html
rename to web/newtmpl/base.html
diff --git a/web/newtmpl/newtmpl.go b/web/newtmpl/newtmpl.go
new file mode 100644
index 0000000..b0184c5
--- /dev/null
+++ b/web/newtmpl/newtmpl.go
@@ -0,0 +1,118 @@
+package newtmpl
+
+import (
+ "embed"
+ "fmt"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ "github.com/bouncepaw/mycorrhiza/util"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
+ "html/template"
+ "strings"
+)
+
+//go:embed *.html
+var fs embed.FS
+
+var base = template.Must(template.ParseFS(fs, "base.html"))
+
+type Page struct {
+ TemplateEnglish *template.Template
+ TemplateRussian *template.Template
+}
+
+func NewPage(fs embed.FS, russianTranslation map[string]string, tmpls ...string) *Page {
+ must := template.Must
+ en := must(must(must(
+ base.Clone()).
+ Funcs(template.FuncMap{
+ "beautifulName": util.BeautifulName,
+ "inc": func(i int) int { return i + 1 },
+ "base": func(hyphaName string) string {
+ parts := strings.Split(hyphaName, "/")
+ return parts[len(parts)-1]
+ },
+ "beautifulLink": func(hyphaName string) template.HTML {
+ return template.HTML(
+ fmt.Sprintf(
+ `%s `, hyphaName, hyphaName))
+ },
+ }).
+ Parse(fmt.Sprintf(`
+{{define "wiki name"}}%s{{end}}
+{{define "user hypha"}}%s{{end}}
+`, cfg.WikiName, cfg.UserHypha))).
+ ParseFS(fs, tmpls...))
+
+ if cfg.UseAuth {
+ en = must(en.Parse(`
+{{define "auth"}}
+
+{{end}}
+`))
+ }
+ if cfg.AllowRegistration {
+ must(en.Parse(`{{define "registration"}}
+{{if .Meta.U.Group | eq "anon"}}
+
+
+ {{block "register" .}}Register{{end}}
+
+
+{{end}}
+{{end}}`))
+ }
+
+ russianTranslation["search by title"] = "Поиск по названию"
+ russianTranslation["login"] = "Войти"
+ russianTranslation["register"] = "Регистрация"
+ russianTranslation["cancel"] = "Отмена"
+ russianTranslation["categories"] = "Категории"
+ russianTranslation["remove from category title"] = "Убрать гифу из этой категории"
+ russianTranslation["placeholder"] = "Название категории..."
+ russianTranslation["add to category title"] = "Добавить гифу в эту категорию"
+
+ return &Page{
+ TemplateEnglish: en,
+ TemplateRussian: must(must(en.Clone()).
+ Parse(translationsIntoTemplates(russianTranslation))),
+ }
+}
+
+func translationsIntoTemplates(m map[string]string) string {
+ var sb strings.Builder
+ for k, v := range m {
+ sb.WriteString(fmt.Sprintf(`{{define "%s"}}%s{{end}}
+`, k, v))
+ }
+ return sb.String()
+}
+
+func (p *Page) RenderTo(meta viewutil.Meta, data map[string]any) error {
+ data["Meta"] = meta
+ data["HeadElements"] = meta.HeadElements
+ data["BodyAttributes"] = meta.BodyAttributes
+ data["CommonScripts"] = cfg.CommonScripts
+ data["EditScripts"] = cfg.EditScripts
+ data["HeaderLinks"] = viewutil.HeaderLinks
+ data["UseAuth"] = cfg.UseAuth
+
+ tmpl := p.TemplateEnglish
+ if meta.LocaleIsRussian() {
+ tmpl = p.TemplateRussian
+ }
+
+ return tmpl.ExecuteTemplate(meta.W, "page", data)
+}
diff --git a/web/pages.go b/web/pages.go
new file mode 100644
index 0000000..38cf691
--- /dev/null
+++ b/web/pages.go
@@ -0,0 +1,202 @@
+package web
+
+import (
+ "embed"
+ "github.com/bouncepaw/mycorrhiza/web/newtmpl"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
+)
+
+//go:embed views/*.html
+var fs embed.FS
+
+var pageOrphans, pageBacklinks, pageUserList, pageChangePassword *newtmpl.Page
+var pageHyphaDelete, pageHyphaEdit, pageHyphaEmpty, pageHypha *newtmpl.Page
+var pageRevision, pageMedia *newtmpl.Page
+var pageAuthLock, pageAuthLogin, pageAuthLogout, pageAuthRegister *newtmpl.Page
+var pageCatPage, pageCatList, pageCatEdit *newtmpl.Page
+
+var panelChain, listChain, newUserChain, editUserChain, deleteUserChain viewutil.Chain
+
+func initPages() {
+
+ panelChain = viewutil.CopyEnRuWith(fs, "views/admin-panel.html", adminTranslationRu)
+ listChain = viewutil.CopyEnRuWith(fs, "views/admin-user-list.html", adminTranslationRu)
+ newUserChain = viewutil.CopyEnRuWith(fs, "views/admin-new-user.html", adminTranslationRu)
+ editUserChain = viewutil.CopyEnRuWith(fs, "views/admin-edit-user.html", adminTranslationRu)
+ deleteUserChain = viewutil.CopyEnRuWith(fs, "views/admin-delete-user.html", adminTranslationRu)
+
+ pageOrphans = newtmpl.NewPage(fs, map[string]string{
+ "orphaned hyphae": "Гифы-сироты",
+ "orphan description": "Ниже перечислены гифы без ссылок на них.",
+ }, "views/orphans.html")
+ pageBacklinks = newtmpl.NewPage(fs, map[string]string{
+ "backlinks to text": `Обратные ссылки на {{.}}`,
+ "backlinks to link": `Обратные ссылки на {{beautifulName .}} `,
+ "description": `Ниже перечислены гифы, на которых есть ссылка на эту гифу, трансклюзия этой гифы или эта гифа вставлена как изображение.`,
+ }, "views/backlinks.html")
+ pageUserList = newtmpl.NewPage(fs, map[string]string{
+ "title": "Список пользователей",
+ "administrators": "Администраторы",
+ "moderators": "Модераторы",
+ "editors": "Редакторы",
+ "readers": "Читатели",
+ }, "views/user-list.html")
+ pageChangePassword = newtmpl.NewPage(fs, map[string]string{
+ "change password": "Сменить пароль",
+ "confirm password": "Повторите пароль",
+ "current password": "Текущий пароль",
+ "non local password change": "Пароль можно поменять только местным аккаунтам. Telegram-аккаунтам нельзя.",
+ "password": "Пароль",
+ "submit": "Поменять",
+ }, "views/change-password.html")
+ pageHyphaDelete = newtmpl.NewPage(fs, map[string]string{
+ "delete hypha?": "Удалить {{beautifulName .}}?",
+ "delete [[hypha]]?": "Удалить {{beautifulName .}} ?",
+ "want to delete?": "Вы действительно хотите удалить эту гифу?",
+ "delete tip": "Нельзя отменить удаление гифы, но её история останется доступной.",
+ }, "views/hypha-delete.html")
+ pageHyphaEdit = newtmpl.NewPage(fs, map[string]string{
+ "editing hypha": `Редактирование {{beautifulName .}}`,
+ "editing [[hypha]]": `Редактирование {{beautifulName .}} `,
+ "creating [[hypha]]": `Создание {{beautifulName .}} `,
+ "you're creating a new hypha": `Вы создаёте новую гифу.`,
+ "describe your changes": `Опишите ваши правки`,
+ "save": `Сохранить`,
+ "preview": `Предпросмотр`,
+ "previewing hypha": `Предпросмотр {{beautifulName .}}`,
+ "preview tip": `Заметьте, эта гифа ещё не сохранена. Вот её предпросмотр:`,
+
+ "markup": `Разметка`,
+ "link": `Ссылка`,
+ "link title": `Текст`,
+ "heading": `Заголовок`,
+ "bold": `Жирный`,
+ "italic": `Курсив`,
+ "highlight": `Выделение`,
+ "underline": `Подчеркивание`,
+ "mono": `Моноширинный`,
+ "super": `Надстрочный`,
+ "sub": `Подстрочный`,
+ "strike": `Зачёркнутый`,
+ "rocket": `Ссылка-ракета`,
+ "transclude": `Трансклюзия`,
+ "hr": `Гориз. черта`,
+ "code": `Код-блок`,
+ "bullets": `Маркир. список`,
+ "numbers": `Нумер. список`,
+ "mycomarkup help": `Подробнее о Микоразметке`,
+ "actions": `Действия`,
+ "current date local": `Местная дата`,
+ "current time local": `Местное время`,
+ "current date utc": "Дата UTC",
+ "current time utc": "Время UTC",
+ "selflink": `Ссылка на вас`,
+ }, "views/hypha-edit.html")
+ pageHypha = newtmpl.NewPage(fs, map[string]string{
+ "edit text": "Редактировать",
+ "log out": "Выйти",
+ "admin panel": "Админка",
+ "subhyphae": "Подгифы",
+ "history": "История",
+ "rename": "Переименовать",
+ "delete": "Удалить",
+ "view markup": "Посмотреть разметку",
+ "manage media": "Медиа",
+ "turn to media": "Превратить в медиа-гифу",
+ "backlinks": "{{.BacklinkCount}} обратн{{if eq .BacklinkCount 1}}ая ссылка{{else if and (le .BacklinkCount 4) (gt .BacklinkCount 1)}}ые ссылки{{else}}ых ссылок{{end}}",
+
+ "empty heading": `Эта гифа не существует`,
+ "empty no rights": `У вас нет прав для создания новых гиф. Вы можете:`,
+ "empty log in": `Войти в свою учётную запись, если она у вас есть`,
+ "empty register": `Создать новую учётную запись`,
+ "write a text": `Написать текст`,
+ "write a text tip": `Напишите заметку, дневник, статью, рассказ или иной текст с помощью Микоразметки . Сохраняется полная история правок документа.`,
+ "write a text writing conventions": `Не забывайте следовать правилам оформления этой вики, если они имеются.`,
+ "write a text btn": `Создать`,
+ "upload a media": `Загрузить медиа`,
+ "upload a media tip": `Загрузите изображение, видео или аудио. Распространённые форматы можно просматривать из браузера, остальные можно только скачать и просмотреть локально. Позже вы можете дописать пояснение к этому медиа.`,
+ "upload a media btn": `Загрузить`,
+ }, "views/hypha.html")
+ pageRevision = newtmpl.NewPage(fs, map[string]string{
+ "revision warning": "Обратите внимание, просмотр медиа в истории пока что недоступен.",
+ "revision link": "Посмотреть Микоразметку для этой ревизии",
+ "hypha at rev": "{{.HyphaName}} на {{.RevHash}}",
+ }, "views/hypha-revision.html")
+ pageMedia = newtmpl.NewPage(fs, map[string]string{ // TODO: сделать новый перевод
+ "media title": "Медиа «{{.HyphaName | beautifulLink}}»",
+ "tip": "На этой странице вы можете управлять медиа.",
+ "empty": "Эта гифа не имеет медиа, здесь вы можете его загрузить.",
+ "what is media?": "Что такое медиа?",
+ "stat": "Свойства",
+ "stat size": "Размер файла:",
+ "stat mime": "MIME-тип:",
+
+ "upload title": "Прикрепить",
+ "upload tip": "Вы можете загрузить новое медиа. Пожалуйста, не загружайте слишком большие изображения без необходимости, чтобы впоследствии не ждать её долгую загрузку.",
+ "upload btn": "Загрузить",
+
+ "remove title": "Открепить",
+ "remove tip": "Заметьте, чтобы заменить медиа, вам не нужно его перед этим откреплять.",
+ "remove btn": "Открепить",
+ }, "views/hypha-media.html")
+
+ pageAuthLock = newtmpl.NewPage(fs, map[string]string{
+ "lock title": "Доступ закрыт",
+ "username": "Логин",
+ "password": "Пароль",
+ "log in": "Войти",
+ }, "views/auth-telegram.html", "views/auth-lock.html")
+
+ pageAuthLogin = newtmpl.NewPage(fs, map[string]string{
+ "username": "Логин",
+ "password": "Пароль",
+ "log in": "Войти",
+ "cookie tip": "Отправляя эту форму, вы разрешаете вики хранить cookie в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.",
+ "log in to x": "Войти в {{.}}",
+ "auth disabled": "Аутентификация отключена. Вы можете делать правки анонимно.",
+ "error username": "Неизвестное имя пользователя.",
+ "error password": "Неправильный пароль.",
+ "error telegram": "Не удалось войти через Телеграм.",
+ "go home": "Домой",
+ }, "views/auth-telegram.html", "views/auth-login.html")
+
+ pageAuthLogout = newtmpl.NewPage(fs, map[string]string{
+ "log out?": "Выйти?",
+ "log out": "Выйти",
+ "cannot log out anon": "Вы не можете выйти, потому что ещё не вошли.",
+ "log in": "Войти",
+ "go home": "Домой",
+ }, "views/auth-logout.html")
+
+ pageAuthRegister = newtmpl.NewPage(fs, map[string]string{
+ "username": "Логин",
+ "password": "Пароль",
+ "cookie tip": "Отправляя эту форму, вы разрешаете вики хранить cookie в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.",
+ "password tip": "Сервер хранит ваш пароль в зашифрованном виде, даже администраторы не смогут его прочесть.",
+ "register btn": "Зарегистрироваться",
+ "register on x": "Регистрация на {{.}}",
+ }, "views/auth-telegram.html", "views/auth-register.html")
+
+ pageCatPage = newtmpl.NewPage(fs, map[string]string{
+ "category x": "Категория {{. | beautifulName}}",
+ "edit": "Редактировать",
+ "cat": "Категория",
+ "empty cat": "Эта категория пуста.",
+ }, "views/cat-page.html")
+
+ pageCatEdit = newtmpl.NewPage(fs, map[string]string{
+ "edit category x": "Редактирование категории {{beautifulName .}}",
+ "edit category heading": "Редактирование категории {{beautifulName .}} ",
+ "empty cat": "Эта категория пуста.",
+ "add to category title": "Добавить гифу в эту категорию",
+ "hypha name": "Название гифы",
+ "add": "Добавить",
+ "remove hyphae": "Убрать гифы из этой категории",
+ "remove": "Убрать",
+ }, "views/cat-edit.html")
+
+ pageCatList = newtmpl.NewPage(fs, map[string]string{
+ "category list": "Список категорий",
+ "no categories": "В этой вики нет категорий.",
+ }, "views/cat-list.html")
+}
diff --git a/settings/settings.go b/web/password.go
similarity index 84%
rename from settings/settings.go
rename to web/password.go
index fbb3eca..4a74a7b 100644
--- a/settings/settings.go
+++ b/web/password.go
@@ -1,15 +1,13 @@
-package settings
+package web
import (
"fmt"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
+ "github.com/bouncepaw/mycorrhiza/util"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
"mime"
"net/http"
"reflect"
-
- "github.com/bouncepaw/mycorrhiza/viewutil"
-
- "github.com/bouncepaw/mycorrhiza/user"
- "github.com/bouncepaw/mycorrhiza/util"
)
func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) {
@@ -49,6 +47,7 @@ func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) {
f = f.WithError(err)
}
} else {
+ // TODO: handle first attempt different
err := fmt.Errorf("incorrect password")
f = f.WithError(err)
}
@@ -58,5 +57,11 @@ func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) {
}
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
- changePasswordPage(viewutil.MetaFrom(w, rq), f, u)
+ _ = pageChangePassword.RenderTo(
+ viewutil.MetaFrom(w, rq),
+ map[string]any{
+ "Form": f,
+ "U": u,
+ },
+ )
}
diff --git a/web/readers.go b/web/readers.go
index 0482a9a..0f8363c 100644
--- a/web/readers.go
+++ b/web/readers.go
@@ -3,15 +3,24 @@ package web
import (
"fmt"
"git.sr.ht/~bouncepaw/mycomarkup/v5"
- "github.com/bouncepaw/mycorrhiza/categories"
- "github.com/bouncepaw/mycorrhiza/files"
- views2 "github.com/bouncepaw/mycorrhiza/hypview"
+ "github.com/bouncepaw/mycorrhiza/hypview"
+ "github.com/bouncepaw/mycorrhiza/internal/backlinks"
+ "github.com/bouncepaw/mycorrhiza/internal/categories"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ "github.com/bouncepaw/mycorrhiza/internal/files"
+ "github.com/bouncepaw/mycorrhiza/internal/hyphae"
+ "github.com/bouncepaw/mycorrhiza/internal/mimetype"
+ "github.com/bouncepaw/mycorrhiza/internal/tree"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/mycoopts"
- "github.com/bouncepaw/mycorrhiza/viewutil"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
+ "html/template"
"io"
"log"
+ "log/slog"
"net/http"
"os"
+ "path"
"path/filepath"
"strings"
"time"
@@ -21,10 +30,7 @@ import (
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
"github.com/bouncepaw/mycorrhiza/history"
- "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/l18n"
- "github.com/bouncepaw/mycorrhiza/mimetype"
- "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
)
@@ -38,6 +44,10 @@ func initReaders(r *mux.Router) {
r.PathPrefix("/media/").HandlerFunc(handlerMedia)
r.Path("/today").HandlerFunc(handlerToday)
r.Path("/edit-today").HandlerFunc(handlerEditToday)
+
+ // Backlinks
+ r.PathPrefix("/backlinks/").HandlerFunc(handlerBacklinks)
+ r.PathPrefix("/orphans").HandlerFunc(handlerOrphans)
}
func handlerEditToday(w http.ResponseWriter, rq *http.Request) {
@@ -56,15 +66,31 @@ func handlerMedia(w http.ResponseWriter, rq *http.Request) {
hyphaName = util.HyphaNameFromRq(rq, "media")
h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq)
- lc = l18n.FromRequest(rq)
+ isMedia = false
+
+ mime string
+ fileSize int64
)
- util.HTTP200Page(w,
- viewutil.Base(
- viewutil.MetaFrom(w, rq),
- lc.Get("ui.media_title", &l18n.Replacements{"name": util.BeautifulName(hyphaName)}),
- views2.MediaMenu(rq, h, u),
- map[string]string{},
- ))
+ switch h := h.(type) {
+ case *hyphae.MediaHypha:
+ isMedia = true
+ mime = mimetype.FromExtension(path.Ext(h.MediaFilePath()))
+
+ fileinfo, err := os.Stat(h.MediaFilePath())
+ if err != nil {
+ slog.Error("failed to stat media file", "err", err)
+ // no return
+ }
+
+ fileSize = fileinfo.Size()
+ }
+ _ = pageMedia.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
+ "HyphaName": h.CanonicalName(),
+ "U": u,
+ "IsMediaHypha": isMedia,
+ "MimeType": mime,
+ "FileSize": fileSize,
+ })
}
// handlerRevisionText sends Mycomarkup text of the hypha at the given revision. See also: handlerRevision, handlerText.
@@ -129,7 +155,7 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
var (
hyphaName = util.CanonicalName(slug)
h = hyphae.ByName(hyphaName)
- contents = fmt.Sprintf(`%s
`, lc.Get("ui.revision_no_text"))
+ contents = template.HTML(fmt.Sprintf(`%s
`, lc.Get("ui.revision_no_text")))
textContents string
err error
mycoFilePath string
@@ -143,26 +169,17 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
textContents, err = history.FileAtRevision(mycoFilePath, revHash)
if err == nil {
ctx, _ := mycocontext.ContextFromStringInput(textContents, mycoopts.MarkupOptions(hyphaName))
- contents = mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx))
+ contents = template.HTML(mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx)))
}
- page := views2.Revision(
- viewutil.MetaFrom(w, rq),
- h,
- contents,
- revHash,
- )
- w.Header().Set("Content-Type", "text/html;charset=utf-8")
- w.WriteHeader(http.StatusOK)
- _, _ = fmt.Fprint(
- w,
- viewutil.Base(
- viewutil.MetaFrom(w, rq),
- lc.Get("ui.revision_title", &l18n.Replacements{"name": util.BeautifulName(hyphaName), "rev": revHash}),
- page,
- map[string]string{},
- ),
- )
+ meta := viewutil.MetaFrom(w, rq)
+ _ = pageRevision.RenderTo(meta, map[string]any{
+ "ViewScripts": cfg.ViewScripts,
+ "Contents": contents,
+ "RevHash": revHash,
+ "NaviTitle": hypview.NaviTitle(meta, h.CanonicalName()),
+ "HyphaName": h.CanonicalName(),
+ })
}
// handlerText serves raw source text of the hypha.
@@ -182,8 +199,7 @@ func handlerBinary(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
hyphaName := util.HyphaNameFromRq(rq, "binary")
switch h := hyphae.ByName(hyphaName).(type) {
- case *hyphae.EmptyHypha:
- case *hyphae.TextualHypha:
+ case *hyphae.EmptyHypha, *hyphae.TextualHypha:
w.WriteHeader(http.StatusNotFound)
log.Printf("Textual hypha ‘%s’ has no media, cannot serve\n", h.CanonicalName())
case *hyphae.MediaHypha:
@@ -197,44 +213,80 @@ func handlerBinary(w http.ResponseWriter, rq *http.Request) {
func handlerHypha(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
var (
- hyphaName = util.HyphaNameFromRq(rq, "page", "hypha")
- h = hyphae.ByName(hyphaName)
- contents string
- openGraph string
- lc = l18n.FromRequest(rq)
+ hyphaName = util.HyphaNameFromRq(rq, "page", "hypha")
+ h = hyphae.ByName(hyphaName)
+ contents template.HTML
+ openGraph template.HTML
+ lc = l18n.FromRequest(rq)
+ meta = viewutil.MetaFrom(w, rq)
+ subhyphae, prevHyphaName, nextHyphaName = tree.Tree(h.CanonicalName())
+ cats = categories.CategoriesWithHypha(h.CanonicalName())
+ category_list = ":" + strings.Join(cats, ":") + ":"
+ isMyProfile = cfg.UseAuth && util.IsProfileName(h.CanonicalName()) && meta.U.Name == strings.TrimPrefix(h.CanonicalName(), cfg.UserHypha+"/")
+
+ data = map[string]any{
+ "HyphaName": h.CanonicalName(),
+ "SubhyphaeHTML": subhyphae,
+ "PrevHyphaName": prevHyphaName,
+ "NextHyphaName": nextHyphaName,
+ "IsMyProfile": isMyProfile,
+ "NaviTitle": hypview.NaviTitle(meta, h.CanonicalName()),
+ "BacklinkCount": backlinks.BacklinksCount(h.CanonicalName()),
+ "GivenPermissionToModify": user.CanProceed(rq, "edit"),
+ "Categories": cats,
+ "IsMediaHypha": false,
+ }
)
+ slog.Info("reading hypha", "name", h.CanonicalName(), "can edit", data["GivenPermissionToModify"])
+ meta.BodyAttributes = map[string]string{
+ "cats": category_list,
+ }
switch h := h.(type) {
case *hyphae.EmptyHypha:
- util.HTTP404Page(w,
- viewutil.Base(
- viewutil.MetaFrom(w, rq),
- util.BeautifulName(hyphaName),
- views2.Hypha(viewutil.MetaFrom(w, rq), h, contents),
- map[string]string{},
- openGraph))
+ w.WriteHeader(http.StatusNotFound)
+ data["Contents"] = ""
+ _ = pageHypha.RenderTo(meta, data)
case hyphae.ExistingHypha:
- fileContentsT, errT := os.ReadFile(h.TextFilePath())
- if errT == nil {
+ fileContentsT, err := os.ReadFile(h.TextFilePath())
+ if err == nil {
ctx, _ := mycocontext.ContextFromStringInput(string(fileContentsT), mycoopts.MarkupOptions(hyphaName))
getOpenGraph, descVisitor, imgVisitor := tools.OpenGraphVisitors(ctx)
+ openGraph = template.HTML(getOpenGraph())
ast := mycomarkup.BlockTree(ctx, descVisitor, imgVisitor)
- contents = mycomarkup.BlocksToHTML(ctx, ast)
- openGraph = getOpenGraph()
+ contents = template.HTML(mycomarkup.BlocksToHTML(ctx, ast))
}
switch h := h.(type) {
case *hyphae.MediaHypha:
- contents = mycoopts.Media(h, lc) + contents
+ contents = template.HTML(mycoopts.Media(h, lc)) + contents
+ data["IsMediaHypha"] = true
}
- category_list := ":" + strings.Join(categories.CategoriesWithHypha(h.CanonicalName()), ":") + ":"
+ data["Contents"] = contents
+ meta.HeadElements = append(meta.HeadElements, openGraph)
+ _ = pageHypha.RenderTo(meta, data)
- util.HTTP200Page(w,
- viewutil.Base(
- viewutil.MetaFrom(w, rq),
- util.BeautifulName(hyphaName),
- views2.Hypha(viewutil.MetaFrom(w, rq), h, contents),
- map[string]string{"cats": category_list},
- openGraph))
+ // TODO: check head cats
+ // TODO: check opengraph
}
}
+
+// handlerBacklinks lists all backlinks to a hypha.
+func handlerBacklinks(w http.ResponseWriter, rq *http.Request) {
+ hyphaName := util.HyphaNameFromRq(rq, "backlinks")
+
+ _ = pageBacklinks.RenderTo(viewutil.MetaFrom(w, rq),
+ map[string]any{
+ "Addr": "/backlinks/" + hyphaName,
+ "HyphaName": hyphaName,
+ "Backlinks": backlinks.BacklinksFor(hyphaName),
+ })
+}
+
+func handlerOrphans(w http.ResponseWriter, rq *http.Request) {
+ _ = pageOrphans.RenderTo(viewutil.MetaFrom(w, rq),
+ map[string]any{
+ "Addr": "/orphans",
+ "Orphans": backlinks.Orphans(),
+ })
+}
diff --git a/static/common.js b/web/static/common.js
similarity index 100%
rename from static/common.js
rename to web/static/common.js
diff --git a/static/default.css b/web/static/default.css
similarity index 99%
rename from static/default.css
rename to web/static/default.css
index f13975f..36c683f 100644
--- a/static/default.css
+++ b/web/static/default.css
@@ -354,7 +354,7 @@ kbd {
margin: 0;
padding: 8px;
border: none;
- background: url(/static/icon/x.svg) no-repeat 8px 8px / 16px 16px;
+ background: url(/web/static/icon/x.svg) no-repeat 8px 8px / 16px 16px;
width: 32px;
height: 32px;
cursor: pointer;
diff --git a/static/editor.js b/web/static/editor.js
similarity index 100%
rename from static/editor.js
rename to web/static/editor.js
diff --git a/static/icon/README.md b/web/static/icon/README.md
similarity index 100%
rename from static/icon/README.md
rename to web/static/icon/README.md
diff --git a/static/icon/feed.svg b/web/static/icon/feed.svg
similarity index 100%
rename from static/icon/feed.svg
rename to web/static/icon/feed.svg
diff --git a/static/icon/gemini-proto.svg b/web/static/icon/gemini-proto.svg
similarity index 100%
rename from static/icon/gemini-proto.svg
rename to web/static/icon/gemini-proto.svg
diff --git a/static/icon/gopher-proto.svg b/web/static/icon/gopher-proto.svg
similarity index 100%
rename from static/icon/gopher-proto.svg
rename to web/static/icon/gopher-proto.svg
diff --git a/static/icon/http-proto.svg b/web/static/icon/http-proto.svg
similarity index 100%
rename from static/icon/http-proto.svg
rename to web/static/icon/http-proto.svg
diff --git a/static/icon/mailto-proto.svg b/web/static/icon/mailto-proto.svg
similarity index 100%
rename from static/icon/mailto-proto.svg
rename to web/static/icon/mailto-proto.svg
diff --git a/static/icon/mushroom.png b/web/static/icon/mushroom.png
similarity index 100%
rename from static/icon/mushroom.png
rename to web/static/icon/mushroom.png
diff --git a/static/icon/x.svg b/web/static/icon/x.svg
similarity index 100%
rename from static/icon/x.svg
rename to web/static/icon/x.svg
diff --git a/static/robots.txt b/web/static/robots.txt
similarity index 100%
rename from static/robots.txt
rename to web/static/robots.txt
diff --git a/static/shortcuts.js b/web/static/shortcuts.js
similarity index 100%
rename from static/shortcuts.js
rename to web/static/shortcuts.js
diff --git a/static/static.go b/web/static/static.go
similarity index 100%
rename from static/static.go
rename to web/static/static.go
diff --git a/static/toolbar.js b/web/static/toolbar.js
similarity index 100%
rename from static/toolbar.js
rename to web/static/toolbar.js
diff --git a/static/view.js b/web/static/view.js
similarity index 100%
rename from static/view.js
rename to web/static/view.js
diff --git a/admin/view_delete_user.html b/web/views/admin-delete-user.html
similarity index 100%
rename from admin/view_delete_user.html
rename to web/views/admin-delete-user.html
diff --git a/admin/view_edit_user.html b/web/views/admin-edit-user.html
similarity index 100%
rename from admin/view_edit_user.html
rename to web/views/admin-edit-user.html
diff --git a/admin/view_new_user.html b/web/views/admin-new-user.html
similarity index 100%
rename from admin/view_new_user.html
rename to web/views/admin-new-user.html
diff --git a/admin/view_panel.html b/web/views/admin-panel.html
similarity index 93%
rename from admin/view_panel.html
rename to web/views/admin-panel.html
index a850107..fdd99d4 100644
--- a/admin/view_panel.html
+++ b/web/views/admin-panel.html
@@ -11,6 +11,7 @@
{{block "panel link user list" .}}User list{{end}}
{{block "panel users" .}}Manage users{{end}}
{{block "panel interwiki" .}}Interwiki{{end}}
+ {{block "panel/orphans" .}}Orphaned hyphae{{end}}
diff --git a/admin/view_user_list.html b/web/views/admin-user-list.html
similarity index 100%
rename from admin/view_user_list.html
rename to web/views/admin-user-list.html
diff --git a/web/views/auth-lock.html b/web/views/auth-lock.html
new file mode 100644
index 0000000..0784400
--- /dev/null
+++ b/web/views/auth-lock.html
@@ -0,0 +1,33 @@
+{{define "title"}}{{block "lock title" .}}Locked{{end}}{{end}}
+{{define "page"}}
+
+
+
+
+
+ 🔒 {{template "lock title" .}}
+
+
+
+
+
+
+ 🔒
+ {{template "lock title" .}}
+
+ {{template "telegram widget"}}
+
+
+
+
+{{end}}
\ No newline at end of file
diff --git a/web/views/auth-login.html b/web/views/auth-login.html
new file mode 100644
index 0000000..73ecb8a
--- /dev/null
+++ b/web/views/auth-login.html
@@ -0,0 +1,39 @@
+{{define "log in to x"}}Log in to {{.}}{{end}}
+{{define "title"}}{{template "log in to x" .WikiName}}{{end}}
+{{define "body"}}
+
+
+ {{if .UseAuth}}
+ {{if .ErrUnknownUsername}}
+ {{block "error username" .}}Unknown username.{{end}}
+ {{else if .ErrWrongPassword}}
+ {{block "error password" .}}Wrong password.{{end}}
+ {{else if .ErrTelegram}}
+ {{block "error telegram" .}}Could not authorize using Telegram.{{end}}
+ {{else if .Err}}
+ {{.Err}}
+ {{end}}
+
+
+ {{template "telegram widget" .}}
+ {{else}}
+ {{block "auth disabled" .}}Authentication is disabled. You can make edits anonymously.{{end}}
+ ← {{block "go home" .}}Go home{{end}}
+ {{end}}
+
+
+{{end}}
\ No newline at end of file
diff --git a/web/views/auth-logout.html b/web/views/auth-logout.html
new file mode 100644
index 0000000..7a40f6c
--- /dev/null
+++ b/web/views/auth-logout.html
@@ -0,0 +1,18 @@
+{{define "title"}}{{end}}
+{{define "body"}}
+
+
+
+{{end}}
\ No newline at end of file
diff --git a/web/views/auth-register.html b/web/views/auth-register.html
new file mode 100644
index 0000000..b2c44fa
--- /dev/null
+++ b/web/views/auth-register.html
@@ -0,0 +1,35 @@
+{{define "register on x"}}Register on {{.}}{{end}}
+{{define "title"}}{{template "register on x" .WikiName}}{{end}}
+{{define "body"}}
+
+
+
+{{end}}
\ No newline at end of file
diff --git a/web/views/auth-telegram.html b/web/views/auth-telegram.html
new file mode 100644
index 0000000..432f5f7
--- /dev/null
+++ b/web/views/auth-telegram.html
@@ -0,0 +1,11 @@
+{{define "telegram widget"}}
+ {{if .TelegramEnabled}}
+ {{block "telegram tip" .}}You can log in using Telegram. It only works if you have set your @username in Telegram and this username is free on this wiki.{{end}}
+
+ {{end}}
+{{end}}
diff --git a/backlinks/view_backlinks.html b/web/views/backlinks.html
similarity index 100%
rename from backlinks/view_backlinks.html
rename to web/views/backlinks.html
diff --git a/web/views/cat-edit.html b/web/views/cat-edit.html
new file mode 100644
index 0000000..6fd61bb
--- /dev/null
+++ b/web/views/cat-edit.html
@@ -0,0 +1,37 @@
+{{define "edit category x"}}Edit category {{beautifulName .}}{{end}}
+{{define "title"}}{{template "edit category x" .CatName}}{{end}}
+{{define "body"}}
+
+ {{block "edit category heading" .CatName}}Edit category {{beautifulName .}} {{end}}
+ {{if len .Hyphae | not}}
+ {{block "empty cat" .}}This category is empty{{end}}
+ {{end}}
+
+ {{if .GivenPermissionToModify}}
+ {{block "add to category title" .}}Add a hypha to the category{{end}}
+
+
+ {{if len .Hyphae}}
+ {{block "remove hyphae" .}}Remove hyphae from the category{{end}}
+
+ {{end}}{{end}}
+
+{{end}}
diff --git a/categories/view_list.html b/web/views/cat-list.html
similarity index 100%
rename from categories/view_list.html
rename to web/views/cat-list.html
diff --git a/categories/view_page.html b/web/views/cat-page.html
similarity index 100%
rename from categories/view_page.html
rename to web/views/cat-page.html
diff --git a/settings/view_change_password.html b/web/views/change-password.html
similarity index 100%
rename from settings/view_change_password.html
rename to web/views/change-password.html
diff --git a/hypview/view_delete.html b/web/views/hypha-delete.html
similarity index 100%
rename from hypview/view_delete.html
rename to web/views/hypha-delete.html
diff --git a/hypview/view_edit.html b/web/views/hypha-edit.html
similarity index 100%
rename from hypview/view_edit.html
rename to web/views/hypha-edit.html
diff --git a/web/views/hypha-media.html b/web/views/hypha-media.html
new file mode 100644
index 0000000..c3c4d49
--- /dev/null
+++ b/web/views/hypha-media.html
@@ -0,0 +1,57 @@
+{{define "title"}}{{end}}
+{{define "body"}}
+
+ {{block "media title" .}}Media of {{.HyphaName | beautifulLink }}{{end}}
+
+ {{if .IsMediaHypha}}
+ {{block "tip" .}}You can manage the hypha's media on this page.{{end}}
+ {{else}}
+ {{block "empty" .}}This hypha has no media, you can upload it here.{{end}}
+ {{end}}
+
+ {{block "what is media?" .}}What is media?{{end}}
+
+
+
+
+ {{if .IsMediaHypha}}
+
+ {{end}}
+
+ {{if .U.CanProceed "upload-binary" }}
+
+ {{end}}
+
+ {{if .IsMediaHypha | and (.U.CanProceed "remove-media")}}
+
+ {{end}}
+
+
+{{end}}
\ No newline at end of file
diff --git a/web/views/hypha-revision.html b/web/views/hypha-revision.html
new file mode 100644
index 0000000..d17bbee
--- /dev/null
+++ b/web/views/hypha-revision.html
@@ -0,0 +1,18 @@
+{{define "hypha at rev"}}{{.HyphaName}} at {{.RevHash}}{{end}}
+{{define "title"}}{{template "hypha at rev" .}}{{end}}
+{{define "body"}}
+
+
+
+ {{range .ViewScripts}}
+ {{end}}
+{{end}}
\ No newline at end of file
diff --git a/web/views/hypha.html b/web/views/hypha.html
new file mode 100644
index 0000000..3c2188d
--- /dev/null
+++ b/web/views/hypha.html
@@ -0,0 +1,160 @@
+{{define "title"}}{{.HyphaName | beautifulName}}{{end}}
+
+{{define "body"}}
+
+
+ {{if .Meta.U.CanProceed "edit"}}
+
+ {{end}}
+
+ {{if .IsMyProfile}}
+
+ {{if eq .Meta.U.Group "admin"}}
+
+ {{end}}
+ {{end}}
+
+ {{.NaviTitle}}
+
+ {{if .Contents}}{{.Contents}}{{else}}{{template "empty hypha card" .}}{{end}}
+
+
+
+
+ {{ if .SubhyphaeHTML }}
+
+ {{block "subhyphae" .}}Subhyphae{{end}}
+
+
+
+
+ {{end}}
+
+
+
+ {{template "category card" .}}
+ {{range .ViewScripts}}{{end}}
+{{end}}
+
+{{define "category card"}}
+ {{if or .GivenPermissionToModify (len .Categories)}}
+ {{$hyphaName := .HyphaName}}
+ {{$givenPermission := .GivenPermissionToModify}}
+
+ {{block `categories` .}}Categories{{end}}
+
+ {{range .Categories}}
+
+ {{beautifulName .}}
+
+
+ {{end}}
+ {{if .GivenPermissionToModify}}
+
+
+
+ {{end}}
+
+
+ {{end}}
+{{end}}
+
+
+{{define "empty hypha card"}}
+
+ {{block "empty heading" .}}This hypha does not exist{{end}}
+ {{if and .UseAuth (eq .Meta.U.Group "anon")}}
+ {{block "empty no rights" .}}You are not authorized to create new hyphae. Here is what you can do:{{end}}
+
+ {{else}}
+
+
+ 📝 {{block "write a text" .}}Write a text{{end}}
+ {{block "write a text tip" .}}Write a note, a diary, an article, a story or anything textual using Mycomarkup . Full history of edits to the document will be saved.{{end}}
+ {{block "write a text writing conventions" .}}Make sure to follow this wiki's writing conventions if there are any.{{end}}
+ {{block "write a text btn" .}}Create{{end}}
+
+
+
+ 🖼 {{block "upload a media" .}}Upload a media{{end}}
+ {{block "upload a media tip" .}}Upload a picture, a video or an audio. Most common formats can be viewed from the browser, others can only be downloaded and viewed locally. You can write a description for the media later.{{end}}
+
+
+
+ {{end}}
+
+{{end}}
\ No newline at end of file
diff --git a/backlinks/view_orphans.html b/web/views/orphans.html
similarity index 100%
rename from backlinks/view_orphans.html
rename to web/views/orphans.html
diff --git a/web/views/user-list.html b/web/views/user-list.html
new file mode 100644
index 0000000..1ae4984
--- /dev/null
+++ b/web/views/user-list.html
@@ -0,0 +1,30 @@
+{{define "title"}}List of users{{end}}
+{{define "body"}}
+
+ {{template "title"}}
+ {{$u := .UserHypha}}
+ {{block "administrators" .}}Administrators{{end}}
+
+ {{range .Admins}}{{.}} {{end}}
+
+
+
+ {{block "moderators" .}}Moderators{{end}}
+
+ {{range .Moderators}}{{.}} {{end}}
+
+
+
+ {{block "editors" .}}Editors{{end}}
+
+ {{range .Editors}}{{.}} {{end}}
+
+
+
+ {{block "readers" .}}Readers{{end}}
+
+ {{range .Readers}}{{.}} {{end}}
+
+
+
+ {{end}}
\ No newline at end of file
diff --git a/web/viewutil/base.html b/web/viewutil/base.html
new file mode 100644
index 0000000..48ac707
--- /dev/null
+++ b/web/viewutil/base.html
@@ -0,0 +1,56 @@
+{{define "confirm"}}Confirm{{end}}
+{{define "cancel"}}Cancel{{end}}
+{{define "save"}}Save{{end}}
+{{define "error"}}Error{{end}}
+{{define "delete"}}Delete{{end}}
+{{define "page"}}
+
+
+
+
+
+ {{block "title" .}}{{end}}
+
+
+ {{range .HeadElements}}{{.}}{{end}}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{block "auth" .}}{{end}}
+
+
+
+
+
+
+
+{{block "body" .}}{{end}}
+
+
+
+{{range .CommonScripts}}
+
+{{end}}
+
+
+{{end}}
diff --git a/viewutil/chain.go b/web/viewutil/chain.go
similarity index 100%
rename from viewutil/chain.go
rename to web/viewutil/chain.go
diff --git a/viewutil/err.go b/web/viewutil/err.go
similarity index 100%
rename from viewutil/err.go
rename to web/viewutil/err.go
diff --git a/viewutil/meta.go b/web/viewutil/meta.go
similarity index 70%
rename from viewutil/meta.go
rename to web/viewutil/meta.go
index d69b35f..d47dc02 100644
--- a/viewutil/meta.go
+++ b/web/viewutil/meta.go
@@ -1,8 +1,9 @@
package viewutil
import (
+ "github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/l18n"
- "github.com/bouncepaw/mycorrhiza/user"
+ "html/template"
"io"
"net/http"
)
@@ -13,6 +14,10 @@ type Meta struct {
U *user.User
W io.Writer
Addr string
+
+ // New template additions
+ HeadElements []template.HTML
+ BodyAttributes map[string]string
}
// MetaFrom makes a Meta from the given data. You are meant to further modify it.
@@ -28,3 +33,7 @@ func MetaFrom(w http.ResponseWriter, rq *http.Request) Meta {
func (m Meta) Locale() string {
return m.Lc.Locale
}
+
+func (m Meta) LocaleIsRussian() bool {
+ return m.Locale() == "ru"
+}
diff --git a/viewutil/viewutil.go b/web/viewutil/viewutil.go
similarity index 99%
rename from viewutil/viewutil.go
rename to web/viewutil/viewutil.go
index 25de03b..f49de2d 100644
--- a/viewutil/viewutil.go
+++ b/web/viewutil/viewutil.go
@@ -4,12 +4,12 @@ package viewutil
import (
"embed"
"fmt"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
"io/fs"
"log"
"strings"
"text/template" // TODO: save the world
- "github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/util"
)
diff --git a/web/web.go b/web/web.go
index 3ebfc14..01ba4e6 100644
--- a/web/web.go
+++ b/web/web.go
@@ -2,25 +2,27 @@
package web
import (
+ "errors"
+ "fmt"
+ "github.com/bouncepaw/mycorrhiza/internal/cfg"
+ "github.com/bouncepaw/mycorrhiza/internal/user"
+ "github.com/bouncepaw/mycorrhiza/l18n"
+ "github.com/bouncepaw/mycorrhiza/web/viewutil"
"io"
+ "log"
+ "log/slog"
+ "mime"
"net/http"
"net/url"
+ "strings"
- "github.com/bouncepaw/mycorrhiza/admin"
- "github.com/bouncepaw/mycorrhiza/settings"
- "github.com/bouncepaw/mycorrhiza/auth"
- "github.com/bouncepaw/mycorrhiza/backlinks"
- "github.com/bouncepaw/mycorrhiza/categories"
"github.com/bouncepaw/mycorrhiza/help"
"github.com/bouncepaw/mycorrhiza/history/histweb"
"github.com/bouncepaw/mycorrhiza/hypview"
"github.com/bouncepaw/mycorrhiza/interwiki"
"github.com/bouncepaw/mycorrhiza/misc"
-
"github.com/gorilla/mux"
- "github.com/bouncepaw/mycorrhiza/cfg"
- "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
)
@@ -40,11 +42,25 @@ func Handler() http.Handler {
// Public routes. They're always accessible regardless of the user status.
misc.InitAssetHandlers(router)
- auth.InitAuth(router)
+
+ // Auth
+ router.HandleFunc("/user-list", handlerUserList)
+ router.HandleFunc("/lock", handlerLock)
+ // The check below saves a lot of extra checks and lines of codes in other places in this file.
+ if cfg.UseAuth {
+ if cfg.AllowRegistration {
+ router.HandleFunc("/register", handlerRegister).Methods(http.MethodPost, http.MethodGet)
+ }
+ if cfg.TelegramEnabled {
+ router.HandleFunc("/telegram-login", handlerTelegramLogin)
+ }
+ router.HandleFunc("/login", handlerLogin)
+ router.HandleFunc("/logout", handlerLogout)
+ }
// Wiki routes. They may be locked or restricted.
- wikiRouter := router.PathPrefix("").Subrouter()
- wikiRouter.Use(func(next http.Handler) http.Handler {
+ r := router.PathPrefix("").Subrouter()
+ r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, rq *http.Request) {
user := user.FromRequest(rq)
if !user.ShowLockMaybe(w, rq) {
@@ -53,36 +69,52 @@ func Handler() http.Handler {
})
})
- initReaders(wikiRouter)
- initMutators(wikiRouter)
- help.InitHandlers(wikiRouter)
- backlinks.InitHandlers(wikiRouter)
- categories.InitHandlers(wikiRouter)
- misc.InitHandlers(wikiRouter)
+ initReaders(r)
+ initMutators(r)
+ help.InitHandlers(r)
+ misc.InitHandlers(r)
hypview.Init()
- histweb.InitHandlers(wikiRouter)
- interwiki.InitHandlers(wikiRouter)
+ histweb.InitHandlers(r)
+ interwiki.InitHandlers(r)
- // Admin routes.
+ r.PathPrefix("/add-to-category").HandlerFunc(handlerAddToCategory).Methods("POST")
+ r.PathPrefix("/remove-from-category").HandlerFunc(handlerRemoveFromCategory).Methods("POST")
+ r.PathPrefix("/category/").HandlerFunc(handlerCategory).Methods("GET")
+ r.PathPrefix("/edit-category/").HandlerFunc(handlerEditCategory).Methods("GET")
+ r.PathPrefix("/category").HandlerFunc(handlerListCategory).Methods("GET")
+
+ // Admin routes
if cfg.UseAuth {
- adminRouter := wikiRouter.PathPrefix("/admin").Subrouter()
+ adminRouter := r.PathPrefix("/admin").Subrouter()
adminRouter.Use(groupMiddleware("admin"))
- admin.Init(adminRouter)
- settingsRouter := wikiRouter.PathPrefix("/settings").Subrouter()
+ adminRouter.HandleFunc("/shutdown", handlerAdminShutdown).Methods(http.MethodPost)
+ adminRouter.HandleFunc("/reindex-users", handlerAdminReindexUsers).Methods(http.MethodPost)
+
+ adminRouter.HandleFunc("/new-user", handlerAdminUserNew).Methods(http.MethodGet, http.MethodPost)
+ adminRouter.HandleFunc("/users/{username}/edit", handlerAdminUserEdit).Methods(http.MethodGet, http.MethodPost)
+ adminRouter.HandleFunc("/users/{username}/change-password", handlerAdminUserChangePassword).Methods(http.MethodPost)
+ adminRouter.HandleFunc("/users/{username}/delete", handlerAdminUserDelete).Methods(http.MethodGet, http.MethodPost)
+ adminRouter.HandleFunc("/users", handlerAdminUsers)
+
+ adminRouter.HandleFunc("/", handlerAdmin)
+
+ settingsRouter := r.PathPrefix("/settings").Subrouter()
// TODO: check if necessary?
//settingsRouter.Use(groupMiddleware("settings"))
- settings.Init(settingsRouter)
+ settingsRouter.HandleFunc("/change-password", handlerUserChangePassword).Methods(http.MethodGet, http.MethodPost)
}
// Index page
- wikiRouter.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
+ r.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
// Let's pray it never fails
addr, _ := url.Parse("/hypha/" + cfg.HomeHypha)
rq.URL = addr
handlerHypha(w, rq)
})
+ initPages()
+
return router
}
@@ -102,3 +134,198 @@ func groupMiddleware(group string) func(http.Handler) http.Handler {
})
}
}
+
+// Auth
+func handlerUserList(w http.ResponseWriter, rq *http.Request) {
+ admins, moderators, editors, readers := user.UsersInGroups()
+ _ = pageUserList.RenderTo(viewutil.MetaFrom(w, rq),
+ map[string]any{
+ "Admins": admins,
+ "Moderators": moderators,
+ "Editors": editors,
+ "Readers": readers,
+ })
+}
+
+func handlerLock(w http.ResponseWriter, rq *http.Request) {
+ _ = pageAuthLock.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{})
+}
+
+// handlerRegister displays the register form (GET) or registers the user (POST).
+func handlerRegister(w http.ResponseWriter, rq *http.Request) {
+ util.PrepareRq(rq)
+ if rq.Method == http.MethodGet {
+ slog.Info("Showing registration form")
+ _ = pageAuthRegister.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
+ "UseAuth": cfg.UseAuth,
+ "AllowRegistration": cfg.AllowRegistration,
+ "RawQuery": rq.URL.RawQuery,
+ "WikiName": cfg.WikiName,
+ })
+ return
+ }
+
+ var (
+ username = rq.PostFormValue("username")
+ password = rq.PostFormValue("password")
+ err = user.Register(username, password, "editor", "local", false)
+ )
+ if err != nil {
+ slog.Info("Failed to register", "username", username, "err", err.Error())
+ w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
+ w.WriteHeader(http.StatusBadRequest)
+ _ = pageAuthRegister.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
+ "UseAuth": cfg.UseAuth,
+ "AllowRegistration": cfg.AllowRegistration,
+ "RawQuery": rq.URL.RawQuery,
+ "WikiName": cfg.WikiName,
+
+ "Err": err,
+ "Username": username,
+ "Password": password,
+ })
+ return
+ }
+
+ slog.Info("Registered user", "username", username)
+ if err := user.LoginDataHTTP(w, username, password); err != nil {
+ return
+ }
+ http.Redirect(w, rq, "/"+rq.URL.RawQuery, http.StatusSeeOther)
+}
+
+// handlerLogout shows the logout form (GET) or logs the user out (POST).
+func handlerLogout(w http.ResponseWriter, rq *http.Request) {
+ if rq.Method == http.MethodPost {
+ slog.Info("Somebody logged out")
+ user.LogoutFromRequest(w, rq)
+ http.Redirect(w, rq, "/", http.StatusSeeOther)
+ return
+ }
+
+ var (
+ u = user.FromRequest(rq)
+ can = u != nil
+ )
+ w.Header().Set("Content-Type", "text/html;charset=utf-8")
+ if can {
+ slog.Info("Logging out", "username", u.Name)
+ w.WriteHeader(http.StatusOK)
+ } else {
+ slog.Info("Unknown user logging out")
+ w.WriteHeader(http.StatusForbidden)
+ }
+ _ = pageAuthLogout.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
+ "CanLogout": can,
+ })
+}
+
+// handlerLogin shows the login form (GET) or logs the user in (POST).
+func handlerLogin(w http.ResponseWriter, rq *http.Request) {
+ if rq.Method == http.MethodGet {
+ w.WriteHeader(http.StatusOK)
+ _ = pageAuthLogin.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
+ "UseAuth": cfg.UseAuth,
+ "ErrUnknownUsername": false,
+ "ErrWrongPassword": false,
+ "ErrTelegram": false,
+ "Err": nil,
+ "WikiName": cfg.WikiName,
+ })
+ slog.Info("Somebody logging in")
+ return
+ }
+
+ var (
+ username = util.CanonicalName(rq.PostFormValue("username"))
+ password = rq.PostFormValue("password")
+ err = user.LoginDataHTTP(w, username, password)
+ )
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ _ = pageAuthLogin.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
+ "UseAuth": cfg.UseAuth,
+ "ErrUnknownUsername": errors.Is(err, user.ErrUnknownUsername),
+ "ErrWrongPassword": errors.Is(err, user.ErrWrongPassword),
+ "ErrTelegram": false, // TODO: ?
+ "Err": err.Error(),
+ "WikiName": cfg.WikiName,
+ "Username": username,
+ })
+ slog.Info("Failed to log in", "username", username, "err", err.Error())
+ return
+ }
+ http.Redirect(w, rq, "/", http.StatusSeeOther)
+ slog.Info("Logged in", "username", username)
+}
+
+func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) {
+ // Note there is no lock here.
+ lc := l18n.FromRequest(rq)
+ w.Header().Set("Content-Type", "text/html;charset=utf-8")
+ _ = rq.ParseForm()
+ var (
+ values = rq.URL.Query()
+ username = strings.ToLower(values.Get("username"))
+ seemsValid = user.TelegramAuthParamsAreValid(values)
+ err = user.Register(
+ username,
+ "", // Password matters not
+ "editor",
+ "telegram",
+ false,
+ )
+ )
+ // If registering a user via Telegram failed, because a Telegram user with this name
+ // has already registered, then everything is actually ok!
+ if user.HasUsername(username) && user.ByName(username).Source == "telegram" {
+ err = nil
+ }
+
+ if !seemsValid {
+ err = errors.New("Wrong parameters")
+ }
+
+ if err != nil {
+ slog.Info("Failed to register", "username", username, "err", err.Error(), "method", "telegram")
+ w.WriteHeader(http.StatusBadRequest)
+ _, _ = io.WriteString(
+ w,
+ viewutil.Base(
+ viewutil.MetaFrom(w, rq),
+ lc.Get("ui.error"),
+ fmt.Sprintf(
+ `%s
%s
%s
`,
+ lc.Get("auth.error_telegram"),
+ err.Error(),
+ lc.Get("auth.go_login"),
+ ),
+ map[string]string{},
+ ),
+ )
+ return
+ }
+
+ errmsg := user.LoginDataHTTP(w, username, "")
+ if errmsg != nil {
+ log.Printf("Failed to login ‘%s’ using Telegram: %s", username, err.Error())
+ w.WriteHeader(http.StatusBadRequest)
+ _, _ = io.WriteString(
+ w,
+ viewutil.Base(
+ viewutil.MetaFrom(w, rq),
+ "Error",
+ fmt.Sprintf(
+ `%s
%s
%s
`,
+ lc.Get("auth.error_telegram"),
+ err.Error(),
+ lc.Get("auth.go_login"),
+ ),
+ map[string]string{},
+ ),
+ )
+ return
+ }
+ http.Redirect(w, rq, "/", http.StatusSeeOther)
+ slog.Info("Logged in", "username", username, "method", "telegram")
+}