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) -%} -
-
- {% if cfg.AllowRegistration %} - - {%= telegramWidget(lc) %} - {% elseif cfg.UseAuth %} -

{%s lc.Get("auth.noregister") %}

-

← {%s lc.Get("auth.go_back") %}

- {% else %} -

{%s lc.Get("auth.noauth") %}

-

← {%s lc.Get("auth.go_back") %}

- {% endif %} -
-
-{% endfunc %} - -{% func Login(lc *l18n.Localizer) %} -
-
- {% if cfg.UseAuth %} - - {%= telegramWidget(lc) %} - {% else %} -

{%s lc.Get("auth.noauth") %}

-

← {%s lc.Get("auth.go_home") %}

- {% endif %} -
-
-{% 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) %} -
-
- {% if can %} -

{%s lc.Get("auth.logout_header") %}

-
- - {%s lc.Get("auth.go_home") %} -
- {% else %} -

{%s lc.Get("auth.logout_anon") %}

-

{%s lc.Get("auth.login_title") %}

-

← {%s lc.Get("auth.go_home") %}

- {% endif %} -
-
-{% 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 %} -
  1. {%s name %}
  2. - {% endfor %}
-
-
-

{%s get("moderators") %}

-
    {% for _, name := range moderators %} -
  1. {%s name %}
  2. - {% endfor %}
-
-
-

{%s get("editors") %}

-
    {% for _, name := range editors %} -
  1. {%s name %}
  2. - {% endfor %}
-
-
-

{%s get("readers") %}

-
    {% for _, name := range readers %} -
  1. {%s name %}
  2. - {% 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:13 - if cfg.AllowRegistration { -//line auth/auth.qtpl:13 - qw422016.N().S(` - - `) -//line auth/auth.qtpl:31 - streamtelegramWidget(qw422016, lc) -//line auth/auth.qtpl:31 - qw422016.N().S(` - `) -//line auth/auth.qtpl:32 - } else if cfg.UseAuth { -//line auth/auth.qtpl:32 - qw422016.N().S(` -

`) -//line auth/auth.qtpl:33 - qw422016.E().S(lc.Get("auth.noregister")) -//line auth/auth.qtpl:33 - qw422016.N().S(`

-

← `) -//line auth/auth.qtpl:34 - qw422016.E().S(lc.Get("auth.go_back")) -//line auth/auth.qtpl:34 - qw422016.N().S(`

- `) -//line auth/auth.qtpl:35 - } else { -//line auth/auth.qtpl:35 - qw422016.N().S(` -

`) -//line auth/auth.qtpl:36 - qw422016.E().S(lc.Get("auth.noauth")) -//line auth/auth.qtpl:36 - qw422016.N().S(`

-

← `) -//line auth/auth.qtpl:37 - qw422016.E().S(lc.Get("auth.go_back")) -//line auth/auth.qtpl:37 - qw422016.N().S(`

- `) -//line auth/auth.qtpl:38 - } -//line auth/auth.qtpl:38 - 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:98 - if can { -//line auth/auth.qtpl:98 - qw422016.N().S(` -

`) -//line auth/auth.qtpl:99 - qw422016.E().S(lc.Get("auth.logout_header")) -//line auth/auth.qtpl:99 - qw422016.N().S(`

-
- - `) -//line auth/auth.qtpl:102 - qw422016.E().S(lc.Get("auth.go_home")) -//line auth/auth.qtpl:102 - qw422016.N().S(` -
- `) -//line auth/auth.qtpl:104 - } else { -//line auth/auth.qtpl:104 - qw422016.N().S(` -

`) -//line auth/auth.qtpl:105 - qw422016.E().S(lc.Get("auth.logout_anon")) -//line auth/auth.qtpl:105 - qw422016.N().S(`

-

`) -//line auth/auth.qtpl:106 - qw422016.E().S(lc.Get("auth.login_title")) -//line auth/auth.qtpl:106 - qw422016.N().S(`

-

← `) -//line auth/auth.qtpl:107 - qw422016.E().S(lc.Get("auth.go_home")) -//line auth/auth.qtpl:107 - qw422016.N().S(`

- `) -//line auth/auth.qtpl:108 - } -//line auth/auth.qtpl:108 - 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:189 - qw422016.E().S(get("administrators")) -//line auth/auth.qtpl:189 - qw422016.N().S(`

-
    `) -//line auth/auth.qtpl:190 - for _, name := range admins { -//line auth/auth.qtpl:190 - qw422016.N().S(` -
  1. `) -//line auth/auth.qtpl:191 - qw422016.E().S(name) -//line auth/auth.qtpl:191 - qw422016.N().S(`
  2. - `) -//line auth/auth.qtpl:192 - } -//line auth/auth.qtpl:192 - qw422016.N().S(`
-
-
-

`) -//line auth/auth.qtpl:195 - qw422016.E().S(get("moderators")) -//line auth/auth.qtpl:195 - qw422016.N().S(`

-
    `) -//line auth/auth.qtpl:196 - for _, name := range moderators { -//line auth/auth.qtpl:196 - qw422016.N().S(` -
  1. `) -//line auth/auth.qtpl:197 - qw422016.E().S(name) -//line auth/auth.qtpl:197 - qw422016.N().S(`
  2. - `) -//line auth/auth.qtpl:198 - } -//line auth/auth.qtpl:198 - qw422016.N().S(`
-
-
-

`) -//line auth/auth.qtpl:201 - qw422016.E().S(get("editors")) -//line auth/auth.qtpl:201 - qw422016.N().S(`

-
    `) -//line auth/auth.qtpl:202 - for _, name := range editors { -//line auth/auth.qtpl:202 - qw422016.N().S(` -
  1. `) -//line auth/auth.qtpl:203 - qw422016.E().S(name) -//line auth/auth.qtpl:203 - qw422016.N().S(`
  2. - `) -//line auth/auth.qtpl:204 - } -//line auth/auth.qtpl:204 - qw422016.N().S(`
-
-
-

`) -//line auth/auth.qtpl:207 - qw422016.E().S(get("readers")) -//line auth/auth.qtpl:207 - qw422016.N().S(`

-
    `) -//line auth/auth.qtpl:208 - for _, name := range readers { -//line auth/auth.qtpl:208 - qw422016.N().S(` -
  1. `) -//line auth/auth.qtpl:209 - qw422016.E().S(name) -//line auth/auth.qtpl:209 - qw422016.N().S(`
  2. - `) -//line auth/auth.qtpl:210 - } -//line auth/auth.qtpl:210 - 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}} - -{{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}}

-
-
    - {{range .Hyphae}} -
  1. - - -
  2. - {{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 {% endfor %} {% endfunc %} - -{% func (rev *Revision) asHistoryEntry(hyphaName string) %} -
  • - - - - {%s rev.Hash %} - {%s rev.Message %} - {% if rev.Username != "anon" %} - by - {% 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: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: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: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: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()) -%} - -{% 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: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 %} -
    - {%s lc.Get("ui.media_stat") %} - -

    {%s lc.Get("ui.media_stat_mime") %} {%s mime %}

    -
    - {% endif %} - - {% if strings.HasPrefix(mime, "image/") %} -
    - {%s lc.Get("ui.media_include") %} - -
    img { {%s h.CanonicalName() %} }
    -
    - {% 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 prevHyphaName != "" %} - - {% endif %} - {% if nextHyphaName != "" %} - - {% endif %} -
    -{% 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) %} -
    -
    -

    {%s meta.Lc.Get("ui.revision_warning") %} {%s meta.Lc.Get("ui.revision_link") %}

    - {%s= NaviTitle(meta, h.CanonicalName()) %} - {%s= contents %} -
    -
    -{%= 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:37 - qw422016.E().S(lc.Get("ui.media_stat")) -//line hypview/readers.qtpl:37 - qw422016.N().S(` - -

    `) -//line hypview/readers.qtpl:39 - qw422016.E().S(lc.Get("ui.media_stat_mime")) -//line hypview/readers.qtpl:39 - qw422016.N().S(` `) -//line hypview/readers.qtpl:39 - qw422016.E().S(mime) -//line hypview/readers.qtpl:39 - 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:45 - qw422016.E().S(lc.Get("ui.media_include")) -//line hypview/readers.qtpl:45 - qw422016.N().S(` - -
    img { `)
    -//line hypview/readers.qtpl:47
    -			qw422016.E().S(h.CanonicalName())
    -//line hypview/readers.qtpl:47
    -			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:121 - if prevHyphaName != "" { -//line hypview/readers.qtpl:121 - qw422016.N().S(` - - `) -//line hypview/readers.qtpl:123 - } -//line hypview/readers.qtpl:123 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:124 - if nextHyphaName != "" { -//line hypview/readers.qtpl:124 - qw422016.N().S(` - - `) -//line hypview/readers.qtpl:126 - } -//line hypview/readers.qtpl:126 - 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: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:149 - qw422016.E().S(meta.Lc.Get("ui.revision_warning")) -//line hypview/readers.qtpl:149 - qw422016.N().S(` `) -//line hypview/readers.qtpl:149 - qw422016.E().S(meta.Lc.Get("ui.revision_link")) -//line hypview/readers.qtpl:149 - qw422016.N().S(`

    - `) -//line hypview/readers.qtpl:150 - qw422016.N().S(NaviTitle(meta, h.CanonicalName())) -//line hypview/readers.qtpl:150 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:151 - qw422016.N().S(contents) -//line hypview/readers.qtpl:151 - 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") + } + _, _ = 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 %} - -{% 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: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"}} + +{{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"}} +
    +
    + {{if .CanLogout}} +

    {{block "log out?" .}}Log out?{{end}}

    +
    + +

    ← {{block "go home" .}}Go home{{end}}

    +
    + {{else}} +

    {{block "cannot log out anon" .}}You cannot log out because you are not logged in.{{end}}

    +

    {{block "log in" .}}Log in{{end}}

    +

    ← {{template "go home"}}

    + {{end}} +
    +
    +{{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"}} +
    +
    + {{if .AllowRegistration}} + + {{template "telegram widget" .}} + {{else if .UseAuth}} +

    {%s lc.Get("auth.noregister") %}

    +

    ← {%s lc.Get("auth.go_back") %}

    + {{else}} +

    {%s lc.Get("auth.noauth") %}

    +

    ← {%s lc.Get("auth.go_back") %}

    + {{end}} +
    +
    +{{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}}

    +
    +
      + {{range .Hyphae}} +
    1. + + +
    2. + {{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}} +
    + {{block "stat" .}}Stat{{end}} +

    {{block "stat size" .}}File size:{{end}} {{.FileSize}}

    +

    {{block "stat mime" .}}MIME type:{{end}} {{.MimeType}}

    +
    + {{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"}} +
    +
    +

    + {{block "revision warning" .}}Please note that viewing media is not supported in history for now.{{end}} + + {{block "revision link" .}}Get Mycomarkup source of this revision{{end}} + +

    + {{.NaviTitle}} + {{.Contents}} +
    +
    + {{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 .PrevHyphaName}} + + {{end}} + {{if .NextHyphaName}} + + {{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}} + + {{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}}
    1. {{.}}
    2. {{end}} +
    +
    +
    +

    {{block "moderators" .}}Moderators{{end}}

    +
      + {{range .Moderators}}
    1. {{.}}
    2. {{end}} +
    +
    +
    +

    {{block "editors" .}}Editors{{end}}

    +
      + {{range .Editors}}
    1. {{.}}
    2. {{end}} +
    +
    +
    +

    {{block "readers" .}}Readers{{end}}

    +
      + {{range .Readers}}
    1. {{.}}
    2. {{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 "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") +}