Compare commits
No commits in common. "master" and "v1.15.0" have entirely different histories.
1
.gitignore
vendored
@ -1,2 +1 @@
|
|||||||
mycorrhiza
|
mycorrhiza
|
||||||
.idea
|
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**Mycorrhiza Wiki** is a lightweight file-system wiki engine that uses Git for keeping history. [Main wiki](https://mycorrhiza.wiki)
|
**Mycorrhiza Wiki** is a lightweight file-system wiki engine that uses Git for keeping history. [Main wiki](https://mycorrhiza.wiki)
|
||||||
|
|
||||||
<img src="https://mycorrhiza.wiki/binary/release/1.15.1/screenshot" alt="A screenshot of mycorrhiza.wiki's home page in the Safari browser" width="700">
|
<img src="https://mycorrhiza.wiki/binary/release/1.14/screenshot" alt="A screenshot of mycorrhiza.wiki's home page in the Safari browser" width="700">
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@ -30,7 +30,7 @@ Compare Mycorrhiza Wiki with other engines on [WikiMatrix](https://www.wikimatri
|
|||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|
||||||
See [the deployment guide](https://mycorrhiza.wiki/hypha/deployment) on the wiki. Also, Mycorrhiza might be available in your repositories. See [Repology](https://repology.org/project/mycorrhiza/versions).
|
See [the deployment guide](https://mycorrhiza.wiki/hypha/deployment) on the wiki. Also, Mycorrhiza might be available in your repositories.
|
||||||
|
|
||||||
|
|
||||||
## Contributing and community
|
## Contributing and community
|
||||||
@ -46,5 +46,3 @@ If you want to contribute with code, open a pull request on GitHub or send a pat
|
|||||||
If you want to report an issue, open an issue on GitHub or contact us directly.
|
If you want to report an issue, open an issue on GitHub or contact us directly.
|
||||||
|
|
||||||
Consider supporting the development on [Boosty](https://boosty.to/bouncepaw).
|
Consider supporting the development on [Boosty](https://boosty.to/bouncepaw).
|
||||||
|
|
||||||
Check out [Betula](https://betula.mycorrhiza.wiki) as well.
|
|
||||||
@ -1,110 +1,20 @@
|
|||||||
package web
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"github.com/bouncepaw/mycorrhiza/viewutil"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"log"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"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.
|
// handlerAdmin provides the admin panel.
|
||||||
func handlerAdmin(w http.ResponseWriter, rq *http.Request) {
|
func handlerAdmin(w http.ResponseWriter, rq *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||||
@ -115,7 +25,7 @@ func handlerAdmin(w http.ResponseWriter, rq *http.Request) {
|
|||||||
// handlerAdminShutdown kills the wiki.
|
// handlerAdminShutdown kills the wiki.
|
||||||
func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) {
|
func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) {
|
||||||
if user.CanProceed(rq, "admin/shutdown") {
|
if user.CanProceed(rq, "admin/shutdown") {
|
||||||
slog.Info("An admin commanded the wiki to shutdown")
|
log.Println("An admin commanded the wiki to shutdown")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,7 +72,7 @@ func handlerAdminUserEdit(w http.ResponseWriter, rq *http.Request) {
|
|||||||
u.Group = newGroup
|
u.Group = newGroup
|
||||||
if err := user.SaveUserDatabase(); err != nil {
|
if err := user.SaveUserDatabase(); err != nil {
|
||||||
u.Group = oldGroup
|
u.Group = oldGroup
|
||||||
slog.Info("Failed to save user database", "err", err)
|
log.Println(err)
|
||||||
f = f.WithError(err)
|
f = f.WithError(err)
|
||||||
} else {
|
} else {
|
||||||
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
|
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
|
||||||
@ -241,7 +151,7 @@ func handlerAdminUserDelete(w http.ResponseWriter, rq *http.Request) {
|
|||||||
if !f.HasError() {
|
if !f.HasError() {
|
||||||
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
|
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
|
||||||
} else {
|
} else {
|
||||||
slog.Info("Failed to delete user", "err", f.Error())
|
log.Println(f.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
126
admin/view.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -11,7 +11,6 @@
|
|||||||
<li><a href="/user-list">{{block "panel link user list" .}}User list{{end}}</a></li>
|
<li><a href="/user-list">{{block "panel link user list" .}}User list{{end}}</a></li>
|
||||||
<li><a href="/admin/users/">{{block "panel users" .}}Manage users{{end}}</a></li>
|
<li><a href="/admin/users/">{{block "panel users" .}}Manage users{{end}}</a></li>
|
||||||
<li><a href="/interwiki">{{block "panel interwiki" .}}Interwiki{{end}}</a></li>
|
<li><a href="/interwiki">{{block "panel interwiki" .}}Interwiki{{end}}</a></li>
|
||||||
<li><a href="/orphans">{{block "panel/orphans" .}}Orphaned hyphae{{end}}</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
213
auth/auth.qtpl
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
{% 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)
|
||||||
|
%}
|
||||||
|
<main class="main-width">
|
||||||
|
<section>
|
||||||
|
{% if cfg.AllowRegistration %}
|
||||||
|
<form class="modal" method="post" action="/register?{%s rq.URL.RawQuery %}" id="register-form" enctype="multipart/form-data" autocomplete="off">
|
||||||
|
<fieldset class="modal__fieldset">
|
||||||
|
<legend class="modal__title">{%s lc.Get("auth.register_header", &l18n.Replacements{"name": cfg.WikiName}) %}</legend>
|
||||||
|
|
||||||
|
<label for="register-form__username">{%s lc.Get("auth.username") %}</label>
|
||||||
|
<br>
|
||||||
|
<input type="text" required autofocus id="login-form__username" name="username">
|
||||||
|
<br>
|
||||||
|
<label for="login-form__password">{%s lc.Get("auth.password") %}</label>
|
||||||
|
<br>
|
||||||
|
<input type="password" required name="password">
|
||||||
|
<p>{%s lc.Get("auth.password_tip") %}</p>
|
||||||
|
<p>{%s lc.Get("auth.cookie_tip") %}</p>
|
||||||
|
<button class="btn" type="submit" value="Register">{%s lc.Get("auth.register_button") %}</button>
|
||||||
|
<a class="btn btn_weak" href="/{%s rq.URL.RawQuery %}">{%s lc.Get("ui.cancel") %}</a>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
{%= telegramWidget(lc) %}
|
||||||
|
{% elseif cfg.UseAuth %}
|
||||||
|
<p>{%s lc.Get("auth.noregister") %}</p>
|
||||||
|
<p><a href="/{%s rq.URL.RawQuery %}">← {%s lc.Get("auth.go_back") %}</a></p>
|
||||||
|
{% else %}
|
||||||
|
<p>{%s lc.Get("auth.noauth") %}</p>
|
||||||
|
<p><a href="/{%s rq.URL.RawQuery %}">← {%s lc.Get("auth.go_back") %}</a></p>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% func Login(lc *l18n.Localizer) %}
|
||||||
|
<main class="main-width">
|
||||||
|
<section>
|
||||||
|
{% if cfg.UseAuth %}
|
||||||
|
<form class="modal" method="post" action="/login" id="login-form" enctype="multipart/form-data" autocomplete="on">
|
||||||
|
<fieldset class="modal__fieldset">
|
||||||
|
<legend class="modal__title">{%s lc.Get("auth.login_header", &l18n.Replacements{"name": cfg.WikiName}) %}</legend>
|
||||||
|
<label for="login-form__username">{%s lc.Get("auth.username") %}</label>
|
||||||
|
<br>
|
||||||
|
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
|
||||||
|
<br>
|
||||||
|
<label for="login-form__password">{%s lc.Get("auth.password") %}</label>
|
||||||
|
<br>
|
||||||
|
<input type="password" required name="password" autocomplete="current-password">
|
||||||
|
<p>{%s lc.Get("auth.cookie_tip") %}</p>
|
||||||
|
<button class="btn" type="submit" value="Log in">{%s lc.Get("auth.login_button") %}</button>
|
||||||
|
<a class="btn btn_weak" href="/">{%s lc.Get("ui.cancel") %}</a>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
{%= telegramWidget(lc) %}
|
||||||
|
{% else %}
|
||||||
|
<p>{%s lc.Get("auth.noauth") %}</p>
|
||||||
|
<p><a class="btn btn_weak" href="/">← {%s lc.Get("auth.go_home") %}</a></p>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
{% 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 %}
|
||||||
|
<p class="telegram-notice">{%s lc.Get("auth.telegram_tip") %}</p>
|
||||||
|
<script async src="https://telegram.org/js/telegram-widget.js?15" data-telegram-login="{%s cfg.TelegramBotName %}" data-size="medium" data-userpic="false" data-auth-url="{%s cfg.URL %}/telegram-login"></script>
|
||||||
|
{% endif %}
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% func LoginError(err string, lc *l18n.Localizer) %}
|
||||||
|
<main class="main-width">
|
||||||
|
<section>
|
||||||
|
{% switch err %}
|
||||||
|
{% case "unknown username" %}
|
||||||
|
<p class="error">{%s lc.Get("auth.error_username") %}</p>
|
||||||
|
{% case "wrong password" %}
|
||||||
|
<p class="error">{%s lc.Get("auth.error_password") %}</p>
|
||||||
|
{% default %}
|
||||||
|
<p class="error">{%s err %}</p>
|
||||||
|
{% endswitch %}
|
||||||
|
<p><a href="/login">← {%s lc.Get("auth.try_again") %}</a></p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% func Logout(can bool, lc *l18n.Localizer) %}
|
||||||
|
<main class="main-width">
|
||||||
|
<section>
|
||||||
|
{% if can %}
|
||||||
|
<h1>{%s lc.Get("auth.logout_header") %}</h1>
|
||||||
|
<form method="POST" action="/logout">
|
||||||
|
<input class="btn btn_accent" type="submit" value="{%s lc.Get("auth.logout_button") %}"/>
|
||||||
|
<a class="btn btn_weak" href="/">{%s lc.Get("auth.go_home") %}</a>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<p>{%s lc.Get("auth.logout_anon") %}</p>
|
||||||
|
<p><a href="/login">{%s lc.Get("auth.login_title") %}</a></p>
|
||||||
|
<p><a href="/">← {%s lc.Get("auth.go_home") %}</a></p>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% func Lock(lc *l18n.Localizer) %}
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>🔒 {%s lc.Get("auth.lock_title") %}</title>
|
||||||
|
<link rel="shortcut icon" href="/static/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="locked-notice main-width">
|
||||||
|
<section class="locked-notice__message">
|
||||||
|
<p class="locked-notice__lock">🔒</p>
|
||||||
|
<h1 class="locked-notice__title">{%s lc.Get("auth.lock_title") %}</h1>
|
||||||
|
<form class="locked-notice__login-form" method="post" action="/login" id="login-form" enctype="multipart/form-data" autocomplete="on">
|
||||||
|
<label for="login-form__username">{%s lc.Get("auth.username") %}</label>
|
||||||
|
<br>
|
||||||
|
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
|
||||||
|
<br>
|
||||||
|
<label for="login-form__password">{%s lc.Get("auth.password") %}</label>
|
||||||
|
<br>
|
||||||
|
<input type="password" required name="password" autocomplete="current-password">
|
||||||
|
<br>
|
||||||
|
<button class="btn" type="submit" value="Log in">{%s lc.Get("auth.login_button") %}</button>
|
||||||
|
</form>
|
||||||
|
{%= telegramWidget(lc) %}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{% 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) %}
|
||||||
|
<main class="main-width user-list">
|
||||||
|
{% 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)
|
||||||
|
%}
|
||||||
|
<h1>{%s get("heading") %}</h1>
|
||||||
|
<section>
|
||||||
|
<h2>{%s get("administrators") %}</h2>
|
||||||
|
<ol>{% for _, name := range admins %}
|
||||||
|
<li><a href="/hypha/{%s cfg.UserHypha %}/{%s name %}">{%s name %}</a></li>
|
||||||
|
{% endfor %}</ol>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2>{%s get("moderators") %}</h2>
|
||||||
|
<ol>{% for _, name := range moderators %}
|
||||||
|
<li><a href="/hypha/{%s cfg.UserHypha %}/{%s name %}">{%s name %}</a></li>
|
||||||
|
{% endfor %}</ol>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2>{%s get("editors") %}</h2>
|
||||||
|
<ol>{% for _, name := range editors %}
|
||||||
|
<li><a href="/hypha/{%s cfg.UserHypha %}/{%s name %}">{%s name %}</a></li>
|
||||||
|
{% endfor %}</ol>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2>{%s get("readers") %}</h2>
|
||||||
|
<ol>{% for _, name := range readers %}
|
||||||
|
<li><a href="/hypha/{%s cfg.UserHypha %}/{%s name %}">{%s name %}</a></li>
|
||||||
|
{% endfor %}</ol>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
{% endfunc %}
|
||||||
805
auth/auth.qtpl.go
Normal file
@ -0,0 +1,805 @@
|
|||||||
|
// 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(`
|
||||||
|
<main class="main-width">
|
||||||
|
<section>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:13
|
||||||
|
if cfg.AllowRegistration {
|
||||||
|
//line auth/auth.qtpl:13
|
||||||
|
qw422016.N().S(`
|
||||||
|
<form class="modal" method="post" action="/register?`)
|
||||||
|
//line auth/auth.qtpl:14
|
||||||
|
qw422016.E().S(rq.URL.RawQuery)
|
||||||
|
//line auth/auth.qtpl:14
|
||||||
|
qw422016.N().S(`" id="register-form" enctype="multipart/form-data" autocomplete="off">
|
||||||
|
<fieldset class="modal__fieldset">
|
||||||
|
<legend class="modal__title">`)
|
||||||
|
//line auth/auth.qtpl:16
|
||||||
|
qw422016.E().S(lc.Get("auth.register_header", &l18n.Replacements{"name": cfg.WikiName}))
|
||||||
|
//line auth/auth.qtpl:16
|
||||||
|
qw422016.N().S(`</legend>
|
||||||
|
|
||||||
|
<label for="register-form__username">`)
|
||||||
|
//line auth/auth.qtpl:18
|
||||||
|
qw422016.E().S(lc.Get("auth.username"))
|
||||||
|
//line auth/auth.qtpl:18
|
||||||
|
qw422016.N().S(`</label>
|
||||||
|
<br>
|
||||||
|
<input type="text" required autofocus id="login-form__username" name="username">
|
||||||
|
<br>
|
||||||
|
<label for="login-form__password">`)
|
||||||
|
//line auth/auth.qtpl:22
|
||||||
|
qw422016.E().S(lc.Get("auth.password"))
|
||||||
|
//line auth/auth.qtpl:22
|
||||||
|
qw422016.N().S(`</label>
|
||||||
|
<br>
|
||||||
|
<input type="password" required name="password">
|
||||||
|
<p>`)
|
||||||
|
//line auth/auth.qtpl:25
|
||||||
|
qw422016.E().S(lc.Get("auth.password_tip"))
|
||||||
|
//line auth/auth.qtpl:25
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
<p>`)
|
||||||
|
//line auth/auth.qtpl:26
|
||||||
|
qw422016.E().S(lc.Get("auth.cookie_tip"))
|
||||||
|
//line auth/auth.qtpl:26
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
<button class="btn" type="submit" value="Register">`)
|
||||||
|
//line auth/auth.qtpl:27
|
||||||
|
qw422016.E().S(lc.Get("auth.register_button"))
|
||||||
|
//line auth/auth.qtpl:27
|
||||||
|
qw422016.N().S(`</button>
|
||||||
|
<a class="btn btn_weak" href="/`)
|
||||||
|
//line auth/auth.qtpl:28
|
||||||
|
qw422016.E().S(rq.URL.RawQuery)
|
||||||
|
//line auth/auth.qtpl:28
|
||||||
|
qw422016.N().S(`">`)
|
||||||
|
//line auth/auth.qtpl:28
|
||||||
|
qw422016.E().S(lc.Get("ui.cancel"))
|
||||||
|
//line auth/auth.qtpl:28
|
||||||
|
qw422016.N().S(`</a>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<p>`)
|
||||||
|
//line auth/auth.qtpl:33
|
||||||
|
qw422016.E().S(lc.Get("auth.noregister"))
|
||||||
|
//line auth/auth.qtpl:33
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
<p><a href="/`)
|
||||||
|
//line auth/auth.qtpl:34
|
||||||
|
qw422016.E().S(rq.URL.RawQuery)
|
||||||
|
//line auth/auth.qtpl:34
|
||||||
|
qw422016.N().S(`">← `)
|
||||||
|
//line auth/auth.qtpl:34
|
||||||
|
qw422016.E().S(lc.Get("auth.go_back"))
|
||||||
|
//line auth/auth.qtpl:34
|
||||||
|
qw422016.N().S(`</a></p>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:35
|
||||||
|
} else {
|
||||||
|
//line auth/auth.qtpl:35
|
||||||
|
qw422016.N().S(`
|
||||||
|
<p>`)
|
||||||
|
//line auth/auth.qtpl:36
|
||||||
|
qw422016.E().S(lc.Get("auth.noauth"))
|
||||||
|
//line auth/auth.qtpl:36
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
<p><a href="/`)
|
||||||
|
//line auth/auth.qtpl:37
|
||||||
|
qw422016.E().S(rq.URL.RawQuery)
|
||||||
|
//line auth/auth.qtpl:37
|
||||||
|
qw422016.N().S(`">← `)
|
||||||
|
//line auth/auth.qtpl:37
|
||||||
|
qw422016.E().S(lc.Get("auth.go_back"))
|
||||||
|
//line auth/auth.qtpl:37
|
||||||
|
qw422016.N().S(`</a></p>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:38
|
||||||
|
}
|
||||||
|
//line auth/auth.qtpl:38
|
||||||
|
qw422016.N().S(`
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<main class="main-width">
|
||||||
|
<section>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:46
|
||||||
|
if cfg.UseAuth {
|
||||||
|
//line auth/auth.qtpl:46
|
||||||
|
qw422016.N().S(`
|
||||||
|
<form class="modal" method="post" action="/login" id="login-form" enctype="multipart/form-data" autocomplete="on">
|
||||||
|
<fieldset class="modal__fieldset">
|
||||||
|
<legend class="modal__title">`)
|
||||||
|
//line auth/auth.qtpl:49
|
||||||
|
qw422016.E().S(lc.Get("auth.login_header", &l18n.Replacements{"name": cfg.WikiName}))
|
||||||
|
//line auth/auth.qtpl:49
|
||||||
|
qw422016.N().S(`</legend>
|
||||||
|
<label for="login-form__username">`)
|
||||||
|
//line auth/auth.qtpl:50
|
||||||
|
qw422016.E().S(lc.Get("auth.username"))
|
||||||
|
//line auth/auth.qtpl:50
|
||||||
|
qw422016.N().S(`</label>
|
||||||
|
<br>
|
||||||
|
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
|
||||||
|
<br>
|
||||||
|
<label for="login-form__password">`)
|
||||||
|
//line auth/auth.qtpl:54
|
||||||
|
qw422016.E().S(lc.Get("auth.password"))
|
||||||
|
//line auth/auth.qtpl:54
|
||||||
|
qw422016.N().S(`</label>
|
||||||
|
<br>
|
||||||
|
<input type="password" required name="password" autocomplete="current-password">
|
||||||
|
<p>`)
|
||||||
|
//line auth/auth.qtpl:57
|
||||||
|
qw422016.E().S(lc.Get("auth.cookie_tip"))
|
||||||
|
//line auth/auth.qtpl:57
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
<button class="btn" type="submit" value="Log in">`)
|
||||||
|
//line auth/auth.qtpl:58
|
||||||
|
qw422016.E().S(lc.Get("auth.login_button"))
|
||||||
|
//line auth/auth.qtpl:58
|
||||||
|
qw422016.N().S(`</button>
|
||||||
|
<a class="btn btn_weak" href="/">`)
|
||||||
|
//line auth/auth.qtpl:59
|
||||||
|
qw422016.E().S(lc.Get("ui.cancel"))
|
||||||
|
//line auth/auth.qtpl:59
|
||||||
|
qw422016.N().S(`</a>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<p>`)
|
||||||
|
//line auth/auth.qtpl:64
|
||||||
|
qw422016.E().S(lc.Get("auth.noauth"))
|
||||||
|
//line auth/auth.qtpl:64
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
<p><a class="btn btn_weak" href="/">← `)
|
||||||
|
//line auth/auth.qtpl:65
|
||||||
|
qw422016.E().S(lc.Get("auth.go_home"))
|
||||||
|
//line auth/auth.qtpl:65
|
||||||
|
qw422016.N().S(`</a></p>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:66
|
||||||
|
}
|
||||||
|
//line auth/auth.qtpl:66
|
||||||
|
qw422016.N().S(`
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<p class="telegram-notice">`)
|
||||||
|
//line auth/auth.qtpl:74
|
||||||
|
qw422016.E().S(lc.Get("auth.telegram_tip"))
|
||||||
|
//line auth/auth.qtpl:74
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
<script async src="https://telegram.org/js/telegram-widget.js?15" data-telegram-login="`)
|
||||||
|
//line auth/auth.qtpl:75
|
||||||
|
qw422016.E().S(cfg.TelegramBotName)
|
||||||
|
//line auth/auth.qtpl:75
|
||||||
|
qw422016.N().S(`" data-size="medium" data-userpic="false" data-auth-url="`)
|
||||||
|
//line auth/auth.qtpl:75
|
||||||
|
qw422016.E().S(cfg.URL)
|
||||||
|
//line auth/auth.qtpl:75
|
||||||
|
qw422016.N().S(`/telegram-login"></script>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<main class="main-width">
|
||||||
|
<section>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:82
|
||||||
|
switch err {
|
||||||
|
//line auth/auth.qtpl:83
|
||||||
|
case "unknown username":
|
||||||
|
//line auth/auth.qtpl:83
|
||||||
|
qw422016.N().S(`
|
||||||
|
<p class="error">`)
|
||||||
|
//line auth/auth.qtpl:84
|
||||||
|
qw422016.E().S(lc.Get("auth.error_username"))
|
||||||
|
//line auth/auth.qtpl:84
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:85
|
||||||
|
case "wrong password":
|
||||||
|
//line auth/auth.qtpl:85
|
||||||
|
qw422016.N().S(`
|
||||||
|
<p class="error">`)
|
||||||
|
//line auth/auth.qtpl:86
|
||||||
|
qw422016.E().S(lc.Get("auth.error_password"))
|
||||||
|
//line auth/auth.qtpl:86
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:87
|
||||||
|
default:
|
||||||
|
//line auth/auth.qtpl:87
|
||||||
|
qw422016.N().S(`
|
||||||
|
<p class="error">`)
|
||||||
|
//line auth/auth.qtpl:88
|
||||||
|
qw422016.E().S(err)
|
||||||
|
//line auth/auth.qtpl:88
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:89
|
||||||
|
}
|
||||||
|
//line auth/auth.qtpl:89
|
||||||
|
qw422016.N().S(`
|
||||||
|
<p><a href="/login">← `)
|
||||||
|
//line auth/auth.qtpl:90
|
||||||
|
qw422016.E().S(lc.Get("auth.try_again"))
|
||||||
|
//line auth/auth.qtpl:90
|
||||||
|
qw422016.N().S(`</a></p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<main class="main-width">
|
||||||
|
<section>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:98
|
||||||
|
if can {
|
||||||
|
//line auth/auth.qtpl:98
|
||||||
|
qw422016.N().S(`
|
||||||
|
<h1>`)
|
||||||
|
//line auth/auth.qtpl:99
|
||||||
|
qw422016.E().S(lc.Get("auth.logout_header"))
|
||||||
|
//line auth/auth.qtpl:99
|
||||||
|
qw422016.N().S(`</h1>
|
||||||
|
<form method="POST" action="/logout">
|
||||||
|
<input class="btn btn_accent" type="submit" value="`)
|
||||||
|
//line auth/auth.qtpl:101
|
||||||
|
qw422016.E().S(lc.Get("auth.logout_button"))
|
||||||
|
//line auth/auth.qtpl:101
|
||||||
|
qw422016.N().S(`"/>
|
||||||
|
<a class="btn btn_weak" href="/">`)
|
||||||
|
//line auth/auth.qtpl:102
|
||||||
|
qw422016.E().S(lc.Get("auth.go_home"))
|
||||||
|
//line auth/auth.qtpl:102
|
||||||
|
qw422016.N().S(`</a>
|
||||||
|
</form>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:104
|
||||||
|
} else {
|
||||||
|
//line auth/auth.qtpl:104
|
||||||
|
qw422016.N().S(`
|
||||||
|
<p>`)
|
||||||
|
//line auth/auth.qtpl:105
|
||||||
|
qw422016.E().S(lc.Get("auth.logout_anon"))
|
||||||
|
//line auth/auth.qtpl:105
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
<p><a href="/login">`)
|
||||||
|
//line auth/auth.qtpl:106
|
||||||
|
qw422016.E().S(lc.Get("auth.login_title"))
|
||||||
|
//line auth/auth.qtpl:106
|
||||||
|
qw422016.N().S(`</a></p>
|
||||||
|
<p><a href="/">← `)
|
||||||
|
//line auth/auth.qtpl:107
|
||||||
|
qw422016.E().S(lc.Get("auth.go_home"))
|
||||||
|
//line auth/auth.qtpl:107
|
||||||
|
qw422016.N().S(`</a></p>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:108
|
||||||
|
}
|
||||||
|
//line auth/auth.qtpl:108
|
||||||
|
qw422016.N().S(`
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>🔒 `)
|
||||||
|
//line auth/auth.qtpl:119
|
||||||
|
qw422016.E().S(lc.Get("auth.lock_title"))
|
||||||
|
//line auth/auth.qtpl:119
|
||||||
|
qw422016.N().S(`</title>
|
||||||
|
<link rel="shortcut icon" href="/static/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="locked-notice main-width">
|
||||||
|
<section class="locked-notice__message">
|
||||||
|
<p class="locked-notice__lock">🔒</p>
|
||||||
|
<h1 class="locked-notice__title">`)
|
||||||
|
//line auth/auth.qtpl:127
|
||||||
|
qw422016.E().S(lc.Get("auth.lock_title"))
|
||||||
|
//line auth/auth.qtpl:127
|
||||||
|
qw422016.N().S(`</h1>
|
||||||
|
<form class="locked-notice__login-form" method="post" action="/login" id="login-form" enctype="multipart/form-data" autocomplete="on">
|
||||||
|
<label for="login-form__username">`)
|
||||||
|
//line auth/auth.qtpl:129
|
||||||
|
qw422016.E().S(lc.Get("auth.username"))
|
||||||
|
//line auth/auth.qtpl:129
|
||||||
|
qw422016.N().S(`</label>
|
||||||
|
<br>
|
||||||
|
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
|
||||||
|
<br>
|
||||||
|
<label for="login-form__password">`)
|
||||||
|
//line auth/auth.qtpl:133
|
||||||
|
qw422016.E().S(lc.Get("auth.password"))
|
||||||
|
//line auth/auth.qtpl:133
|
||||||
|
qw422016.N().S(`</label>
|
||||||
|
<br>
|
||||||
|
<input type="password" required name="password" autocomplete="current-password">
|
||||||
|
<br>
|
||||||
|
<button class="btn" type="submit" value="Log in">`)
|
||||||
|
//line auth/auth.qtpl:137
|
||||||
|
qw422016.E().S(lc.Get("auth.login_button"))
|
||||||
|
//line auth/auth.qtpl:137
|
||||||
|
qw422016.N().S(`</button>
|
||||||
|
</form>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:139
|
||||||
|
streamtelegramWidget(qw422016, lc)
|
||||||
|
//line auth/auth.qtpl:139
|
||||||
|
qw422016.N().S(`
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<main class="main-width user-list">
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<h1>`)
|
||||||
|
//line auth/auth.qtpl:187
|
||||||
|
qw422016.E().S(get("heading"))
|
||||||
|
//line auth/auth.qtpl:187
|
||||||
|
qw422016.N().S(`</h1>
|
||||||
|
<section>
|
||||||
|
<h2>`)
|
||||||
|
//line auth/auth.qtpl:189
|
||||||
|
qw422016.E().S(get("administrators"))
|
||||||
|
//line auth/auth.qtpl:189
|
||||||
|
qw422016.N().S(`</h2>
|
||||||
|
<ol>`)
|
||||||
|
//line auth/auth.qtpl:190
|
||||||
|
for _, name := range admins {
|
||||||
|
//line auth/auth.qtpl:190
|
||||||
|
qw422016.N().S(`
|
||||||
|
<li><a href="/hypha/`)
|
||||||
|
//line auth/auth.qtpl:191
|
||||||
|
qw422016.E().S(cfg.UserHypha)
|
||||||
|
//line auth/auth.qtpl:191
|
||||||
|
qw422016.N().S(`/`)
|
||||||
|
//line auth/auth.qtpl:191
|
||||||
|
qw422016.E().S(name)
|
||||||
|
//line auth/auth.qtpl:191
|
||||||
|
qw422016.N().S(`">`)
|
||||||
|
//line auth/auth.qtpl:191
|
||||||
|
qw422016.E().S(name)
|
||||||
|
//line auth/auth.qtpl:191
|
||||||
|
qw422016.N().S(`</a></li>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:192
|
||||||
|
}
|
||||||
|
//line auth/auth.qtpl:192
|
||||||
|
qw422016.N().S(`</ol>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2>`)
|
||||||
|
//line auth/auth.qtpl:195
|
||||||
|
qw422016.E().S(get("moderators"))
|
||||||
|
//line auth/auth.qtpl:195
|
||||||
|
qw422016.N().S(`</h2>
|
||||||
|
<ol>`)
|
||||||
|
//line auth/auth.qtpl:196
|
||||||
|
for _, name := range moderators {
|
||||||
|
//line auth/auth.qtpl:196
|
||||||
|
qw422016.N().S(`
|
||||||
|
<li><a href="/hypha/`)
|
||||||
|
//line auth/auth.qtpl:197
|
||||||
|
qw422016.E().S(cfg.UserHypha)
|
||||||
|
//line auth/auth.qtpl:197
|
||||||
|
qw422016.N().S(`/`)
|
||||||
|
//line auth/auth.qtpl:197
|
||||||
|
qw422016.E().S(name)
|
||||||
|
//line auth/auth.qtpl:197
|
||||||
|
qw422016.N().S(`">`)
|
||||||
|
//line auth/auth.qtpl:197
|
||||||
|
qw422016.E().S(name)
|
||||||
|
//line auth/auth.qtpl:197
|
||||||
|
qw422016.N().S(`</a></li>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:198
|
||||||
|
}
|
||||||
|
//line auth/auth.qtpl:198
|
||||||
|
qw422016.N().S(`</ol>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2>`)
|
||||||
|
//line auth/auth.qtpl:201
|
||||||
|
qw422016.E().S(get("editors"))
|
||||||
|
//line auth/auth.qtpl:201
|
||||||
|
qw422016.N().S(`</h2>
|
||||||
|
<ol>`)
|
||||||
|
//line auth/auth.qtpl:202
|
||||||
|
for _, name := range editors {
|
||||||
|
//line auth/auth.qtpl:202
|
||||||
|
qw422016.N().S(`
|
||||||
|
<li><a href="/hypha/`)
|
||||||
|
//line auth/auth.qtpl:203
|
||||||
|
qw422016.E().S(cfg.UserHypha)
|
||||||
|
//line auth/auth.qtpl:203
|
||||||
|
qw422016.N().S(`/`)
|
||||||
|
//line auth/auth.qtpl:203
|
||||||
|
qw422016.E().S(name)
|
||||||
|
//line auth/auth.qtpl:203
|
||||||
|
qw422016.N().S(`">`)
|
||||||
|
//line auth/auth.qtpl:203
|
||||||
|
qw422016.E().S(name)
|
||||||
|
//line auth/auth.qtpl:203
|
||||||
|
qw422016.N().S(`</a></li>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:204
|
||||||
|
}
|
||||||
|
//line auth/auth.qtpl:204
|
||||||
|
qw422016.N().S(`</ol>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2>`)
|
||||||
|
//line auth/auth.qtpl:207
|
||||||
|
qw422016.E().S(get("readers"))
|
||||||
|
//line auth/auth.qtpl:207
|
||||||
|
qw422016.N().S(`</h2>
|
||||||
|
<ol>`)
|
||||||
|
//line auth/auth.qtpl:208
|
||||||
|
for _, name := range readers {
|
||||||
|
//line auth/auth.qtpl:208
|
||||||
|
qw422016.N().S(`
|
||||||
|
<li><a href="/hypha/`)
|
||||||
|
//line auth/auth.qtpl:209
|
||||||
|
qw422016.E().S(cfg.UserHypha)
|
||||||
|
//line auth/auth.qtpl:209
|
||||||
|
qw422016.N().S(`/`)
|
||||||
|
//line auth/auth.qtpl:209
|
||||||
|
qw422016.E().S(name)
|
||||||
|
//line auth/auth.qtpl:209
|
||||||
|
qw422016.N().S(`">`)
|
||||||
|
//line auth/auth.qtpl:209
|
||||||
|
qw422016.E().S(name)
|
||||||
|
//line auth/auth.qtpl:209
|
||||||
|
qw422016.N().S(`</a></li>
|
||||||
|
`)
|
||||||
|
//line auth/auth.qtpl:210
|
||||||
|
}
|
||||||
|
//line auth/auth.qtpl:210
|
||||||
|
qw422016.N().S(`</ol>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
`)
|
||||||
|
//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
|
||||||
|
}
|
||||||
22
auth/get_rid_of_it.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
225
auth/web.go
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
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(
|
||||||
|
`<main class="main-width"><p>%s</p><p><a href="/register">%s<a></p></main>`,
|
||||||
|
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(
|
||||||
|
`<main class="main-width"><p>%s</p><p>%s</p><p><a href="/login">%s<a></p></main>`,
|
||||||
|
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(
|
||||||
|
`<main class="main-width"><p>%s</p><p>%s</p><p><a href="/login">%s<a></p></main>`,
|
||||||
|
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)
|
||||||
|
}
|
||||||
@ -2,11 +2,10 @@
|
|||||||
package backlinks
|
package backlinks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
"log"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -62,25 +61,6 @@ func BacklinksCount(hyphaName string) int {
|
|||||||
return 0
|
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 hyphae.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
|
// Using set here seems like the most appropriate solution
|
||||||
type linkSet map[string]struct{}
|
type linkSet map[string]struct{}
|
||||||
|
|
||||||
@ -108,7 +88,7 @@ func fetchText(h hyphae.Hypha) string {
|
|||||||
|
|
||||||
text, err := os.ReadFile(path)
|
text, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to read file", "path", path, "err", err, "hyphaName", h.CanonicalName())
|
log.Println(err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return string(text)
|
return string(text)
|
||||||
@ -1,13 +1,12 @@
|
|||||||
package backlinks
|
package backlinks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/mycoopts"
|
|
||||||
|
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/links"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/links"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/mycoopts"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UpdateBacklinksAfterEdit is a creation/editing hook for backlinks index
|
// UpdateBacklinksAfterEdit is a creation/editing hook for backlinks index
|
||||||
85
backlinks/web.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
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"}}Обратные ссылки на <a href="/hypha/{{.}}">{{beautifulName .}}</a>{{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,
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -23,8 +23,8 @@ package categories
|
|||||||
|
|
||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
// ListOfCategories returns unsorted names of all categories.
|
// listOfCategories returns unsorted names of all categories.
|
||||||
func ListOfCategories() (categoryList []string) {
|
func listOfCategories() (categoryList []string) {
|
||||||
mutex.RLock()
|
mutex.RLock()
|
||||||
for cat, _ := range categoryToHyphae {
|
for cat, _ := range categoryToHyphae {
|
||||||
categoryList = append(categoryList, cat)
|
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.
|
// 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) {
|
func hyphaeInCategory(catName string) (hyphaList []string) {
|
||||||
mutex.RLock()
|
mutex.RLock()
|
||||||
defer mutex.RUnlock()
|
defer mutex.RUnlock()
|
||||||
if node, ok := categoryToHyphae[catName]; ok {
|
if node, ok := categoryToHyphae[catName]; ok {
|
||||||
@ -75,8 +75,8 @@ func AddHyphaToCategory(hyphaName, catName string) {
|
|||||||
go saveToDisk()
|
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.
|
// 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) {
|
func removeHyphaFromCategory(hyphaName, catName string) {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
if node, ok := hyphaToCategories[hyphaName]; ok {
|
if node, ok := hyphaToCategories[hyphaName]; ok {
|
||||||
node.removeCategory(catName)
|
node.removeCategory(catName)
|
||||||
@ -2,25 +2,25 @@ package categories
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log/slog"
|
"github.com/bouncepaw/mycorrhiza/files"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var categoryToHyphae = map[string]*categoryNode{}
|
var categoryToHyphae = map[string]*categoryNode{}
|
||||||
var hyphaToCategories = map[string]*hyphaNode{}
|
var hyphaToCategories = map[string]*hyphaNode{}
|
||||||
|
|
||||||
// Init initializes the category system. Call it after the Structure is initialized. This function might terminate the program in case of a bad mood or filesystem faults.
|
// Init initializes the category system. Call it after the Structure is initialized. This function might terminate the program in case of a bad mood or filesystem faults.
|
||||||
func Init() error {
|
func Init() {
|
||||||
record, err := readCategoriesFromDisk()
|
var (
|
||||||
|
record, err = readCategoriesFromDisk()
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to read categories from disk", "err", err)
|
log.Fatalln(err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cat := range record.Categories {
|
for _, cat := range record.Categories {
|
||||||
@ -45,8 +45,7 @@ func Init() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("Indexed categories", "n", len(categoryToHyphae))
|
log.Println("Found", len(categoryToHyphae), "categories")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type categoryNode struct {
|
type categoryNode struct {
|
||||||
@ -123,7 +122,9 @@ func readCategoriesFromDisk() (catFileRecord, error) {
|
|||||||
var fileMutex sync.Mutex
|
var fileMutex sync.Mutex
|
||||||
|
|
||||||
func saveToDisk() {
|
func saveToDisk() {
|
||||||
var record catFileRecord
|
var (
|
||||||
|
record catFileRecord
|
||||||
|
)
|
||||||
for name, node := range categoryToHyphae {
|
for name, node := range categoryToHyphae {
|
||||||
record.Categories = append(record.Categories, catRecord{
|
record.Categories = append(record.Categories, catRecord{
|
||||||
Name: name,
|
Name: name,
|
||||||
@ -132,16 +133,13 @@ func saveToDisk() {
|
|||||||
}
|
}
|
||||||
data, err := json.MarshalIndent(record, "", "\t")
|
data, err := json.MarshalIndent(record, "", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to marshal categories record", "err", err)
|
log.Fatalln(err) // Better fail now, than later
|
||||||
os.Exit(1) // Better fail now, than later
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make the data safer somehow?? Back it up before overwriting?
|
// TODO: make the data safer somehow?? Back it up before overwriting?
|
||||||
fileMutex.Lock()
|
fileMutex.Lock()
|
||||||
err = os.WriteFile(files.CategoriesJSON(), data, 0666)
|
err = os.WriteFile(files.CategoriesJSON(), data, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to write categories.json", "err", err)
|
log.Fatalln(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
fileMutex.Unlock()
|
fileMutex.Unlock()
|
||||||
}
|
}
|
||||||
@ -1,45 +1,40 @@
|
|||||||
package web
|
package categories
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/categories"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
"github.com/bouncepaw/mycorrhiza/viewutil"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"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()
|
||||||
|
}
|
||||||
|
|
||||||
func handlerEditCategory(w http.ResponseWriter, rq *http.Request) {
|
func handlerEditCategory(w http.ResponseWriter, rq *http.Request) {
|
||||||
util.PrepareRq(rq)
|
util.PrepareRq(rq)
|
||||||
meta := viewutil.MetaFrom(w, rq)
|
|
||||||
catName := util.CanonicalName(strings.TrimPrefix(strings.TrimPrefix(rq.URL.Path, "/edit-category"), "/"))
|
catName := util.CanonicalName(strings.TrimPrefix(strings.TrimPrefix(rq.URL.Path, "/edit-category"), "/"))
|
||||||
if catName == "" {
|
if catName == "" {
|
||||||
viewutil.HandlerNotFound(w, rq)
|
http.Error(w, "No category name", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Println("Editing category", catName)
|
||||||
slog.Info("Editing category", "name", catName)
|
categoryEdit(viewutil.MetaFrom(w, rq), 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) {
|
func handlerListCategory(w http.ResponseWriter, rq *http.Request) {
|
||||||
slog.Info("Viewing list of categories")
|
log.Println("Viewing list of categories")
|
||||||
cats := categories.ListOfCategories()
|
categoryList(viewutil.MetaFrom(w, rq))
|
||||||
sort.Strings(cats)
|
|
||||||
|
|
||||||
_ = pageCatList.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
|
|
||||||
"Addr": "/category",
|
|
||||||
"Categories": cats,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlerCategory(w http.ResponseWriter, rq *http.Request) {
|
func handlerCategory(w http.ResponseWriter, rq *http.Request) {
|
||||||
@ -49,15 +44,8 @@ func handlerCategory(w http.ResponseWriter, rq *http.Request) {
|
|||||||
handlerListCategory(w, rq)
|
handlerListCategory(w, rq)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Println("Viewing category", catName)
|
||||||
meta := viewutil.MetaFrom(w, rq)
|
categoryPage(viewutil.MetaFrom(w, rq), catName)
|
||||||
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.
|
// 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.
|
||||||
@ -65,7 +53,7 @@ func handlerCategory(w http.ResponseWriter, rq *http.Request) {
|
|||||||
// There is one hypha from the hypha field. Then there are n hyphae in fields prefixed by _. It seems like I have to do it myself. Compare with PHP which handles it for you. I hope I am doing this wrong.
|
// There is one hypha from the hypha field. Then there are n hyphae in fields prefixed by _. It seems like I have to do it myself. Compare with PHP which handles it for you. I hope I am doing this wrong.
|
||||||
func hyphaeFromRequest(rq *http.Request) (canonicalNames []string) {
|
func hyphaeFromRequest(rq *http.Request) (canonicalNames []string) {
|
||||||
if err := rq.ParseForm(); err != nil {
|
if err := rq.ParseForm(); err != nil {
|
||||||
slog.Info("Failed to parse form", "err", err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
if hyphaName := util.CanonicalName(rq.PostFormValue("hypha")); hyphaName != "" {
|
if hyphaName := util.CanonicalName(rq.PostFormValue("hypha")); hyphaName != "" {
|
||||||
canonicalNames = append(canonicalNames, hyphaName)
|
canonicalNames = append(canonicalNames, hyphaName)
|
||||||
@ -99,17 +87,15 @@ func handlerRemoveFromCategory(w http.ResponseWriter, rq *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(hyphaNames) == 0 || catName == "" {
|
if len(hyphaNames) == 0 || catName == "" {
|
||||||
slog.Info("No data for removal of hyphae from category passed",
|
log.Printf("%s passed no data for removal of hyphae from a category\n", u.Name)
|
||||||
"username", u.Name, "catName", catName)
|
|
||||||
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
|
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, hyphaName := range hyphaNames {
|
for _, hyphaName := range hyphaNames {
|
||||||
// TODO: Make it more effective.
|
// TODO: Make it more effective.
|
||||||
categories.RemoveHyphaFromCategory(hyphaName, catName)
|
removeHyphaFromCategory(hyphaName, catName)
|
||||||
}
|
}
|
||||||
slog.Info("Remove hyphae from category",
|
log.Printf("%s removed %q from category %s\n", u.Name, hyphaNames, catName)
|
||||||
"username", u.Name, "catName", catName, "hyphaNames", hyphaNames)
|
|
||||||
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
|
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +115,7 @@ func handlerAddToCategory(w http.ResponseWriter, rq *http.Request) {
|
|||||||
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
|
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
slog.Info(user.FromRequest(rq).Name, "added", hyphaName, "to", catName)
|
log.Println(user.FromRequest(rq).Name, "added", hyphaName, "to", catName)
|
||||||
categories.AddHyphaToCategory(hyphaName, catName)
|
AddHyphaToCategory(hyphaName, catName)
|
||||||
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
|
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
37
categories/view_card.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{{define "category card"}}
|
||||||
|
{{if or .GivenPermissionToModify (len .Categories)}}
|
||||||
|
{{$hyphaName := .HyphaName}}
|
||||||
|
{{$givenPermission := .GivenPermissionToModify}}
|
||||||
|
<aside class="layout-card categories-card">
|
||||||
|
<h2 class="layout-card__title">{{block `categories` .}}Categories{{end}}</h2>
|
||||||
|
<ul class="categories-card__entries">
|
||||||
|
{{range .Categories}}
|
||||||
|
<li class="categories-card__entry">
|
||||||
|
<a class="categories-card__link" href="/category/{{.}}">{{beautifulName .}}</a>
|
||||||
|
<form method="POST" action="/remove-from-category" class="categories-card__remove-form">
|
||||||
|
<input type="hidden" name="cat" value="{{.}}">
|
||||||
|
<input type="hidden" name="hypha" value="{{$hyphaName}}">
|
||||||
|
<input type="hidden" name="redirect-to" value="/hypha/{{$hyphaName}}">
|
||||||
|
{{if $givenPermission}}
|
||||||
|
<input type="submit" value="x" class="btn categories-card__btn"
|
||||||
|
title="{{block `remove from category title` .}}Remove the hypha from this category{{end}}">
|
||||||
|
{{end}}
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
{{if .GivenPermissionToModify}}
|
||||||
|
<li class="categories-card__entry categories-card__add-to-cat">
|
||||||
|
<form method="POST" action="/add-to-category" class="categories-card__add-form js-add-cat-form">
|
||||||
|
<input type="text" name="cat" id="_cat-name" class="js-add-cat-name" autocomplete="off"
|
||||||
|
placeholder="{{block `placeholder` .}}Category name...{{end}}">
|
||||||
|
<datalist class="js-add-cat-list" id="cat-name-options"></datalist>
|
||||||
|
<input type="hidden" name="hypha" value="{{$hyphaName}}">
|
||||||
|
<input type="hidden" name="redirect-to" value="/hypha/{{$hyphaName}}">
|
||||||
|
<input type="submit" class="btn categories-card__btn" value="+"
|
||||||
|
title="{{block `add to category title` .}}Add the hypha to this category{{end}}">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
{{end}}{{end}}
|
||||||
37
categories/view_edit.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{{define "edit category x"}}Edit category {{beautifulName .}}{{end}}
|
||||||
|
{{define "title"}}{{template "edit category x" .CatName}}{{end}}
|
||||||
|
{{define "body"}}
|
||||||
|
<main class="main-width category">
|
||||||
|
<h1>{{block "edit category heading" .CatName}}Edit category <a href="/category/{{.}}">{{beautifulName .}}</a>{{end}}</h1>
|
||||||
|
{{if len .Hyphae | not}}
|
||||||
|
<p>{{block "empty cat" .}}This category is empty{{end}}</p>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .GivenPermissionToModify}}
|
||||||
|
<h2>{{block "add to category title" .}}Add a hypha to the category{{end}}</h2>
|
||||||
|
<form method="POST" action="/add-to-category" class="add-to-category">
|
||||||
|
<input type="text" name="hypha" id="_hypha-name"
|
||||||
|
placeholder="{{block `hypha name` .}}Hypha name{{end}}">
|
||||||
|
<input type="hidden" name="cat" value="{{.CatName}}">
|
||||||
|
<input type="hidden" name="redirect-to" value="/category/{{.CatName}}">
|
||||||
|
<input type="submit" class="btn" value="{{block `add` .}}Add{{end}}">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{{if len .Hyphae}}
|
||||||
|
<h2>{{block "remove hyphae" .}}Remove hyphae from the category{{end}}</h2>
|
||||||
|
<form method="POST" action="/remove-from-category" class="multi-remove-from-category">
|
||||||
|
<ol>
|
||||||
|
{{range .Hyphae}}
|
||||||
|
<li>
|
||||||
|
<input type="checkbox" name="_{{.}}" id="_{{.}}">
|
||||||
|
<label for="_{{.}}"><a href="/hypha/{{.}}">{{beautifulName .}}</a></label>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
</ol>
|
||||||
|
<input type="hidden" name="cat" value="{{.CatName}}">
|
||||||
|
<input type="hidden" name="redirect-to" value="/edit-category/{{.CatName}}">
|
||||||
|
<input type="submit" class="btn" value="{{block `remove` .}}Remove{{end}}">
|
||||||
|
</form>
|
||||||
|
{{end}}{{end}}
|
||||||
|
</main>
|
||||||
|
{{end}}
|
||||||
107
categories/views.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
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"}}Редактирование категории <a href="/category/{{.}}">{{beautifulName .}}</a>{{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,
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -109,22 +109,22 @@ type Telegram struct {
|
|||||||
// configuration. Call it sometime during the initialization.
|
// configuration. Call it sometime during the initialization.
|
||||||
func ReadConfigFile(path string) error {
|
func ReadConfigFile(path string) error {
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
WikiName: "Lyxi's Vault",
|
WikiName: "Mycorrhiza Wiki",
|
||||||
NaviTitleIcon: "🦇",
|
NaviTitleIcon: "🍄",
|
||||||
Hyphae: Hyphae{
|
Hyphae: Hyphae{
|
||||||
HomeHypha: "home",
|
HomeHypha: "home",
|
||||||
UserHypha: "u",
|
UserHypha: "u",
|
||||||
HeaderLinksHypha: "u/alyxbatte/header",
|
HeaderLinksHypha: "",
|
||||||
RedirectionCategory: "redirection",
|
RedirectionCategory: "redirection",
|
||||||
},
|
},
|
||||||
Network: Network{
|
Network: Network{
|
||||||
ListenAddr: "0.0.0.0:1737",
|
ListenAddr: "127.0.0.1:1737",
|
||||||
URL: "",
|
URL: "",
|
||||||
},
|
},
|
||||||
Authorization: Authorization{
|
Authorization: Authorization{
|
||||||
UseAuth: true,
|
UseAuth: false,
|
||||||
AllowRegistration: false,
|
AllowRegistration: false,
|
||||||
RegistrationLimit: 1,
|
RegistrationLimit: 0,
|
||||||
Locked: false,
|
Locked: false,
|
||||||
UseWhiteList: false,
|
UseWhiteList: false,
|
||||||
WhiteList: []string{},
|
WhiteList: []string{},
|
||||||
@ -6,8 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/static"
|
"github.com/bouncepaw/mycorrhiza/static"
|
||||||
)
|
)
|
||||||
|
|
||||||
var paths struct {
|
var paths struct {
|
||||||
@ -25,7 +25,7 @@ var paths struct {
|
|||||||
// A separate function is needed to easily know where a general storage path is
|
// A separate function is needed to easily know where a general storage path is
|
||||||
// needed rather than a concrete Git or the whole wiki storage path, so that we
|
// needed rather than a concrete Git or the whole wiki storage path, so that we
|
||||||
// could easily refactor things later if we'll ever support different storages.
|
// could easily refactor things later if we'll ever support different storages.
|
||||||
func HyphaeDir() string { return filepath.ToSlash(paths.gitRepo) }
|
func HyphaeDir() string { return paths.gitRepo }
|
||||||
|
|
||||||
// GitRepo returns the path to the Git repository of the wiki.
|
// GitRepo returns the path to the Git repository of the wiki.
|
||||||
func GitRepo() string { return paths.gitRepo }
|
func GitRepo() string { return paths.gitRepo }
|
||||||
43
flag.go
@ -3,27 +3,26 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/version"
|
|
||||||
|
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/files"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CLI options are read and parsed here.
|
// CLI options are read and parsed here.
|
||||||
|
|
||||||
// printHelp prints the help message.
|
// printHelp prints the help message.
|
||||||
func printHelp() {
|
func printHelp() {
|
||||||
_, _ = fmt.Fprintf(
|
fmt.Fprintf(
|
||||||
flag.CommandLine.Output(),
|
flag.CommandLine.Output(),
|
||||||
"Usage: %s WIKI_PATH\n",
|
"Usage: %s WIKI_PATH\n",
|
||||||
os.Args[0],
|
os.Args[0],
|
||||||
@ -32,7 +31,7 @@ func printHelp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseCliArgs parses CLI options and sets several important global variables. Call it early.
|
// parseCliArgs parses CLI options and sets several important global variables. Call it early.
|
||||||
func parseCliArgs() error {
|
func parseCliArgs() {
|
||||||
var createAdminName string
|
var createAdminName string
|
||||||
var versionFlag bool
|
var versionFlag bool
|
||||||
|
|
||||||
@ -43,38 +42,31 @@ func parseCliArgs() error {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if versionFlag {
|
if versionFlag {
|
||||||
slog.Info("Running Mycorrhiza Wiki", "version", version.Long)
|
fmt.Println("Mycorrhiza Wiki", version.Long)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
slog.Error("Pass a wiki directory")
|
log.Fatal("error: pass a wiki directory")
|
||||||
return errors.New("wiki directory not passed")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wikiDir, err := filepath.Abs(args[0])
|
wikiDir, err := filepath.Abs(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to take absolute filepath of wiki directory",
|
log.Fatal(err)
|
||||||
"path", args[0], "err", err)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.WikiDir = wikiDir
|
cfg.WikiDir = wikiDir
|
||||||
|
|
||||||
if createAdminName != "" {
|
if createAdminName != "" {
|
||||||
if err := createAdminCommand(createAdminName); err != nil {
|
createAdminCommand(createAdminName)
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createAdminCommand(name string) error {
|
func createAdminCommand(name string) {
|
||||||
if err := files.PrepareWikiRoot(); err != nil {
|
if err := files.PrepareWikiRoot(); err != nil {
|
||||||
slog.Error("Failed to prepare wiki root", "err", err)
|
log.Fatal(err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
cfg.UseAuth = true
|
cfg.UseAuth = true
|
||||||
cfg.AllowRegistration = true
|
cfg.AllowRegistration = true
|
||||||
@ -82,14 +74,11 @@ func createAdminCommand(name string) error {
|
|||||||
|
|
||||||
password, err := askPass("Password")
|
password, err := askPass("Password")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to prompt password", "err", err)
|
log.Fatal(err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
if err := user.Register(name, password, "admin", "local", true); err != nil {
|
if err := user.Register(name, password, "admin", "local", true); err != nil {
|
||||||
slog.Error("Failed to register admin", "err", err)
|
log.Fatal(err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func askPass(prompt string) (string, error) {
|
func askPass(prompt string) (string, error) {
|
||||||
|
|||||||
19
go.mod
@ -1,20 +1,23 @@
|
|||||||
module github.com/bouncepaw/mycorrhiza
|
module github.com/bouncepaw/mycorrhiza
|
||||||
|
|
||||||
go 1.21
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.sr.ht/~bouncepaw/mycomarkup/v5 v5.6.0
|
git.sr.ht/~bouncepaw/mycomarkup/v5 v5.6.0
|
||||||
github.com/go-ini/ini v1.67.0
|
github.com/go-ini/ini v1.63.2
|
||||||
github.com/gorilla/feeds v1.2.0
|
github.com/gorilla/feeds v1.1.2
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.0
|
||||||
golang.org/x/crypto v0.31.0
|
github.com/valyala/quicktemplate v1.7.0
|
||||||
golang.org/x/term v0.27.0
|
golang.org/x/crypto v0.17.0
|
||||||
golang.org/x/text v0.21.0
|
golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd
|
||||||
|
golang.org/x/term v0.15.0
|
||||||
|
golang.org/x/text v0.14.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/stretchr/testify v1.7.0 // indirect
|
github.com/stretchr/testify v1.7.0 // indirect
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
// Use this trick to test local Mycomarkup changes, replace the path with yours,
|
// Use this trick to test local Mycomarkup changes, replace the path with yours,
|
||||||
|
|||||||
53
go.sum
@ -1,13 +1,20 @@
|
|||||||
|
git.sr.ht/~bouncepaw/mycomarkup/v5 v5.5.0 h1:0Ycy67Leh4E7HGw/Z2xs/VEw6BH68QIpQdpXcJooX7w=
|
||||||
|
git.sr.ht/~bouncepaw/mycomarkup/v5 v5.5.0/go.mod h1:TCzFBqW11En4EjLfcQtJu8C/Ro7FIFR8vZ+nM9f6Q28=
|
||||||
git.sr.ht/~bouncepaw/mycomarkup/v5 v5.6.0 h1:zAZwMF+6x8U/nunpqPRVYoDiqVUMBHI04PG8GsDrFOk=
|
git.sr.ht/~bouncepaw/mycomarkup/v5 v5.6.0 h1:zAZwMF+6x8U/nunpqPRVYoDiqVUMBHI04PG8GsDrFOk=
|
||||||
git.sr.ht/~bouncepaw/mycomarkup/v5 v5.6.0/go.mod h1:TCzFBqW11En4EjLfcQtJu8C/Ro7FIFR8vZ+nM9f6Q28=
|
git.sr.ht/~bouncepaw/mycomarkup/v5 v5.6.0/go.mod h1:TCzFBqW11En4EjLfcQtJu8C/Ro7FIFR8vZ+nM9f6Q28=
|
||||||
|
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||||
|
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
github.com/go-ini/ini v1.63.2 h1:kwN3umicd2HF3Tgvap4um1ZG52/WyKT9GGdPx0CJk6Y=
|
||||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
github.com/go-ini/ini v1.63.2/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
|
github.com/gorilla/feeds v1.1.2 h1:pxzZ5PD3RJdhFH2FsJJ4x6PqMqbgFk1+Vez4XWBW8Iw=
|
||||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
github.com/gorilla/feeds v1.1.2/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
|
||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
|
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||||
|
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@ -19,14 +26,32 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM=
|
||||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
|
golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd h1:zVFyTKZN/Q7mNRWSs1GOYnHM9NiFSJ54YVRsD0rNWT4=
|
||||||
|
golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||||
|
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
= Help
|
= Help
|
||||||
|
|
||||||
This is documentation for Mycorrhiza Wiki 1.15.1. Choose a topic from the list.
|
This is documentation for Mycorrhiza Wiki 1.15. Choose a topic from the list.
|
||||||
|
|
||||||
The documentation is incomplete. If you want to contribute to the documentation, open a pull request or an issue on [[https://github.com/bouncepaw/mycorrhiza | GitHub]] or [[https://lists.sr.ht/~bouncepaw/mycorrhiza-devel | send a patch]].
|
The documentation is incomplete. If you want to contribute to the documentation, open a pull request or an issue on [[https://github.com/bouncepaw/mycorrhiza | GitHub]] or [[https://lists.sr.ht/~bouncepaw/mycorrhiza-devel | send a patch]].
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ You can upload any media file, but only those listed below will be displayed on
|
|||||||
|
|
||||||
* **Images:** jpg, gif, png, webp, svg, ico
|
* **Images:** jpg, gif, png, webp, svg, ico
|
||||||
* **Video:** ogg, webm, mp4
|
* **Video:** ogg, webm, mp4
|
||||||
* **Audio:** ogg, webm, mp3, flac, wav
|
* **Audio:** ogg, webm, mp3
|
||||||
|
|
||||||
== How to upload media?
|
== How to upload media?
|
||||||
For non-existent hyphae, upload a file in the //Upload media// section.
|
For non-existent hyphae, upload a file in the //Upload media// section.
|
||||||
|
|||||||
10
help/web.go
@ -2,17 +2,15 @@ package help
|
|||||||
|
|
||||||
// stuff.go is used for meta stuff about the wiki or all hyphae at once.
|
// stuff.go is used for meta stuff about the wiki or all hyphae at once.
|
||||||
import (
|
import (
|
||||||
|
"git.sr.ht/~bouncepaw/mycomarkup/v5"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/mycoopts"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/viewutil"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"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"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
|
|
||||||
"github.com/gorilla/feeds"
|
"github.com/gorilla/feeds"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -4,12 +4,12 @@ package history
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
"github.com/bouncepaw/mycorrhiza/files"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,14 +21,12 @@ var renameMsgPattern = regexp.MustCompile(`^Rename ‘(.*)’ to ‘.*’`)
|
|||||||
var gitEnv = []string{"GIT_COMMITTER_NAME=wikimind", "GIT_COMMITTER_EMAIL=wikimind@mycorrhiza"}
|
var gitEnv = []string{"GIT_COMMITTER_NAME=wikimind", "GIT_COMMITTER_EMAIL=wikimind@mycorrhiza"}
|
||||||
|
|
||||||
// Start finds git and initializes git credentials.
|
// Start finds git and initializes git credentials.
|
||||||
func Start() error {
|
func Start() {
|
||||||
path, err := exec.LookPath("git")
|
path, err := exec.LookPath("git")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Could not find the Git executable. Check your $PATH.")
|
log.Fatal("Could not find the git executable. Check your $PATH.")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
gitpath = path
|
gitpath = path
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitGitRepo checks a Git repository and initializes it if necessary.
|
// InitGitRepo checks a Git repository and initializes it if necessary.
|
||||||
@ -46,7 +44,7 @@ func InitGitRepo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !isGitRepo {
|
if !isGitRepo {
|
||||||
slog.Info("Initializing Git repo", "path", files.HyphaeDir())
|
log.Println("Initializing Git repo at", files.HyphaeDir())
|
||||||
gitsh("init")
|
gitsh("init")
|
||||||
gitsh("config", "core.quotePath", "false")
|
gitsh("config", "core.quotePath", "false")
|
||||||
}
|
}
|
||||||
@ -62,7 +60,7 @@ func gitsh(args ...string) (out bytes.Buffer, err error) {
|
|||||||
|
|
||||||
b, err := cmd.CombinedOutput()
|
b, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Info("Git command failed", "err", err, "output", string(b))
|
log.Println("gitsh:", err)
|
||||||
}
|
}
|
||||||
return *bytes.NewBuffer(b), err
|
return *bytes.NewBuffer(b), err
|
||||||
}
|
}
|
||||||
@ -79,9 +77,7 @@ func silentGitsh(args ...string) (out bytes.Buffer, err error) {
|
|||||||
|
|
||||||
// Rename renames from `from` to `to` using `git mv`.
|
// Rename renames from `from` to `to` using `git mv`.
|
||||||
func Rename(from, to string) error {
|
func Rename(from, to string) error {
|
||||||
slog.Info("Renaming file with git mv",
|
log.Println(util.ShorterPath(from), util.ShorterPath(to))
|
||||||
"from", util.ShorterPath(from),
|
|
||||||
"to", util.ShorterPath(to))
|
|
||||||
_, err := gitsh("mv", "--force", from, to)
|
_, err := gitsh("mv", "--force", from, to)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,21 +4,19 @@ package histweb
|
|||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"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/util"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/viewutil"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log/slog"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitHandlers(rtr *mux.Router) {
|
func InitHandlers(rtr *mux.Router) {
|
||||||
@ -83,9 +81,7 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
list = history.WithRevisions(hyphaName, revs)
|
list = history.WithRevisions(hyphaName, revs)
|
||||||
}
|
}
|
||||||
|
log.Println("Found", len(revs), "revisions for", hyphaName)
|
||||||
// TODO: extra log, not needed?
|
|
||||||
slog.Info("Found revisions", "hyphaName", hyphaName, "n", len(revs), "err", err)
|
|
||||||
|
|
||||||
historyView(viewutil.MetaFrom(w, rq), hyphaName, list)
|
historyView(viewutil.MetaFrom(w, rq), hyphaName, list)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -2,72 +2,18 @@ package history
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"log"
|
||||||
"log/slog"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"github.com/bouncepaw/mycorrhiza/files"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithRevisions returns an HTML representation of `revs` that is meant to be inserted in a history page.
|
// Revision represents a revision, duh. Hash is usually short. Username is extracted from email.
|
||||||
func WithRevisions(hyphaName string, revs []Revision) string {
|
|
||||||
var buf strings.Builder
|
|
||||||
|
|
||||||
for _, grp := range groupRevisionsByMonth(revs) {
|
|
||||||
currentYear := grp[0].Time.Year()
|
|
||||||
currentMonth := grp[0].Time.Month()
|
|
||||||
sectionId := fmt.Sprintf("%04d-%02d", currentYear, currentMonth)
|
|
||||||
|
|
||||||
buf.WriteString(fmt.Sprintf(
|
|
||||||
`<section class="history__month">
|
|
||||||
<a href="#%s" class="history__month-anchor">
|
|
||||||
<h2 id="%s" class="history__month-title">%d %s</h2>
|
|
||||||
</a>
|
|
||||||
<ul class="history__entries">`,
|
|
||||||
sectionId, sectionId, currentYear, currentMonth.String(),
|
|
||||||
))
|
|
||||||
|
|
||||||
for _, rev := range grp {
|
|
||||||
buf.WriteString(fmt.Sprintf(
|
|
||||||
`<li class="history__entry">
|
|
||||||
<a class="history-entry" href="/rev/%s/%s">
|
|
||||||
<time class="history-entry__time">%s</time>
|
|
||||||
</a>
|
|
||||||
<span class="history-entry__hash"><a href="/primitive-diff/%s/%s">%s</a></span>
|
|
||||||
<span class="history-entry__msg">%s</span>`,
|
|
||||||
rev.Hash, hyphaName,
|
|
||||||
rev.timeToDisplay(),
|
|
||||||
rev.Hash, hyphaName, rev.Hash,
|
|
||||||
html.EscapeString(rev.Message),
|
|
||||||
))
|
|
||||||
|
|
||||||
if rev.Username != "anon" {
|
|
||||||
buf.WriteString(fmt.Sprintf(
|
|
||||||
`<span class="history-entry__author">by <a href="/hypha/%s/%s" rel="author">%s</a></span>`,
|
|
||||||
cfg.UserHypha, rev.Username, rev.Username,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteString("</li>\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteString(`</ul></section>`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Revision represents a revision of a hypha.
|
|
||||||
type Revision struct {
|
type Revision struct {
|
||||||
// Hash is usually short.
|
|
||||||
Hash string
|
Hash string
|
||||||
// Username is extracted from email.
|
|
||||||
Username string
|
Username string
|
||||||
Time time.Time
|
Time time.Time
|
||||||
Message string
|
Message string
|
||||||
@ -75,62 +21,6 @@ type Revision struct {
|
|||||||
hyphaeAffectedBuf []string
|
hyphaeAffectedBuf []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// HyphaeDiffsHTML returns a comma-separated list of diffs links of current revision for every affected file as HTML string.
|
|
||||||
func (rev Revision) HyphaeDiffsHTML() string {
|
|
||||||
entries := rev.hyphaeAffected()
|
|
||||||
if len(entries) == 1 {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
`<a href="/primitive-diff/%s/%s">%s</a>`,
|
|
||||||
rev.Hash, entries[0], rev.Hash,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf strings.Builder
|
|
||||||
for i, hyphaName := range entries {
|
|
||||||
if i > 0 {
|
|
||||||
buf.WriteString(`<span aria-hidden="true">, </span>`)
|
|
||||||
}
|
|
||||||
buf.WriteString(`<a href="/primitive-diff/`)
|
|
||||||
buf.WriteString(rev.Hash)
|
|
||||||
buf.WriteString(`/`)
|
|
||||||
buf.WriteString(hyphaName)
|
|
||||||
buf.WriteString(`">`)
|
|
||||||
if i == 0 {
|
|
||||||
buf.WriteString(rev.Hash)
|
|
||||||
buf.WriteString(" ")
|
|
||||||
}
|
|
||||||
buf.WriteString(hyphaName)
|
|
||||||
buf.WriteString(`</a>`)
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// descriptionForFeed generates a good enough HTML contents for a web feed.
|
|
||||||
func (rev *Revision) descriptionForFeed() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
`<p><b>%s</b> (by %s at %s)</p>
|
|
||||||
<p>Hyphae affected: %s</p>
|
|
||||||
<pre><code>%s</code></pre>`,
|
|
||||||
rev.Message, rev.Username, rev.TimeString(),
|
|
||||||
rev.HyphaeLinksHTML(),
|
|
||||||
rev.textDiff(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string.
|
|
||||||
func (rev Revision) HyphaeLinksHTML() string {
|
|
||||||
var buf strings.Builder
|
|
||||||
for i, hyphaName := range rev.hyphaeAffected() {
|
|
||||||
if i > 0 {
|
|
||||||
buf.WriteString(`<span aria-hidden="true">, <span>`)
|
|
||||||
}
|
|
||||||
|
|
||||||
urlSafeHyphaName := url.PathEscape(hyphaName)
|
|
||||||
buf.WriteString(fmt.Sprintf(`<a href="/hypha/%s">%s</a>`, urlSafeHyphaName, hyphaName))
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// gitLog calls `git log` and parses the results.
|
// gitLog calls `git log` and parses the results.
|
||||||
func gitLog(args ...string) ([]Revision, error) {
|
func gitLog(args ...string) ([]Revision, error) {
|
||||||
args = append([]string{
|
args = append([]string{
|
||||||
@ -181,9 +71,7 @@ func (stream *recentChangesStream) next(n int) []Revision {
|
|||||||
|
|
||||||
res, err := gitLog(args...)
|
res, err := gitLog(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: return error
|
log.Fatal(err)
|
||||||
slog.Error("Failed to git log", "err", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
if len(res) != 0 {
|
if len(res) != 0 {
|
||||||
stream.currHash = res[len(res)-1].Hash
|
stream.currHash = res[len(res)-1].Hash
|
||||||
@ -215,14 +103,14 @@ func (stream recentChangesStream) iterator() func() (Revision, bool) {
|
|||||||
func RecentChanges(n int) []Revision {
|
func RecentChanges(n int) []Revision {
|
||||||
stream := newRecentChangesStream()
|
stream := newRecentChangesStream()
|
||||||
revs := stream.next(n)
|
revs := stream.next(n)
|
||||||
slog.Info("Found recent changes", "n", len(revs))
|
log.Printf("Found %d recent changes", len(revs))
|
||||||
return revs
|
return revs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Revisions returns slice of revisions for the given hypha name, ordered most recent first.
|
// Revisions returns slice of revisions for the given hypha name, ordered most recent first.
|
||||||
func Revisions(hyphaName string) ([]Revision, error) {
|
func Revisions(hyphaName string) ([]Revision, error) {
|
||||||
revs, err := gitLog("--", hyphaName+".*")
|
revs, err := gitLog("--", hyphaName+".*")
|
||||||
slog.Info("Found revisions", "hyphaName", hyphaName, "n", len(revs), "err", err)
|
log.Printf("Found %d revisions for ‘%s’\n", len(revs), hyphaName)
|
||||||
return revs, err
|
return revs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
77
history/view.qtpl
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
{% import "fmt" %}
|
||||||
|
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
|
||||||
|
|
||||||
|
HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string.
|
||||||
|
{% func (rev Revision) HyphaeLinksHTML() %}
|
||||||
|
{% stripspace %}
|
||||||
|
{% for i, hyphaName := range rev.hyphaeAffected() %}
|
||||||
|
{% if i > 0 %}
|
||||||
|
<span aria-hidden="true">, </span>
|
||||||
|
{% endif %}
|
||||||
|
<a href="/hypha/{%s hyphaName %}">{%s hyphaName %}</a>
|
||||||
|
{% endfor %}
|
||||||
|
{% endstripspace %}
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
|
||||||
|
HyphaeDiffsHTML returns a comma-separated list of diffs links of current revision for every affected file as HTML string.
|
||||||
|
{% func (rev Revision) HyphaeDiffsHTML() %}
|
||||||
|
{% code entries := rev.hyphaeAffected() %}
|
||||||
|
{% stripspace %}
|
||||||
|
{% if len(entries) == 1 %}
|
||||||
|
<a href="/primitive-diff/{%s rev.Hash %}/{%s entries[0] %}">{%s rev.Hash %}</a>
|
||||||
|
{% else %}
|
||||||
|
{% for i, hyphaName := range entries %}
|
||||||
|
{% if i > 0 %}
|
||||||
|
<span aria-hidden="true">, </span>
|
||||||
|
{% endif %}
|
||||||
|
<a href="/primitive-diff/{%s rev.Hash %}/{%s hyphaName %}">
|
||||||
|
{% if i == 0 %}
|
||||||
|
{%s rev.Hash %}
|
||||||
|
{% endif %}
|
||||||
|
{%s hyphaName %}</a>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endstripspace %}
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
descriptionForFeed generates a good enough HTML contents for a web feed.
|
||||||
|
{% func (rev *Revision) descriptionForFeed() %}
|
||||||
|
<p><b>{%s rev.Message %}</b> (by {%s rev.Username %} at {%s rev.TimeString() %})</p>
|
||||||
|
<p>Hyphae affected: {%= rev.HyphaeLinksHTML() %}</p>
|
||||||
|
<pre><code>{%s rev.textDiff() %}</code></pre>
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
WithRevisions returns an html representation of `revs` that is meant to be inserted in a history page.
|
||||||
|
{% func WithRevisions(hyphaName string, revs []Revision) %}
|
||||||
|
{% for _, grp := range groupRevisionsByMonth(revs) %}
|
||||||
|
{% code
|
||||||
|
currentYear := grp[0].Time.Year()
|
||||||
|
currentMonth := grp[0].Time.Month()
|
||||||
|
sectionId := fmt.Sprintf("%04d-%02d", currentYear, currentMonth)
|
||||||
|
%}
|
||||||
|
<section class="history__month">
|
||||||
|
<a href="#{%s sectionId %}" class="history__month-anchor">
|
||||||
|
<h2 id="{%s sectionId %}" class="history__month-title">{%d currentYear %} {%s currentMonth.String() %}</h2>
|
||||||
|
</a>
|
||||||
|
<ul class="history__entries">
|
||||||
|
{% for _, rev := range grp %}
|
||||||
|
{%= rev.asHistoryEntry(hyphaName) %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% func (rev *Revision) asHistoryEntry(hyphaName string) %}
|
||||||
|
<li class="history__entry">
|
||||||
|
<a class="history-entry" href="/rev/{%s rev.Hash %}/{%s hyphaName %}">
|
||||||
|
<time class="history-entry__time">{%s rev.timeToDisplay() %}</time>
|
||||||
|
</a>
|
||||||
|
<span class="history-entry__hash"><a href="/primitive-diff/{%s rev.Hash %}/{%s hyphaName %}">{%s rev.Hash %}</a></span>
|
||||||
|
<span class="history-entry__msg">{%s rev.Message %}</span>
|
||||||
|
{% if rev.Username != "anon" %}
|
||||||
|
<span class="history-entry__author">by <a href="/hypha/{%s cfg.UserHypha %}/{%s rev.Username %}" rel="author">{%s rev.Username %}</a></span>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfunc %}
|
||||||
423
history/view.qtpl.go
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
// Code generated by qtc from "view.qtpl". DO NOT EDIT.
|
||||||
|
// See https://github.com/valyala/quicktemplate for details.
|
||||||
|
|
||||||
|
//line history/view.qtpl:1
|
||||||
|
package history
|
||||||
|
|
||||||
|
//line history/view.qtpl:1
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
//line history/view.qtpl:2
|
||||||
|
import "github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
|
|
||||||
|
// HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string.
|
||||||
|
|
||||||
|
//line history/view.qtpl:5
|
||||||
|
import (
|
||||||
|
qtio422016 "io"
|
||||||
|
|
||||||
|
qt422016 "github.com/valyala/quicktemplate"
|
||||||
|
)
|
||||||
|
|
||||||
|
//line history/view.qtpl:5
|
||||||
|
var (
|
||||||
|
_ = qtio422016.Copy
|
||||||
|
_ = qt422016.AcquireByteBuffer
|
||||||
|
)
|
||||||
|
|
||||||
|
//line history/view.qtpl:5
|
||||||
|
func (rev Revision) StreamHyphaeLinksHTML(qw422016 *qt422016.Writer) {
|
||||||
|
//line history/view.qtpl:5
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line history/view.qtpl:7
|
||||||
|
for i, hyphaName := range rev.hyphaeAffected() {
|
||||||
|
//line history/view.qtpl:8
|
||||||
|
if i > 0 {
|
||||||
|
//line history/view.qtpl:8
|
||||||
|
qw422016.N().S(`<span aria-hidden="true">, </span>`)
|
||||||
|
//line history/view.qtpl:10
|
||||||
|
}
|
||||||
|
//line history/view.qtpl:10
|
||||||
|
qw422016.N().S(`<a href="/hypha/`)
|
||||||
|
//line history/view.qtpl:11
|
||||||
|
qw422016.E().S(hyphaName)
|
||||||
|
//line history/view.qtpl:11
|
||||||
|
qw422016.N().S(`">`)
|
||||||
|
//line history/view.qtpl:11
|
||||||
|
qw422016.E().S(hyphaName)
|
||||||
|
//line history/view.qtpl:11
|
||||||
|
qw422016.N().S(`</a>`)
|
||||||
|
//line history/view.qtpl:12
|
||||||
|
}
|
||||||
|
//line history/view.qtpl:13
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line history/view.qtpl:14
|
||||||
|
}
|
||||||
|
|
||||||
|
//line history/view.qtpl:14
|
||||||
|
func (rev Revision) WriteHyphaeLinksHTML(qq422016 qtio422016.Writer) {
|
||||||
|
//line history/view.qtpl:14
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line history/view.qtpl:14
|
||||||
|
rev.StreamHyphaeLinksHTML(qw422016)
|
||||||
|
//line history/view.qtpl:14
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line history/view.qtpl:14
|
||||||
|
}
|
||||||
|
|
||||||
|
//line history/view.qtpl:14
|
||||||
|
func (rev Revision) HyphaeLinksHTML() string {
|
||||||
|
//line history/view.qtpl:14
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line history/view.qtpl:14
|
||||||
|
rev.WriteHyphaeLinksHTML(qb422016)
|
||||||
|
//line history/view.qtpl:14
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line history/view.qtpl:14
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line history/view.qtpl:14
|
||||||
|
return qs422016
|
||||||
|
//line history/view.qtpl:14
|
||||||
|
}
|
||||||
|
|
||||||
|
// HyphaeDiffsHTML returns a comma-separated list of diffs links of current revision for every affected file as HTML string.
|
||||||
|
|
||||||
|
//line history/view.qtpl:18
|
||||||
|
func (rev Revision) StreamHyphaeDiffsHTML(qw422016 *qt422016.Writer) {
|
||||||
|
//line history/view.qtpl:18
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line history/view.qtpl:19
|
||||||
|
entries := rev.hyphaeAffected()
|
||||||
|
|
||||||
|
//line history/view.qtpl:19
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line history/view.qtpl:21
|
||||||
|
if len(entries) == 1 {
|
||||||
|
//line history/view.qtpl:21
|
||||||
|
qw422016.N().S(`<a href="/primitive-diff/`)
|
||||||
|
//line history/view.qtpl:22
|
||||||
|
qw422016.E().S(rev.Hash)
|
||||||
|
//line history/view.qtpl:22
|
||||||
|
qw422016.N().S(`/`)
|
||||||
|
//line history/view.qtpl:22
|
||||||
|
qw422016.E().S(entries[0])
|
||||||
|
//line history/view.qtpl:22
|
||||||
|
qw422016.N().S(`">`)
|
||||||
|
//line history/view.qtpl:22
|
||||||
|
qw422016.E().S(rev.Hash)
|
||||||
|
//line history/view.qtpl:22
|
||||||
|
qw422016.N().S(`</a>`)
|
||||||
|
//line history/view.qtpl:23
|
||||||
|
} else {
|
||||||
|
//line history/view.qtpl:24
|
||||||
|
for i, hyphaName := range entries {
|
||||||
|
//line history/view.qtpl:25
|
||||||
|
if i > 0 {
|
||||||
|
//line history/view.qtpl:25
|
||||||
|
qw422016.N().S(`<span aria-hidden="true">, </span>`)
|
||||||
|
//line history/view.qtpl:27
|
||||||
|
}
|
||||||
|
//line history/view.qtpl:27
|
||||||
|
qw422016.N().S(`<a href="/primitive-diff/`)
|
||||||
|
//line history/view.qtpl:28
|
||||||
|
qw422016.E().S(rev.Hash)
|
||||||
|
//line history/view.qtpl:28
|
||||||
|
qw422016.N().S(`/`)
|
||||||
|
//line history/view.qtpl:28
|
||||||
|
qw422016.E().S(hyphaName)
|
||||||
|
//line history/view.qtpl:28
|
||||||
|
qw422016.N().S(`">`)
|
||||||
|
//line history/view.qtpl:29
|
||||||
|
if i == 0 {
|
||||||
|
//line history/view.qtpl:30
|
||||||
|
qw422016.E().S(rev.Hash)
|
||||||
|
//line history/view.qtpl:30
|
||||||
|
qw422016.N().S(` `)
|
||||||
|
//line history/view.qtpl:31
|
||||||
|
}
|
||||||
|
//line history/view.qtpl:32
|
||||||
|
qw422016.E().S(hyphaName)
|
||||||
|
//line history/view.qtpl:32
|
||||||
|
qw422016.N().S(`</a>`)
|
||||||
|
//line history/view.qtpl:33
|
||||||
|
}
|
||||||
|
//line history/view.qtpl:34
|
||||||
|
}
|
||||||
|
//line history/view.qtpl:35
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line history/view.qtpl:36
|
||||||
|
}
|
||||||
|
|
||||||
|
//line history/view.qtpl:36
|
||||||
|
func (rev Revision) WriteHyphaeDiffsHTML(qq422016 qtio422016.Writer) {
|
||||||
|
//line history/view.qtpl:36
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line history/view.qtpl:36
|
||||||
|
rev.StreamHyphaeDiffsHTML(qw422016)
|
||||||
|
//line history/view.qtpl:36
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line history/view.qtpl:36
|
||||||
|
}
|
||||||
|
|
||||||
|
//line history/view.qtpl:36
|
||||||
|
func (rev Revision) HyphaeDiffsHTML() string {
|
||||||
|
//line history/view.qtpl:36
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line history/view.qtpl:36
|
||||||
|
rev.WriteHyphaeDiffsHTML(qb422016)
|
||||||
|
//line history/view.qtpl:36
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line history/view.qtpl:36
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line history/view.qtpl:36
|
||||||
|
return qs422016
|
||||||
|
//line history/view.qtpl:36
|
||||||
|
}
|
||||||
|
|
||||||
|
// descriptionForFeed generates a good enough HTML contents for a web feed.
|
||||||
|
|
||||||
|
//line history/view.qtpl:39
|
||||||
|
func (rev *Revision) streamdescriptionForFeed(qw422016 *qt422016.Writer) {
|
||||||
|
//line history/view.qtpl:39
|
||||||
|
qw422016.N().S(`
|
||||||
|
<p><b>`)
|
||||||
|
//line history/view.qtpl:40
|
||||||
|
qw422016.E().S(rev.Message)
|
||||||
|
//line history/view.qtpl:40
|
||||||
|
qw422016.N().S(`</b> (by `)
|
||||||
|
//line history/view.qtpl:40
|
||||||
|
qw422016.E().S(rev.Username)
|
||||||
|
//line history/view.qtpl:40
|
||||||
|
qw422016.N().S(` at `)
|
||||||
|
//line history/view.qtpl:40
|
||||||
|
qw422016.E().S(rev.TimeString())
|
||||||
|
//line history/view.qtpl:40
|
||||||
|
qw422016.N().S(`)</p>
|
||||||
|
<p>Hyphae affected: `)
|
||||||
|
//line history/view.qtpl:41
|
||||||
|
rev.StreamHyphaeLinksHTML(qw422016)
|
||||||
|
//line history/view.qtpl:41
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
<pre><code>`)
|
||||||
|
//line history/view.qtpl:42
|
||||||
|
qw422016.E().S(rev.textDiff())
|
||||||
|
//line history/view.qtpl:42
|
||||||
|
qw422016.N().S(`</code></pre>
|
||||||
|
`)
|
||||||
|
//line history/view.qtpl:43
|
||||||
|
}
|
||||||
|
|
||||||
|
//line history/view.qtpl:43
|
||||||
|
func (rev *Revision) writedescriptionForFeed(qq422016 qtio422016.Writer) {
|
||||||
|
//line history/view.qtpl:43
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line history/view.qtpl:43
|
||||||
|
rev.streamdescriptionForFeed(qw422016)
|
||||||
|
//line history/view.qtpl:43
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line history/view.qtpl:43
|
||||||
|
}
|
||||||
|
|
||||||
|
//line history/view.qtpl:43
|
||||||
|
func (rev *Revision) descriptionForFeed() string {
|
||||||
|
//line history/view.qtpl:43
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line history/view.qtpl:43
|
||||||
|
rev.writedescriptionForFeed(qb422016)
|
||||||
|
//line history/view.qtpl:43
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line history/view.qtpl:43
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line history/view.qtpl:43
|
||||||
|
return qs422016
|
||||||
|
//line history/view.qtpl:43
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRevisions returns an html representation of `revs` that is meant to be inserted in a history page.
|
||||||
|
|
||||||
|
//line history/view.qtpl:46
|
||||||
|
func StreamWithRevisions(qw422016 *qt422016.Writer, hyphaName string, revs []Revision) {
|
||||||
|
//line history/view.qtpl:46
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line history/view.qtpl:47
|
||||||
|
for _, grp := range groupRevisionsByMonth(revs) {
|
||||||
|
//line history/view.qtpl:47
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line history/view.qtpl:49
|
||||||
|
currentYear := grp[0].Time.Year()
|
||||||
|
currentMonth := grp[0].Time.Month()
|
||||||
|
sectionId := fmt.Sprintf("%04d-%02d", currentYear, currentMonth)
|
||||||
|
|
||||||
|
//line history/view.qtpl:52
|
||||||
|
qw422016.N().S(`
|
||||||
|
<section class="history__month">
|
||||||
|
<a href="#`)
|
||||||
|
//line history/view.qtpl:54
|
||||||
|
qw422016.E().S(sectionId)
|
||||||
|
//line history/view.qtpl:54
|
||||||
|
qw422016.N().S(`" class="history__month-anchor">
|
||||||
|
<h2 id="`)
|
||||||
|
//line history/view.qtpl:55
|
||||||
|
qw422016.E().S(sectionId)
|
||||||
|
//line history/view.qtpl:55
|
||||||
|
qw422016.N().S(`" class="history__month-title">`)
|
||||||
|
//line history/view.qtpl:55
|
||||||
|
qw422016.N().D(currentYear)
|
||||||
|
//line history/view.qtpl:55
|
||||||
|
qw422016.N().S(` `)
|
||||||
|
//line history/view.qtpl:55
|
||||||
|
qw422016.E().S(currentMonth.String())
|
||||||
|
//line history/view.qtpl:55
|
||||||
|
qw422016.N().S(`</h2>
|
||||||
|
</a>
|
||||||
|
<ul class="history__entries">
|
||||||
|
`)
|
||||||
|
//line history/view.qtpl:58
|
||||||
|
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
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line history/view.qtpl:60
|
||||||
|
}
|
||||||
|
//line history/view.qtpl:60
|
||||||
|
qw422016.N().S(`
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
`)
|
||||||
|
//line history/view.qtpl:63
|
||||||
|
}
|
||||||
|
//line history/view.qtpl:63
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line history/view.qtpl:64
|
||||||
|
}
|
||||||
|
|
||||||
|
//line history/view.qtpl:64
|
||||||
|
func WriteWithRevisions(qq422016 qtio422016.Writer, hyphaName string, revs []Revision) {
|
||||||
|
//line history/view.qtpl:64
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line history/view.qtpl:64
|
||||||
|
StreamWithRevisions(qw422016, hyphaName, revs)
|
||||||
|
//line history/view.qtpl:64
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line history/view.qtpl:64
|
||||||
|
}
|
||||||
|
|
||||||
|
//line history/view.qtpl:64
|
||||||
|
func WithRevisions(hyphaName string, revs []Revision) string {
|
||||||
|
//line history/view.qtpl:64
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line history/view.qtpl:64
|
||||||
|
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(`
|
||||||
|
<li class="history__entry">
|
||||||
|
<a class="history-entry" href="/rev/`)
|
||||||
|
//line history/view.qtpl:68
|
||||||
|
qw422016.E().S(rev.Hash)
|
||||||
|
//line history/view.qtpl:68
|
||||||
|
qw422016.N().S(`/`)
|
||||||
|
//line history/view.qtpl:68
|
||||||
|
qw422016.E().S(hyphaName)
|
||||||
|
//line history/view.qtpl:68
|
||||||
|
qw422016.N().S(`">
|
||||||
|
<time class="history-entry__time">`)
|
||||||
|
//line history/view.qtpl:69
|
||||||
|
qw422016.E().S(rev.timeToDisplay())
|
||||||
|
//line history/view.qtpl:69
|
||||||
|
qw422016.N().S(`</time>
|
||||||
|
</a>
|
||||||
|
<span class="history-entry__hash"><a href="/primitive-diff/`)
|
||||||
|
//line history/view.qtpl:71
|
||||||
|
qw422016.E().S(rev.Hash)
|
||||||
|
//line history/view.qtpl:71
|
||||||
|
qw422016.N().S(`/`)
|
||||||
|
//line history/view.qtpl:71
|
||||||
|
qw422016.E().S(hyphaName)
|
||||||
|
//line history/view.qtpl:71
|
||||||
|
qw422016.N().S(`">`)
|
||||||
|
//line history/view.qtpl:71
|
||||||
|
qw422016.E().S(rev.Hash)
|
||||||
|
//line history/view.qtpl:71
|
||||||
|
qw422016.N().S(`</a></span>
|
||||||
|
<span class="history-entry__msg">`)
|
||||||
|
//line history/view.qtpl:72
|
||||||
|
qw422016.E().S(rev.Message)
|
||||||
|
//line history/view.qtpl:72
|
||||||
|
qw422016.N().S(`</span>
|
||||||
|
`)
|
||||||
|
//line history/view.qtpl:73
|
||||||
|
if rev.Username != "anon" {
|
||||||
|
//line history/view.qtpl:73
|
||||||
|
qw422016.N().S(`
|
||||||
|
<span class="history-entry__author">by <a href="/hypha/`)
|
||||||
|
//line history/view.qtpl:74
|
||||||
|
qw422016.E().S(cfg.UserHypha)
|
||||||
|
//line history/view.qtpl:74
|
||||||
|
qw422016.N().S(`/`)
|
||||||
|
//line history/view.qtpl:74
|
||||||
|
qw422016.E().S(rev.Username)
|
||||||
|
//line history/view.qtpl:74
|
||||||
|
qw422016.N().S(`" rel="author">`)
|
||||||
|
//line history/view.qtpl:74
|
||||||
|
qw422016.E().S(rev.Username)
|
||||||
|
//line history/view.qtpl:74
|
||||||
|
qw422016.N().S(`</a></span>
|
||||||
|
`)
|
||||||
|
//line history/view.qtpl:75
|
||||||
|
}
|
||||||
|
//line history/view.qtpl:75
|
||||||
|
qw422016.N().S(`
|
||||||
|
</li>
|
||||||
|
`)
|
||||||
|
//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
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line history/view.qtpl:77
|
||||||
|
return qs422016
|
||||||
|
//line history/view.qtpl:77
|
||||||
|
}
|
||||||
61
httpd.go
@ -1,18 +1,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"log"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveHTTP(handler http.Handler) (err error) {
|
func serveHTTP(handler http.Handler) {
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
ReadTimeout: 300 * time.Second,
|
ReadTimeout: 300 * time.Second,
|
||||||
WriteTimeout: 300 * time.Second,
|
WriteTimeout: 300 * time.Second,
|
||||||
@ -21,51 +20,35 @@ func serveHTTP(handler http.Handler) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(cfg.ListenAddr, "/") {
|
if strings.HasPrefix(cfg.ListenAddr, "/") {
|
||||||
err = startUnixSocketServer(server, cfg.ListenAddr)
|
startUnixSocketServer(server, cfg.ListenAddr)
|
||||||
} else {
|
} else {
|
||||||
server.Addr = cfg.ListenAddr
|
server.Addr = cfg.ListenAddr
|
||||||
err = startHTTPServer(server)
|
startHTTPServer(server)
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func startUnixSocketServer(server *http.Server, socketPath string) error {
|
func startUnixSocketServer(server *http.Server, socketFile string) {
|
||||||
err := os.Remove(socketPath)
|
os.Remove(socketFile)
|
||||||
|
|
||||||
|
listener, err := net.Listen("unix", socketFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Failed to clean up old socket", "err", err)
|
log.Fatalf("Failed to start a server: %v", err)
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
if err := os.Chmod(socketFile, 0666); err != nil {
|
||||||
|
log.Fatalf("Failed to set socket permissions: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
listener, err := net.Listen("unix", socketPath)
|
log.Printf("Listening on Unix socket %s", cfg.ListenAddr)
|
||||||
if err != nil {
|
if err := server.Serve(listener); err != http.ErrServerClosed {
|
||||||
slog.Error("Failed to start the server", "err", err)
|
log.Fatalf("Failed to start a server: %v", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
defer func(listener net.Listener) {
|
|
||||||
_ = listener.Close()
|
|
||||||
}(listener)
|
|
||||||
|
|
||||||
if err := os.Chmod(socketPath, 0666); err != nil {
|
|
||||||
slog.Error("Failed to set socket permissions", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("Listening Unix socket", "addr", socketPath)
|
|
||||||
|
|
||||||
if err := server.Serve(listener); !errors.Is(err, http.ErrServerClosed) {
|
|
||||||
slog.Error("Failed to start the server", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func startHTTPServer(server *http.Server) error {
|
func startHTTPServer(server *http.Server) {
|
||||||
slog.Info("Listening over HTTP", "addr", server.Addr)
|
log.Printf("Listening on %s", server.Addr)
|
||||||
|
if err := server.ListenAndServe(); err != http.ErrServerClosed {
|
||||||
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
log.Fatalf("Failed to start a server: %v", err)
|
||||||
slog.Error("Failed to start the server", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
package hyphae
|
package hyphae
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExistingHypha is not EmptyHypha. *MediaHypha and *TextualHypha implement this interface.
|
// ExistingHypha is not EmptyHypha. *MediaHypha and *TextualHypha implement this interface.
|
||||||
@ -1,11 +1,12 @@
|
|||||||
package hyphae
|
package hyphae
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/mimetype"
|
"github.com/bouncepaw/mycorrhiza/mimetype"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Index finds all hypha files in the full `path` and saves them to the hypha storage.
|
// Index finds all hypha files in the full `path` and saves them to the hypha storage.
|
||||||
@ -51,7 +52,7 @@ func Index(path string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
slog.Info("Indexed hyphae", "n", Count())
|
log.Println("Indexed", Count(), "hyphae")
|
||||||
}
|
}
|
||||||
|
|
||||||
// indexHelper finds all hypha files in the full `path` and sends them to the
|
// indexHelper finds all hypha files in the full `path` and sends them to the
|
||||||
@ -60,8 +61,7 @@ func Index(path string) {
|
|||||||
func indexHelper(path string, nestLevel uint, ch chan ExistingHypha) {
|
func indexHelper(path string, nestLevel uint, ch chan ExistingHypha) {
|
||||||
nodes, err := os.ReadDir(path)
|
nodes, err := os.ReadDir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to read directory", "path", path, "err", err)
|
log.Fatal(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
@ -73,7 +73,7 @@ func indexHelper(path string, nestLevel uint, ch chan ExistingHypha) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hyphaPartPath = filepath.ToSlash(filepath.Join(path, node.Name()))
|
hyphaPartPath = filepath.Join(path, node.Name())
|
||||||
hyphaName, isText, skip = mimetype.DataFromFilename(hyphaPartPath)
|
hyphaName, isText, skip = mimetype.DataFromFilename(hyphaPartPath)
|
||||||
)
|
)
|
||||||
if !skip {
|
if !skip {
|
||||||
@ -1,10 +1,9 @@
|
|||||||
package hyphae
|
package hyphae
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bouncepaw/mycorrhiza/files"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MediaHypha struct {
|
type MediaHypha struct {
|
||||||
@ -2,19 +2,72 @@ package hypview
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/backlinks"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log/slog"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"github.com/bouncepaw/mycorrhiza/viewutil"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
//go:embed *.html
|
//go:embed *.html
|
||||||
fs embed.FS
|
fs embed.FS
|
||||||
ruTranslation = `
|
ruTranslation = `
|
||||||
|
{{define "editing hypha"}}Редактирование {{beautifulName .}}{{end}}
|
||||||
|
{{define "editing [[hypha]]"}}Редактирование <a href="/hypha/{{.}}">{{beautifulName .}}</a>{{end}}
|
||||||
|
{{define "creating [[hypha]]"}}Создание <a href="/hypha/{{.}}">{{beautifulName .}}</a>{{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"}}<a href="/help/en/mycomarkup" class="shy-link">Подробнее</a> о Микоразметке{{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"}}Напишите заметку, дневник, статью, рассказ или иной текст с помощью <a href="/help/en/mycomarkup" class="shy-link">Микоразметки</a>. Сохраняется полная история правок документа.{{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]]?"}}Удалить <a href="/hypha/{{.}}">{{beautifulName .}}</a>?{{end}}
|
||||||
|
{{define "want to delete?"}}Вы действительно хотите удалить эту гифу?{{end}}
|
||||||
|
{{define "delete tip"}}Нельзя отменить удаление гифы, но её история останется доступной.{{end}}
|
||||||
|
|
||||||
{{define "rename hypha?"}}Переименовать {{beautifulName .}}?{{end}}
|
{{define "rename hypha?"}}Переименовать {{beautifulName .}}?{{end}}
|
||||||
{{define "rename [[hypha]]?"}}Переименовать <a href="/hypha/{{.}}">{{beautifulName .}}</a>?{{end}}
|
{{define "rename [[hypha]]?"}}Переименовать <a href="/hypha/{{.}}">{{beautifulName .}}</a>?{{end}}
|
||||||
{{define "new name"}}Новое название:{{end}}
|
{{define "new name"}}Новое название:{{end}}
|
||||||
@ -22,15 +75,48 @@ var (
|
|||||||
{{define "rename tip"}}Переименовывайте аккуратно. <a href="/help/en/rename">Документация на английском.</a>{{end}}
|
{{define "rename tip"}}Переименовывайте аккуратно. <a href="/help/en/rename">Документация на английском.</a>{{end}}
|
||||||
{{define "leave redirection"}}Оставить перенаправление{{end}}
|
{{define "leave redirection"}}Оставить перенаправление{{end}}
|
||||||
|
|
||||||
|
{{define "remove media from x?"}}Убрать медиа у {{beautifulName .}}?{{end}}
|
||||||
|
{{define "remove media from [[x]]?"}}Убрать медиа у <a href="/hypha/{{.MatchedHyphaName}}">{{beautifulName .MatchedHyphaName}}</a>?{{end}}
|
||||||
|
{{define "remove media for real?"}}Вы точно хотите убрать медиа у гифы «{{beautifulName .MatchedHyphaName}}»?{{end}}
|
||||||
`
|
`
|
||||||
chainNaviTitle viewutil.Chain
|
chainNaviTitle viewutil.Chain
|
||||||
|
chainEditHypha viewutil.Chain
|
||||||
|
chainEmptyHypha viewutil.Chain
|
||||||
|
chainDeleteHypha viewutil.Chain
|
||||||
chainRenameHypha viewutil.Chain
|
chainRenameHypha viewutil.Chain
|
||||||
|
chainRemoveMedia viewutil.Chain
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
chainNaviTitle = viewutil.CopyEnRuWith(fs, "view_navititle.html", "")
|
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)
|
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 {
|
type renameData struct {
|
||||||
@ -49,6 +135,49 @@ 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 {
|
type naviTitleData struct {
|
||||||
HyphaNameParts []string
|
HyphaNameParts []string
|
||||||
HyphaNamePartsWithParents []string
|
HyphaNamePartsWithParents []string
|
||||||
@ -56,7 +185,7 @@ type naviTitleData struct {
|
|||||||
HomeHypha string
|
HomeHypha string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NaviTitle(meta viewutil.Meta, hyphaName string) template.HTML {
|
func NaviTitle(meta viewutil.Meta, hyphaName string) string {
|
||||||
parts, partsWithParents := naviTitleify(hyphaName)
|
parts, partsWithParents := naviTitleify(hyphaName)
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
err := chainNaviTitle.Get(meta).ExecuteTemplate(&buf, "navititle", naviTitleData{
|
err := chainNaviTitle.Get(meta).ExecuteTemplate(&buf, "navititle", naviTitleData{
|
||||||
@ -66,9 +195,9 @@ func NaviTitle(meta viewutil.Meta, hyphaName string) template.HTML {
|
|||||||
HomeHypha: cfg.HomeHypha,
|
HomeHypha: cfg.HomeHypha,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to render NaviTitle properly; using nevertheless", "err", err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
return template.HTML(buf.String())
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func naviTitleify(hyphaName string) ([]string, []string) {
|
func naviTitleify(hyphaName string) ([]string, []string) {
|
||||||
|
|||||||
50
hypview/nav.qtpl
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{% 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 %}
|
||||||
|
<li class="hypha-info__entry hypha-info__entry_{%s action %}">
|
||||||
|
<a class="hypha-info__link" href="/{%s action %}/{%s h.CanonicalName() %}">{%s displayText %}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% func hyphaInfo(meta viewutil.Meta, h hyphae.Hypha) %}
|
||||||
|
{% code
|
||||||
|
u := meta.U
|
||||||
|
lc := meta.Lc
|
||||||
|
backs := backlinks.BacklinksCount(h.CanonicalName())
|
||||||
|
%}
|
||||||
|
<nav class="hypha-info">
|
||||||
|
<ul class="hypha-info__list">
|
||||||
|
{%= hyphaInfoEntry(h, u, "history", false, lc.Get("ui.history_link")) %}
|
||||||
|
{%= hyphaInfoEntry(h, u, "rename", true, lc.Get("ui.rename_link")) %}
|
||||||
|
{%= hyphaInfoEntry(h, u, "delete", true, lc.Get("ui.delete_link")) %}
|
||||||
|
{%= hyphaInfoEntry(h, u, "text", true, lc.Get("ui.text_link")) %}
|
||||||
|
{% switch h := h.(type) %}
|
||||||
|
{% case *hyphae.TextualHypha %}
|
||||||
|
{%= hyphaInfoEntry(h, u, "media", true, lc.Get("ui.media_link_for_textual")) %}
|
||||||
|
{% default %}
|
||||||
|
{%= hyphaInfoEntry(h, u, "media", true, lc.Get("ui.media_link")) %}
|
||||||
|
{% endswitch %}
|
||||||
|
{%= hyphaInfoEntry(h, u, "backlinks", false, lc.GetPlural("ui.backlinks_link", backs)) %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% func commonScripts() %}
|
||||||
|
{% for _, scriptPath := range cfg.CommonScripts %}
|
||||||
|
<script src="{%s scriptPath %}"></script>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% func beautifulLink(hyphaName string) %}<a href="/hypha/{%s= hyphaName %}">{%s util.BeautifulName(hyphaName) %}</a>{% endfunc %}
|
||||||
311
hypview/nav.qtpl.go
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
// 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(`
|
||||||
|
<li class="hypha-info__entry hypha-info__entry_`)
|
||||||
|
//line hypview/nav.qtpl:15
|
||||||
|
qw422016.E().S(action)
|
||||||
|
//line hypview/nav.qtpl:15
|
||||||
|
qw422016.N().S(`">
|
||||||
|
<a class="hypha-info__link" href="/`)
|
||||||
|
//line hypview/nav.qtpl:16
|
||||||
|
qw422016.E().S(action)
|
||||||
|
//line hypview/nav.qtpl:16
|
||||||
|
qw422016.N().S(`/`)
|
||||||
|
//line hypview/nav.qtpl:16
|
||||||
|
qw422016.E().S(h.CanonicalName())
|
||||||
|
//line hypview/nav.qtpl:16
|
||||||
|
qw422016.N().S(`">`)
|
||||||
|
//line hypview/nav.qtpl:16
|
||||||
|
qw422016.E().S(displayText)
|
||||||
|
//line hypview/nav.qtpl:16
|
||||||
|
qw422016.N().S(`</a>
|
||||||
|
</li>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<nav class="hypha-info">
|
||||||
|
<ul class="hypha-info__list">
|
||||||
|
`)
|
||||||
|
//line hypview/nav.qtpl:29
|
||||||
|
streamhyphaInfoEntry(qw422016, h, u, "history", false, lc.Get("ui.history_link"))
|
||||||
|
//line hypview/nav.qtpl:29
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line hypview/nav.qtpl:30
|
||||||
|
streamhyphaInfoEntry(qw422016, h, u, "rename", true, lc.Get("ui.rename_link"))
|
||||||
|
//line hypview/nav.qtpl:30
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line hypview/nav.qtpl:31
|
||||||
|
streamhyphaInfoEntry(qw422016, h, u, "delete", true, lc.Get("ui.delete_link"))
|
||||||
|
//line hypview/nav.qtpl:31
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line hypview/nav.qtpl:32
|
||||||
|
streamhyphaInfoEntry(qw422016, h, u, "text", true, lc.Get("ui.text_link"))
|
||||||
|
//line hypview/nav.qtpl:32
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line hypview/nav.qtpl:33
|
||||||
|
switch h := h.(type) {
|
||||||
|
//line hypview/nav.qtpl:34
|
||||||
|
case *hyphae.TextualHypha:
|
||||||
|
//line hypview/nav.qtpl:34
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line hypview/nav.qtpl:35
|
||||||
|
streamhyphaInfoEntry(qw422016, h, u, "media", true, lc.Get("ui.media_link_for_textual"))
|
||||||
|
//line hypview/nav.qtpl:35
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line hypview/nav.qtpl:36
|
||||||
|
default:
|
||||||
|
//line hypview/nav.qtpl:36
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line hypview/nav.qtpl:37
|
||||||
|
streamhyphaInfoEntry(qw422016, h, u, "media", true, lc.Get("ui.media_link"))
|
||||||
|
//line hypview/nav.qtpl:37
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line hypview/nav.qtpl:38
|
||||||
|
}
|
||||||
|
//line hypview/nav.qtpl:38
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line hypview/nav.qtpl:39
|
||||||
|
streamhyphaInfoEntry(qw422016, h, u, "backlinks", false, lc.GetPlural("ui.backlinks_link", backs))
|
||||||
|
//line hypview/nav.qtpl:39
|
||||||
|
qw422016.N().S(`
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<script src="`)
|
||||||
|
//line hypview/nav.qtpl:46
|
||||||
|
qw422016.E().S(scriptPath)
|
||||||
|
//line hypview/nav.qtpl:46
|
||||||
|
qw422016.N().S(`"></script>
|
||||||
|
`)
|
||||||
|
//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(`<a href="/hypha/`)
|
||||||
|
//line hypview/nav.qtpl:50
|
||||||
|
qw422016.N().S(hyphaName)
|
||||||
|
//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(`</a>`)
|
||||||
|
//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
|
||||||
|
}
|
||||||
161
hypview/readers.qtpl
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
{% 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)
|
||||||
|
%}
|
||||||
|
<main class="main-width media-tab">
|
||||||
|
<h1>{%s= lc.Get("ui.media_title", &l18n.Replacements{"name": beautifulLink(h.CanonicalName())}) %}</h1>
|
||||||
|
{% switch h.(type) %}
|
||||||
|
{% case *hyphae.MediaHypha %}
|
||||||
|
<p class="explanation">{%s lc.Get("ui.media_tip") %} <a href="/help/en/media" class="shy-link">{%s lc.Get("ui.media_what_is") %}</a></p>
|
||||||
|
{% default %}
|
||||||
|
<p class="explanation">{%s lc.Get("ui.media_empty") %} <a href="/help/en/media" class="shy-link">{%s lc.Get("ui.media_what_is") %}</a></p>
|
||||||
|
{% endswitch %}
|
||||||
|
|
||||||
|
<section class="amnt-grid">
|
||||||
|
{% switch h := h.(type) %}
|
||||||
|
{% case *hyphae.MediaHypha %}
|
||||||
|
{% code
|
||||||
|
mime := mimetype.FromExtension(path.Ext(h.MediaFilePath()))
|
||||||
|
fileinfo, err := os.Stat(h.MediaFilePath()) %}
|
||||||
|
{% if err == nil %}
|
||||||
|
<fieldset class="amnt-menu-block">
|
||||||
|
<legend class="modal__title modal__title_small">{%s lc.Get("ui.media_stat") %}</legend>
|
||||||
|
<p class="modal__confirmation-msg"><b>{%s lc.Get("ui.media_stat_size") %}</b> {%s lc.GetPlural64("ui.media_size_value", fileinfo.Size())%}</p>
|
||||||
|
<p><b>{%s lc.Get("ui.media_stat_mime") %}</b> {%s mime %}</p>
|
||||||
|
</fieldset>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if strings.HasPrefix(mime, "image/") %}
|
||||||
|
<fieldset class="amnt-menu-block">
|
||||||
|
<legend class="modal__title modal__title_small">{%s lc.Get("ui.media_include") %}</legend>
|
||||||
|
<p class="modal__confirmation-msg">{%s lc.Get("ui.media_include_tip") %}</p>
|
||||||
|
<pre class="codeblock"><code>img { {%s h.CanonicalName() %} }</code></pre>
|
||||||
|
</fieldset>
|
||||||
|
{% endif %}
|
||||||
|
{% endswitch %}
|
||||||
|
|
||||||
|
{% if u.CanProceed("upload-binary") %}
|
||||||
|
<form action="/upload-binary/{%s h.CanonicalName() %}"
|
||||||
|
method="post" enctype="multipart/form-data"
|
||||||
|
class="upload-binary modal amnt-menu-block">
|
||||||
|
<fieldset class="modal__fieldset">
|
||||||
|
<legend class="modal__title modal__title_small">{%s lc.Get("ui.media_new") %}</legend>
|
||||||
|
<p class="modal__confirmation-msg">{%s lc.Get("ui.media_new_tip") %}</p>
|
||||||
|
<label for="upload-binary__input"></label>
|
||||||
|
<input type="file" id="upload-binary__input" name="binary">
|
||||||
|
|
||||||
|
<button type="submit" class="btn stick-to-bottom" value="Upload">{%s lc.Get("ui.media_upload")%}</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
{% switch h := h.(type) %}
|
||||||
|
{% case *hyphae.MediaHypha %}
|
||||||
|
{% if u.CanProceed("remove-media") %}
|
||||||
|
<form action="/remove-media/{%s h.CanonicalName() %}" method="post" class="modal amnt-menu-block" method="POST">
|
||||||
|
<fieldset class="modal__fieldset">
|
||||||
|
<legend class="modal__title modal__title_small">{%s lc.Get("ui.media_remove") %}</legend>
|
||||||
|
<p class="modal__confirmation-msg">{%s lc.Get("ui.media_remove_tip") %}</p>
|
||||||
|
<button type="submit" class="btn" value="Remove media">{%s lc.Get("ui.media_remove_button") %}</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% endswitch %}
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
{% 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
|
||||||
|
%}
|
||||||
|
<main class="main-width">
|
||||||
|
<section id="hypha">
|
||||||
|
{% if meta.U.CanProceed("edit") %}
|
||||||
|
<div class="btn btn_navititle">
|
||||||
|
<a class="btn__link_navititle" href="/edit/{%s h.CanonicalName() %}">{%s lc.Get("ui.edit_link") %}</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if cfg.UseAuth && util.IsProfileName(h.CanonicalName()) && meta.U.Name == strings.TrimPrefix(h.CanonicalName(), cfg.UserHypha + "/") %}
|
||||||
|
<div class="btn btn_navititle">
|
||||||
|
<a class="btn__link_navititle" href="/logout">{%s lc.Get("ui.logout_link") %}</a>
|
||||||
|
</div>
|
||||||
|
{% if meta.U.Group == "admin" %}
|
||||||
|
<div class="btn btn_navititle">
|
||||||
|
<a class="btn__link_navititle" href="/admin">{%s lc.Get("ui.admin_panel") %}<a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{%s= NaviTitle(meta, h.CanonicalName()) %}
|
||||||
|
{% switch h.(type) %}
|
||||||
|
{% case *hyphae.EmptyHypha %}
|
||||||
|
{%s= EmptyHypha(meta, h.CanonicalName()) %}
|
||||||
|
{% default %}
|
||||||
|
{%s= contents %}
|
||||||
|
{% endswitch %}
|
||||||
|
</section>
|
||||||
|
<section class="prevnext">
|
||||||
|
{% if prevHyphaName != "" %}
|
||||||
|
<a class="prevnext__el prevnext__prev" href="/hypha/{%s prevHyphaName %}" rel="prev">← {%s util.BeautifulName(path.Base(prevHyphaName)) %}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if nextHyphaName != "" %}
|
||||||
|
<a class="prevnext__el prevnext__next" href="/hypha/{%s nextHyphaName %}" rel="next">{%s util.BeautifulName(path.Base(nextHyphaName)) %} →</a>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
|
{% if strings.TrimSpace(subhyphae) != "" %}
|
||||||
|
<section class="subhyphae">
|
||||||
|
<h2 class="subhyphae__title">{%s lc.Get("ui.subhyphae") %}</h2>
|
||||||
|
<nav class="subhyphae__nav">
|
||||||
|
<ul class="subhyphae__list">
|
||||||
|
{%s= subhyphae %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
<section id="hypha-bottom">
|
||||||
|
{%= hyphaInfo(meta, h) %}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
{%s= categories.CategoryCard(meta, h.CanonicalName()) %}
|
||||||
|
{%= viewScripts() %}
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% func Revision(meta viewutil.Meta, h hyphae.Hypha, contents, revHash string) %}
|
||||||
|
<main class="main-width">
|
||||||
|
<section>
|
||||||
|
<p>{%s meta.Lc.Get("ui.revision_warning") %} <a href="/rev-text/{%s revHash %}/{%s h.CanonicalName() %}">{%s meta.Lc.Get("ui.revision_link") %}</a></p>
|
||||||
|
{%s= NaviTitle(meta, h.CanonicalName()) %}
|
||||||
|
{%s= contents %}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
{%= viewScripts() %}
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% func viewScripts() %}
|
||||||
|
{% for _, scriptPath := range cfg.ViewScripts %}
|
||||||
|
<script src="{%s scriptPath %}"></script>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfunc %}
|
||||||
651
hypview/readers.qtpl.go
Normal file
@ -0,0 +1,651 @@
|
|||||||
|
// 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(`
|
||||||
|
<main class="main-width media-tab">
|
||||||
|
<h1>`)
|
||||||
|
//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(`</h1>
|
||||||
|
`)
|
||||||
|
//line hypview/readers.qtpl:22
|
||||||
|
switch h.(type) {
|
||||||
|
//line hypview/readers.qtpl:23
|
||||||
|
case *hyphae.MediaHypha:
|
||||||
|
//line hypview/readers.qtpl:23
|
||||||
|
qw422016.N().S(`
|
||||||
|
<p class="explanation">`)
|
||||||
|
//line hypview/readers.qtpl:24
|
||||||
|
qw422016.E().S(lc.Get("ui.media_tip"))
|
||||||
|
//line hypview/readers.qtpl:24
|
||||||
|
qw422016.N().S(` <a href="/help/en/media" class="shy-link">`)
|
||||||
|
//line hypview/readers.qtpl:24
|
||||||
|
qw422016.E().S(lc.Get("ui.media_what_is"))
|
||||||
|
//line hypview/readers.qtpl:24
|
||||||
|
qw422016.N().S(`</a></p>
|
||||||
|
`)
|
||||||
|
//line hypview/readers.qtpl:25
|
||||||
|
default:
|
||||||
|
//line hypview/readers.qtpl:25
|
||||||
|
qw422016.N().S(`
|
||||||
|
<p class="explanation">`)
|
||||||
|
//line hypview/readers.qtpl:26
|
||||||
|
qw422016.E().S(lc.Get("ui.media_empty"))
|
||||||
|
//line hypview/readers.qtpl:26
|
||||||
|
qw422016.N().S(` <a href="/help/en/media" class="shy-link">`)
|
||||||
|
//line hypview/readers.qtpl:26
|
||||||
|
qw422016.E().S(lc.Get("ui.media_what_is"))
|
||||||
|
//line hypview/readers.qtpl:26
|
||||||
|
qw422016.N().S(`</a></p>
|
||||||
|
`)
|
||||||
|
//line hypview/readers.qtpl:27
|
||||||
|
}
|
||||||
|
//line hypview/readers.qtpl:27
|
||||||
|
qw422016.N().S(`
|
||||||
|
|
||||||
|
<section class="amnt-grid">
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<fieldset class="amnt-menu-block">
|
||||||
|
<legend class="modal__title modal__title_small">`)
|
||||||
|
//line hypview/readers.qtpl:37
|
||||||
|
qw422016.E().S(lc.Get("ui.media_stat"))
|
||||||
|
//line hypview/readers.qtpl:37
|
||||||
|
qw422016.N().S(`</legend>
|
||||||
|
<p class="modal__confirmation-msg"><b>`)
|
||||||
|
//line hypview/readers.qtpl:38
|
||||||
|
qw422016.E().S(lc.Get("ui.media_stat_size"))
|
||||||
|
//line hypview/readers.qtpl:38
|
||||||
|
qw422016.N().S(`</b> `)
|
||||||
|
//line hypview/readers.qtpl:38
|
||||||
|
qw422016.E().S(lc.GetPlural64("ui.media_size_value", fileinfo.Size()))
|
||||||
|
//line hypview/readers.qtpl:38
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
<p><b>`)
|
||||||
|
//line hypview/readers.qtpl:39
|
||||||
|
qw422016.E().S(lc.Get("ui.media_stat_mime"))
|
||||||
|
//line hypview/readers.qtpl:39
|
||||||
|
qw422016.N().S(`</b> `)
|
||||||
|
//line hypview/readers.qtpl:39
|
||||||
|
qw422016.E().S(mime)
|
||||||
|
//line hypview/readers.qtpl:39
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
</fieldset>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<fieldset class="amnt-menu-block">
|
||||||
|
<legend class="modal__title modal__title_small">`)
|
||||||
|
//line hypview/readers.qtpl:45
|
||||||
|
qw422016.E().S(lc.Get("ui.media_include"))
|
||||||
|
//line hypview/readers.qtpl:45
|
||||||
|
qw422016.N().S(`</legend>
|
||||||
|
<p class="modal__confirmation-msg">`)
|
||||||
|
//line hypview/readers.qtpl:46
|
||||||
|
qw422016.E().S(lc.Get("ui.media_include_tip"))
|
||||||
|
//line hypview/readers.qtpl:46
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
<pre class="codeblock"><code>img { `)
|
||||||
|
//line hypview/readers.qtpl:47
|
||||||
|
qw422016.E().S(h.CanonicalName())
|
||||||
|
//line hypview/readers.qtpl:47
|
||||||
|
qw422016.N().S(` }</code></pre>
|
||||||
|
</fieldset>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<form action="/upload-binary/`)
|
||||||
|
//line hypview/readers.qtpl:53
|
||||||
|
qw422016.E().S(h.CanonicalName())
|
||||||
|
//line hypview/readers.qtpl:53
|
||||||
|
qw422016.N().S(`"
|
||||||
|
method="post" enctype="multipart/form-data"
|
||||||
|
class="upload-binary modal amnt-menu-block">
|
||||||
|
<fieldset class="modal__fieldset">
|
||||||
|
<legend class="modal__title modal__title_small">`)
|
||||||
|
//line hypview/readers.qtpl:57
|
||||||
|
qw422016.E().S(lc.Get("ui.media_new"))
|
||||||
|
//line hypview/readers.qtpl:57
|
||||||
|
qw422016.N().S(`</legend>
|
||||||
|
<p class="modal__confirmation-msg">`)
|
||||||
|
//line hypview/readers.qtpl:58
|
||||||
|
qw422016.E().S(lc.Get("ui.media_new_tip"))
|
||||||
|
//line hypview/readers.qtpl:58
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
<label for="upload-binary__input"></label>
|
||||||
|
<input type="file" id="upload-binary__input" name="binary">
|
||||||
|
|
||||||
|
<button type="submit" class="btn stick-to-bottom" value="Upload">`)
|
||||||
|
//line hypview/readers.qtpl:62
|
||||||
|
qw422016.E().S(lc.Get("ui.media_upload"))
|
||||||
|
//line hypview/readers.qtpl:62
|
||||||
|
qw422016.N().S(`</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<form action="/remove-media/`)
|
||||||
|
//line hypview/readers.qtpl:71
|
||||||
|
qw422016.E().S(h.CanonicalName())
|
||||||
|
//line hypview/readers.qtpl:71
|
||||||
|
qw422016.N().S(`" method="post" class="modal amnt-menu-block" method="POST">
|
||||||
|
<fieldset class="modal__fieldset">
|
||||||
|
<legend class="modal__title modal__title_small">`)
|
||||||
|
//line hypview/readers.qtpl:73
|
||||||
|
qw422016.E().S(lc.Get("ui.media_remove"))
|
||||||
|
//line hypview/readers.qtpl:73
|
||||||
|
qw422016.N().S(`</legend>
|
||||||
|
<p class="modal__confirmation-msg">`)
|
||||||
|
//line hypview/readers.qtpl:74
|
||||||
|
qw422016.E().S(lc.Get("ui.media_remove_tip"))
|
||||||
|
//line hypview/readers.qtpl:74
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
<button type="submit" class="btn" value="Remove media">`)
|
||||||
|
//line hypview/readers.qtpl:75
|
||||||
|
qw422016.E().S(lc.Get("ui.media_remove_button"))
|
||||||
|
//line hypview/readers.qtpl:75
|
||||||
|
qw422016.N().S(`</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<main class="main-width">
|
||||||
|
<section id="hypha">
|
||||||
|
`)
|
||||||
|
//line hypview/readers.qtpl:95
|
||||||
|
if meta.U.CanProceed("edit") {
|
||||||
|
//line hypview/readers.qtpl:95
|
||||||
|
qw422016.N().S(`
|
||||||
|
<div class="btn btn_navititle">
|
||||||
|
<a class="btn__link_navititle" href="/edit/`)
|
||||||
|
//line hypview/readers.qtpl:97
|
||||||
|
qw422016.E().S(h.CanonicalName())
|
||||||
|
//line hypview/readers.qtpl:97
|
||||||
|
qw422016.N().S(`">`)
|
||||||
|
//line hypview/readers.qtpl:97
|
||||||
|
qw422016.E().S(lc.Get("ui.edit_link"))
|
||||||
|
//line hypview/readers.qtpl:97
|
||||||
|
qw422016.N().S(`</a>
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<div class="btn btn_navititle">
|
||||||
|
<a class="btn__link_navititle" href="/logout">`)
|
||||||
|
//line hypview/readers.qtpl:103
|
||||||
|
qw422016.E().S(lc.Get("ui.logout_link"))
|
||||||
|
//line hypview/readers.qtpl:103
|
||||||
|
qw422016.N().S(`</a>
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
//line hypview/readers.qtpl:105
|
||||||
|
if meta.U.Group == "admin" {
|
||||||
|
//line hypview/readers.qtpl:105
|
||||||
|
qw422016.N().S(`
|
||||||
|
<div class="btn btn_navititle">
|
||||||
|
<a class="btn__link_navititle" href="/admin">`)
|
||||||
|
//line hypview/readers.qtpl:107
|
||||||
|
qw422016.E().S(lc.Get("ui.admin_panel"))
|
||||||
|
//line hypview/readers.qtpl:107
|
||||||
|
qw422016.N().S(`<a>
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
</section>
|
||||||
|
<section class="prevnext">
|
||||||
|
`)
|
||||||
|
//line hypview/readers.qtpl:121
|
||||||
|
if prevHyphaName != "" {
|
||||||
|
//line hypview/readers.qtpl:121
|
||||||
|
qw422016.N().S(`
|
||||||
|
<a class="prevnext__el prevnext__prev" href="/hypha/`)
|
||||||
|
//line hypview/readers.qtpl:122
|
||||||
|
qw422016.E().S(prevHyphaName)
|
||||||
|
//line hypview/readers.qtpl:122
|
||||||
|
qw422016.N().S(`" rel="prev">← `)
|
||||||
|
//line hypview/readers.qtpl:122
|
||||||
|
qw422016.E().S(util.BeautifulName(path.Base(prevHyphaName)))
|
||||||
|
//line hypview/readers.qtpl:122
|
||||||
|
qw422016.N().S(`</a>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<a class="prevnext__el prevnext__next" href="/hypha/`)
|
||||||
|
//line hypview/readers.qtpl:125
|
||||||
|
qw422016.E().S(nextHyphaName)
|
||||||
|
//line hypview/readers.qtpl:125
|
||||||
|
qw422016.N().S(`" rel="next">`)
|
||||||
|
//line hypview/readers.qtpl:125
|
||||||
|
qw422016.E().S(util.BeautifulName(path.Base(nextHyphaName)))
|
||||||
|
//line hypview/readers.qtpl:125
|
||||||
|
qw422016.N().S(` →</a>
|
||||||
|
`)
|
||||||
|
//line hypview/readers.qtpl:126
|
||||||
|
}
|
||||||
|
//line hypview/readers.qtpl:126
|
||||||
|
qw422016.N().S(`
|
||||||
|
</section>
|
||||||
|
`)
|
||||||
|
//line hypview/readers.qtpl:128
|
||||||
|
if strings.TrimSpace(subhyphae) != "" {
|
||||||
|
//line hypview/readers.qtpl:128
|
||||||
|
qw422016.N().S(`
|
||||||
|
<section class="subhyphae">
|
||||||
|
<h2 class="subhyphae__title">`)
|
||||||
|
//line hypview/readers.qtpl:130
|
||||||
|
qw422016.E().S(lc.Get("ui.subhyphae"))
|
||||||
|
//line hypview/readers.qtpl:130
|
||||||
|
qw422016.N().S(`</h2>
|
||||||
|
<nav class="subhyphae__nav">
|
||||||
|
<ul class="subhyphae__list">
|
||||||
|
`)
|
||||||
|
//line hypview/readers.qtpl:133
|
||||||
|
qw422016.N().S(subhyphae)
|
||||||
|
//line hypview/readers.qtpl:133
|
||||||
|
qw422016.N().S(`
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
`)
|
||||||
|
//line hypview/readers.qtpl:137
|
||||||
|
}
|
||||||
|
//line hypview/readers.qtpl:137
|
||||||
|
qw422016.N().S(`
|
||||||
|
<section id="hypha-bottom">
|
||||||
|
`)
|
||||||
|
//line hypview/readers.qtpl:139
|
||||||
|
streamhyphaInfo(qw422016, meta, h)
|
||||||
|
//line hypview/readers.qtpl:139
|
||||||
|
qw422016.N().S(`
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<main class="main-width">
|
||||||
|
<section>
|
||||||
|
<p>`)
|
||||||
|
//line hypview/readers.qtpl:149
|
||||||
|
qw422016.E().S(meta.Lc.Get("ui.revision_warning"))
|
||||||
|
//line hypview/readers.qtpl:149
|
||||||
|
qw422016.N().S(` <a href="/rev-text/`)
|
||||||
|
//line hypview/readers.qtpl:149
|
||||||
|
qw422016.E().S(revHash)
|
||||||
|
//line hypview/readers.qtpl:149
|
||||||
|
qw422016.N().S(`/`)
|
||||||
|
//line hypview/readers.qtpl:149
|
||||||
|
qw422016.E().S(h.CanonicalName())
|
||||||
|
//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(`</a></p>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
`)
|
||||||
|
//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(`
|
||||||
|
<script src="`)
|
||||||
|
//line hypview/readers.qtpl:159
|
||||||
|
qw422016.E().S(scriptPath)
|
||||||
|
//line hypview/readers.qtpl:159
|
||||||
|
qw422016.N().S(`"></script>
|
||||||
|
`)
|
||||||
|
//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
|
||||||
|
}
|
||||||
32
hypview/view_empty_hypha.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{{define "empty hypha card"}}
|
||||||
|
<section class="non-existent-hypha">
|
||||||
|
<h2 class="non-existent-hypha__title">{{block "empty heading" .}}This hypha does not exist{{end}}</h2>
|
||||||
|
{{if and .UseAuth (eq .Meta.U.Group "anon")}}
|
||||||
|
<p>{{block "empty no rights" .}}You are not authorized to create new hyphae. Here is what you can do:{{end}}</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/login">{{block "empty log in" .}}Log in to your account, if you have one{{end}}</a></li>
|
||||||
|
{{if .AllowRegistration}}<li><a href="/register">{{block "empty register" .}}Register a new account{{end}}</a></li>{{end}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<div class="non-existent-hypha__ways">
|
||||||
|
<section class="non-existent-hypha__way">
|
||||||
|
<h3 class="non-existent-hypha__subtitle">📝 {{block "write a text" .}}Write a text{{end}}</h3>
|
||||||
|
<p>{{block "write a text tip" .}}Write a note, a diary, an article, a story or anything textual using <a href="/help/en/mycomarkup" class="shy-link">Mycomarkup</a>. Full history of edits to the document will be saved.{{end}}</p>
|
||||||
|
<p>{{block "write a text writing conventions" .}}Make sure to follow this wiki's writing conventions if there are any.{{end}}</p>
|
||||||
|
<a class="btn btn_accent stick-to-bottom" href="/edit/{{.HyphaName}}">{{block "write a text btn" .}}Create{{end}}</a>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="non-existent-hypha__way">
|
||||||
|
<h3 class="non-existent-hypha__subtitle">🖼 {{block "upload a media" .}}Upload a media{{end}}</h3>
|
||||||
|
<p>{{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}}</p>
|
||||||
|
<form action="/upload-binary/{{.HyphaName}}"
|
||||||
|
method="post" enctype="multipart/form-data"
|
||||||
|
class="upload-binary">
|
||||||
|
<input type="file" id="upload-binary__input" name="binary">
|
||||||
|
<button type="submit" class="btn stick-to-bottom" value="Upload">{{block "upload a media btn" .}}Upload{{end}}</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</section>
|
||||||
|
{{end}}
|
||||||
22
hypview/view_remove_media.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{{define "remove media from x?"}}Remove media from {{beautifulName .}}?{{end}}
|
||||||
|
{{define "title"}}{{template "remove media from x?" .HyphaName}}{{end}}
|
||||||
|
{{define "body"}}
|
||||||
|
<main class="main-width">
|
||||||
|
<form class="modal" action="/remove-media/{{.HyphaName}}" method="post">
|
||||||
|
<fieldset class="modal__fieldset">
|
||||||
|
<legend class="modal__title">
|
||||||
|
{{block "remove media from [[x]]?" .}}Remove media from <a href="/hypha/{{.HyphaName}}">{{beautifulName .HyphaName}}</a>?{{end}}
|
||||||
|
</legend>
|
||||||
|
<p class="modal__confirmation-msg">
|
||||||
|
{{block "remove media for real?" .}}Do you really want to remove media from hypha ‘{{beautifulName .HyphaName}}’?{{end}}
|
||||||
|
</p>
|
||||||
|
<button type="submit" value="Confirm" class="btn" autofocus>
|
||||||
|
{{template "confirm"}}
|
||||||
|
</button>
|
||||||
|
<a href="/hypha/{%s hyphaName %}" class="btn btn_weak">
|
||||||
|
{{template "cancel"}}
|
||||||
|
</a>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
{{end}}
|
||||||
@ -1,36 +0,0 @@
|
|||||||
package shroom
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log/slog"
|
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
)
|
|
||||||
|
|
||||||
func rejectRenameLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
|
||||||
slog.Info("Reject rename",
|
|
||||||
"hyphaName", h.CanonicalName(),
|
|
||||||
"username", u.Name,
|
|
||||||
"errmsg", errmsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func rejectRemoveMediaLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
|
||||||
slog.Info("Reject remove media",
|
|
||||||
"hyphaName", h.CanonicalName(),
|
|
||||||
"username", u.Name,
|
|
||||||
"errmsg", errmsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func rejectEditLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
|
||||||
slog.Info("Reject edit",
|
|
||||||
"hyphaName", h.CanonicalName(),
|
|
||||||
"username", u.Name,
|
|
||||||
"errmsg", errmsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func rejectUploadMediaLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
|
||||||
slog.Info("Reject upload media",
|
|
||||||
"hyphaName", h.CanonicalName(),
|
|
||||||
"username", u.Name,
|
|
||||||
"errmsg", errmsg)
|
|
||||||
}
|
|
||||||
@ -4,36 +4,29 @@ package interwiki
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/options"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/files"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
|
||||||
|
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/options"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init() error {
|
func Init() {
|
||||||
record, err := readInterwiki()
|
var (
|
||||||
|
record, err = readInterwiki()
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to read interwiki", "err", err)
|
log.Fatalln(err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, wiki := range record {
|
for _, wiki := range record {
|
||||||
wiki := wiki // This line is required
|
wiki := wiki // This line is required
|
||||||
if err := wiki.canonize(); err != nil {
|
wiki.canonize()
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := addEntry(&wiki); err != nil {
|
if err := addEntry(&wiki); err != nil {
|
||||||
slog.Error("Failed to add interwiki entry", "err", err)
|
log.Fatalln(err.Error())
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.Printf("Loaded %d interwiki entries\n", len(listOfEntries))
|
||||||
slog.Info("Indexed interwiki map", "n", len(listOfEntries))
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func dropEmptyStrings(ss []string) (clean []string) {
|
func dropEmptyStrings(ss []string) (clean []string) {
|
||||||
@ -107,6 +100,7 @@ func deleteEntry(wiki *Wiki) {
|
|||||||
for i, w := range listOfEntries {
|
for i, w := range listOfEntries {
|
||||||
i, w := i, w
|
i, w := i, w
|
||||||
if w.Name == wiki.Name {
|
if w.Name == wiki.Name {
|
||||||
|
log.Println("It came to delete")
|
||||||
// Drop ith element.
|
// Drop ith element.
|
||||||
listOfEntries[i] = listOfEntries[len(listOfEntries)-1]
|
listOfEntries[i] = listOfEntries[len(listOfEntries)-1]
|
||||||
listOfEntries = listOfEntries[:len(listOfEntries)-1]
|
listOfEntries = listOfEntries[:len(listOfEntries)-1]
|
||||||
@ -119,22 +113,21 @@ func deleteEntry(wiki *Wiki) {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: There is something clearly wrong with error-returning in this function.
|
|
||||||
func addEntry(wiki *Wiki) error {
|
func addEntry(wiki *Wiki) error {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
wiki.Aliases = dropEmptyStrings(wiki.Aliases)
|
wiki.Aliases = dropEmptyStrings(wiki.Aliases)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
names = append(wiki.Aliases, wiki.Name)
|
names = append(wiki.Aliases, wiki.Name)
|
||||||
ok, name = areNamesFree(names)
|
ok, name = areNamesFree(names)
|
||||||
)
|
)
|
||||||
switch {
|
if !ok {
|
||||||
case !ok:
|
log.Printf("There are multiple uses of the same name ‘%s’\n", name)
|
||||||
slog.Error("There are multiple uses of the same name", "name", name)
|
|
||||||
return errors.New(name)
|
return errors.New(name)
|
||||||
case len(names) == 0:
|
}
|
||||||
slog.Error("No names passed for a new interwiki entry")
|
if len(names) == 0 {
|
||||||
|
log.Println("No names passed for a new interwiki entry")
|
||||||
|
// There is something clearly wrong with error-returning in this function.
|
||||||
return errors.New("")
|
return errors.New("")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,13 +176,10 @@ func readInterwiki() ([]Wiki, error) {
|
|||||||
func saveInterwikiJson() {
|
func saveInterwikiJson() {
|
||||||
// Trust me, wiki crashing when an admin takes an administrative action totally makes sense.
|
// Trust me, wiki crashing when an admin takes an administrative action totally makes sense.
|
||||||
if data, err := json.MarshalIndent(listOfEntries, "", "\t"); err != nil {
|
if data, err := json.MarshalIndent(listOfEntries, "", "\t"); err != nil {
|
||||||
slog.Error("Failed to marshal interwiki entries", "err", err)
|
log.Fatalln(err)
|
||||||
os.Exit(1)
|
|
||||||
} else if err = os.WriteFile(files.InterwikiJSON(), data, 0666); err != nil {
|
} else if err = os.WriteFile(files.InterwikiJSON(), data, 0666); err != nil {
|
||||||
slog.Error("Failed to write interwiki.json", "err", err)
|
log.Fatalln(err)
|
||||||
os.Exit(1)
|
} else {
|
||||||
|
log.Println("Saved interwiki.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("Saved interwiki.json")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,13 +2,11 @@ package interwiki
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"log/slog"
|
"github.com/bouncepaw/mycorrhiza/viewutil"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -65,24 +63,19 @@ func handlerModifyEntry(w http.ResponseWriter, rq *http.Request) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if oldData, ok = entriesByName[name]; !ok {
|
if oldData, ok = entriesByName[name]; !ok {
|
||||||
slog.Info("Could not modify entry",
|
log.Printf("Could not modify interwiki entry ‘%s’ because it does not exist", name)
|
||||||
"name", name,
|
|
||||||
"reason", "does not exist")
|
|
||||||
viewutil.HandlerNotFound(w, rq)
|
viewutil.HandlerNotFound(w, rq)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := replaceEntry(oldData, &newData); err != nil {
|
if err := replaceEntry(oldData, &newData); err != nil {
|
||||||
slog.Info("Could not modify entry",
|
log.Printf("Could not modify interwiki entry ‘%s’ because one of the proposed aliases/name is taken\n", name)
|
||||||
"name", name,
|
|
||||||
"reason", "one of the proposed aliases or the name is taken",
|
|
||||||
"err", err)
|
|
||||||
viewNameTaken(viewutil.MetaFrom(w, rq), oldData, err.Error(), "modify-entry/"+name)
|
viewNameTaken(viewutil.MetaFrom(w, rq), oldData, err.Error(), "modify-entry/"+name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
saveInterwikiJson()
|
saveInterwikiJson()
|
||||||
slog.Info("Modified entry", "name", name)
|
log.Printf("Modified interwiki entry ‘%s’\n", name)
|
||||||
http.Redirect(w, rq, "/interwiki", http.StatusSeeOther)
|
http.Redirect(w, rq, "/interwiki", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
package interwiki
|
package interwiki
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WikiEngine is an enumeration of supported interwiki targets.
|
// WikiEngine is an enumeration of supported interwiki targets.
|
||||||
@ -49,20 +47,14 @@ type Wiki struct {
|
|||||||
Engine WikiEngine `json:"engine"`
|
Engine WikiEngine `json:"engine"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Wiki) canonize() error {
|
func (w *Wiki) canonize() {
|
||||||
switch {
|
switch {
|
||||||
case w.Name == "":
|
case w.Name == "":
|
||||||
slog.Error("A site in the interwiki map has no name")
|
log.Fatalln("Cannot have a wiki in the interwiki map with no name")
|
||||||
return errors.New("site with no name")
|
|
||||||
case w.URL == "":
|
case w.URL == "":
|
||||||
slog.Error("Site in the interwiki map has no URL", "name", w.Name)
|
log.Fatalf("Wiki ‘%s’ has no URL\n", w.Name)
|
||||||
return errors.New("site with no URL")
|
|
||||||
case !w.Engine.Valid():
|
case !w.Engine.Valid():
|
||||||
slog.Error("Site in the interwiki map has an unknown engine",
|
log.Fatalf("Unknown engine ‘%s’ for wiki ‘%s’\n", w.Engine, w.Name)
|
||||||
"siteName", w.Name,
|
|
||||||
"engine", w.Engine,
|
|
||||||
)
|
|
||||||
return errors.New("unknown engine")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Name = util.CanonicalName(w.Name)
|
w.Name = util.CanonicalName(w.Name)
|
||||||
@ -91,6 +83,4 @@ func (w *Wiki) canonize() error {
|
|||||||
w.ImgSrcFormat = fmt.Sprintf("%s/{NAME}", w.URL)
|
w.ImgSrcFormat = fmt.Sprintf("%s/{NAME}", w.URL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,29 +3,33 @@
|
|||||||
"password": "Password",
|
"password": "Password",
|
||||||
|
|
||||||
"register_title": "Register",
|
"register_title": "Register",
|
||||||
"register_header": "",
|
"register_header": "Register on {{.name}}",
|
||||||
"register_button": "Register",
|
"register_button": "Register",
|
||||||
|
|
||||||
"logout_title": "",
|
"login_title": "Login",
|
||||||
|
"login_header": "Log in to {{.name}}",
|
||||||
|
"login_button": "Log in",
|
||||||
|
|
||||||
|
"logout_title": "Logout?",
|
||||||
"logout_header": "Log out?",
|
"logout_header": "Log out?",
|
||||||
"logout_button": "Confirm",
|
"logout_button": "Confirm",
|
||||||
"logout_anon": "",
|
"logout_anon": "You cannot log out because you are not logged in.",
|
||||||
|
|
||||||
"lock_title": "Locked",
|
"lock_title": "Locked",
|
||||||
|
|
||||||
"password_tip": "",
|
"password_tip": "The server stores your password in an encrypted form; even administrators cannot read it.",
|
||||||
"cookie_tip": "",
|
"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": "",
|
"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.",
|
||||||
|
|
||||||
"noauth": "",
|
"noauth": "Authentication is disabled. You can make edits anonymously.",
|
||||||
"noregister": "Registrations are currently closed. Administrators can make an account for you by hand; contact them.",
|
"noregister": "Registrations are currently closed. Administrators can make an account for you by hand; contact them.",
|
||||||
|
|
||||||
"error_username": "",
|
"error_username": "Unknown username.",
|
||||||
"error_password": "",
|
"error_password": "Wrong password.",
|
||||||
"error_telegram": "",
|
"error_telegram": "Could not authorize using Telegram.",
|
||||||
|
|
||||||
"go_back": "Go back",
|
"go_back": "Go back",
|
||||||
"go_home": "",
|
"go_home": "Go home",
|
||||||
"go_login": "Go to the login page",
|
"go_login": "Go to the login page",
|
||||||
"try_again": "Try again"
|
"try_again": "Try again"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,18 @@
|
|||||||
"title_search": "Search by title",
|
"title_search": "Search by title",
|
||||||
"admin_panel": "Admin panel",
|
"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",
|
"subhyphae": "Subhyphae",
|
||||||
|
|
||||||
"random_no_hyphae": "There are no hyphae",
|
"random_no_hyphae": "There are no hyphae",
|
||||||
@ -45,9 +57,9 @@
|
|||||||
|
|
||||||
"diff_title": "Diff of {{.name}} at {{.rev}}",
|
"diff_title": "Diff of {{.name}} at {{.rev}}",
|
||||||
|
|
||||||
"revision_title": "",
|
"revision_title": "{{.name}} at {{.rev}}",
|
||||||
"revision_warning": "",
|
"revision_warning": "Please note that viewing media is not supported in history for now.",
|
||||||
"revision_link": "",
|
"revision_link": "Get Mycomarkup source of this revision",
|
||||||
"revision_no_text": "This hypha had no text at this revision.",
|
"revision_no_text": "This hypha had no text at this revision.",
|
||||||
|
|
||||||
"about_title": "About {{.name}}",
|
"about_title": "About {{.name}}",
|
||||||
@ -64,6 +76,25 @@
|
|||||||
"media_noaudio": "Your browser does not support audio.",
|
"media_noaudio": "Your browser does not support audio.",
|
||||||
"media_noaudio_link": "Download 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",
|
"confirm": "Confirm",
|
||||||
"cancel": "Cancel"
|
"cancel": "Cancel"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log/slog"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -78,7 +78,7 @@ func init() {
|
|||||||
|
|
||||||
var strings map[string]string
|
var strings map[string]string
|
||||||
if err := json.Unmarshal(contents, &strings); err != nil {
|
if err := json.Unmarshal(contents, &strings); err != nil {
|
||||||
slog.Error("Failed to unmarshal localization file", "path", path, "err", err)
|
log.Fatalf("error while parsing %s: %v", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range strings {
|
for key, value := range strings {
|
||||||
|
|||||||
@ -2,31 +2,31 @@
|
|||||||
"username": "Логин",
|
"username": "Логин",
|
||||||
"password": "Пароль",
|
"password": "Пароль",
|
||||||
|
|
||||||
"register_title": "",
|
"register_title": "Регистрация",
|
||||||
"register_header": "",
|
"register_header": "Регистрация на «{{.name}}»",
|
||||||
"register_button": "",
|
"register_button": "Зарегистрироваться",
|
||||||
|
|
||||||
"login_title": "Вход",
|
"login_title": "Вход",
|
||||||
"login_header": "",
|
"login_header": "Вход в «{{.name}}»",
|
||||||
"login_button": "",
|
"login_button": "Войти",
|
||||||
|
|
||||||
"logout_title": "",
|
"logout_title": "Выйти?",
|
||||||
"logout_header": "?",
|
"logout_header": "Выйти?",
|
||||||
"logout_button": "Подтвердить",
|
"logout_button": "Подтвердить",
|
||||||
"logout_anon": "",
|
"logout_anon": "Вы не можете выйти, потому что ещё не вошли.",
|
||||||
|
|
||||||
"lock_title": "",
|
"lock_title": "Доступ закрыт",
|
||||||
|
|
||||||
"password_tip": "",
|
"password_tip": "Сервер хранит ваш пароль в зашифрованном виде, даже администраторы не смогут его прочесть.",
|
||||||
"cookie_tip": "",
|
"cookie_tip": "Отправляя эту форму, вы разрешаете вики хранить cookie в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.",
|
||||||
"telegram_tip": "Вы можете войти с помощью Телеграм. Это сработает, если у вашего профиля есть @имя, и оно не занято в этой вики.",
|
"telegram_tip": "Вы можете войти с помощью Телеграм. Это сработает, если у вашего профиля есть @имя, и оно не занято в этой вики.",
|
||||||
|
|
||||||
"noauth": "",
|
"noauth": "Аутентификация отключена. Вы можете делать правки анонимно.",
|
||||||
"noregister": "Регистрация в текущее время недоступна. Администраторы могут вручную создать вам учётную запись, свяжитесь с ними.",
|
"noregister": "Регистрация в текущее время недоступна. Администраторы могут вручную создать вам учётную запись, свяжитесь с ними.",
|
||||||
|
|
||||||
"error_username": "",
|
"error_username": "Неизвестное имя пользователя.",
|
||||||
"error_password": "Неверный пароль.",
|
"error_password": "Неверный пароль.",
|
||||||
"error_telegram": "",
|
"error_telegram": "Не удалось авторизоваться через Телеграм.",
|
||||||
|
|
||||||
"go_back": "Назад",
|
"go_back": "Назад",
|
||||||
"go_home": "Домой",
|
"go_home": "Домой",
|
||||||
|
|||||||
@ -8,14 +8,14 @@
|
|||||||
"backlinks_heading": "Обратные ссылки на {{.hypha_link}}",
|
"backlinks_heading": "Обратные ссылки на {{.hypha_link}}",
|
||||||
"backlinks_desc": "Ниже перечислены гифы, на которых есть ссылка на эту гифу, трансклюзия этой гифы или эта гифа вставлена как изображение.",
|
"backlinks_desc": "Ниже перечислены гифы, на которых есть ссылка на эту гифу, трансклюзия этой гифы или эта гифа вставлена как изображение.",
|
||||||
|
|
||||||
"edit_link": "",
|
"edit_link": "Редактировать",
|
||||||
"logout_link": "",
|
"logout_link": "Выйти",
|
||||||
"history_link": "",
|
"history_link": "История",
|
||||||
"rename_link": "",
|
"rename_link": "Переименовать",
|
||||||
"delete_link": "",
|
"delete_link": "Удалить",
|
||||||
"text_link": "",
|
"text_link": "Посмотреть разметку",
|
||||||
"media_link": "",
|
"media_link": "Медиа",
|
||||||
"media_link_for_textual": "",
|
"media_link_for_textual": "Превратить в медиа-гифу",
|
||||||
"backlinks_link": "{{.n}} %s сюда",
|
"backlinks_link": "{{.n}} %s сюда",
|
||||||
"backlinks_link+one": "ссылка",
|
"backlinks_link+one": "ссылка",
|
||||||
"backlinks_link+few": "ссылки",
|
"backlinks_link+few": "ссылки",
|
||||||
@ -59,9 +59,9 @@
|
|||||||
"ask_really": "Вы действительно хотите {{.verb}} гифу «{{.name}}»?",
|
"ask_really": "Вы действительно хотите {{.verb}} гифу «{{.name}}»?",
|
||||||
"ask_remove_media_verb": "убрать медиа",
|
"ask_remove_media_verb": "убрать медиа",
|
||||||
|
|
||||||
"revision_title": "",
|
"revision_title": "{{.name}} из {{.rev}}",
|
||||||
"revision_warning": "",
|
"revision_warning": "Обратите внимание, просмотр медиа в истории пока что недоступен.",
|
||||||
"revision_link": "",
|
"revision_link": "Посмотреть Микоразметку для этой ревизии",
|
||||||
"revision_no_text": "В этой ревизии гифы не было текста.",
|
"revision_no_text": "В этой ревизии гифы не было текста.",
|
||||||
|
|
||||||
"about_title": "О {{.name}}",
|
"about_title": "О {{.name}}",
|
||||||
@ -78,6 +78,26 @@
|
|||||||
"media_noaudio": "Ваш браузер не поддерживает аудио.",
|
"media_noaudio": "Ваш браузер не поддерживает аудио.",
|
||||||
"media_noaudio_link": "Скачать аудио",
|
"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": "Применить",
|
"confirm": "Применить",
|
||||||
"cancel": "Отмена"
|
"cancel": "Отмена"
|
||||||
}
|
}
|
||||||
|
|||||||
70
main.go
@ -1,79 +1,69 @@
|
|||||||
// Command mycorrhiza is a program that runs a mycorrhiza wiki.
|
// 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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
"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"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/files"
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/categories"
|
"github.com/bouncepaw/mycorrhiza/shroom"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"github.com/bouncepaw/mycorrhiza/static"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
"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"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/static"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := parseCliArgs(); err != nil {
|
parseCliArgs()
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := files.PrepareWikiRoot(); err != nil {
|
if err := files.PrepareWikiRoot(); err != nil {
|
||||||
slog.Error("Failed to prepare wiki root", "err", err)
|
log.Fatal(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cfg.ReadConfigFile(files.ConfigPath()); err != nil {
|
if err := cfg.ReadConfigFile(files.ConfigPath()); err != nil {
|
||||||
slog.Error("Failed to read config", "err", err)
|
log.Fatal(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Println("Running Mycorrhiza Wiki", version.Short)
|
||||||
if err := os.Chdir(files.HyphaeDir()); err != nil {
|
if err := os.Chdir(files.HyphaeDir()); err != nil {
|
||||||
slog.Error("Failed to chdir to hyphae dir",
|
log.Fatal(err)
|
||||||
"err", err, "hyphaeDir", files.HyphaeDir())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
slog.Info("Running Mycorrhiza Wiki",
|
log.Println("Wiki directory is", cfg.WikiDir)
|
||||||
"version", version.Short, "wikiDir", cfg.WikiDir)
|
|
||||||
|
|
||||||
// Init the subsystems:
|
// Init the subsystems:
|
||||||
// TODO: keep all crashes in main rather than somewhere there
|
|
||||||
viewutil.Init()
|
viewutil.Init()
|
||||||
hyphae.Index(files.HyphaeDir())
|
hyphae.Index(files.HyphaeDir())
|
||||||
backlinks.IndexBacklinks()
|
backlinks.IndexBacklinks()
|
||||||
go backlinks.RunBacklinksConveyor()
|
go backlinks.RunBacklinksConveyor()
|
||||||
user.InitUserDatabase()
|
user.InitUserDatabase()
|
||||||
if err := history.Start(); err != nil {
|
history.Start()
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
history.InitGitRepo()
|
history.InitGitRepo()
|
||||||
migration.MigrateRocketsMaybe()
|
migration.MigrateRocketsMaybe()
|
||||||
migration.MigrateHeadingsMaybe()
|
migration.MigrateHeadingsMaybe()
|
||||||
shroom.SetHeaderLinks()
|
shroom.SetHeaderLinks()
|
||||||
if err := categories.Init(); err != nil {
|
categories.Init()
|
||||||
os.Exit(1)
|
interwiki.Init()
|
||||||
}
|
|
||||||
if err := interwiki.Init(); err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Static files:
|
// Static files:
|
||||||
static.InitFS(files.StaticFiles())
|
static.InitFS(files.StaticFiles())
|
||||||
|
|
||||||
if !user.HasAnyAdmins() {
|
if !user.HasAnyAdmins() {
|
||||||
slog.Error("Your wiki has no admin yet. Run Mycorrhiza with -create-admin <username> option to create an admin.")
|
log.Println("Your wiki has no admin yet. Run Mycorrhiza with -create-admin <username> option to create an admin.")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := serveHTTP(web.Handler())
|
serveHTTP(web.Handler())
|
||||||
if err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
package migration
|
package migration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
|
||||||
|
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/files"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
var headingMarkerPath string
|
var headingMarkerPath string
|
||||||
@ -31,8 +29,7 @@ func shouldMigrateHeadings() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to check if heading migration is needed", "err", err)
|
log.Fatalln("When checking if heading migration is needed:", err.Error())
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
return false
|
return false
|
||||||
@ -45,7 +42,6 @@ func createHeadingMarker() {
|
|||||||
0766,
|
0766,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to create heading migration marker", "err", err)
|
log.Fatalln(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8,14 +8,13 @@
|
|||||||
package migration
|
package migration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func genericLineMigrator(
|
func genericLineMigrator(
|
||||||
@ -37,8 +36,7 @@ func genericLineMigrator(
|
|||||||
file, err := os.OpenFile(hypha.TextFilePath(), os.O_RDWR, 0766)
|
file, err := os.OpenFile(hypha.TextFilePath(), os.O_RDWR, 0766)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hop.WithErrAbort(err)
|
hop.WithErrAbort(err)
|
||||||
slog.Error("Failed to open text part file", "path", hypha.TextFilePath(), "err", err)
|
log.Fatal("Something went wrong when opening ", hypha.TextFilePath(), ": ", err.Error())
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
@ -46,7 +44,7 @@ func genericLineMigrator(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
hop.WithErrAbort(err)
|
hop.WithErrAbort(err)
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
slog.Error("Failed to read text part file", "path", hypha.TextFilePath(), "err", err)
|
log.Fatal("Something went wrong when reading ", hypha.TextFilePath(), ": ", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -60,24 +58,21 @@ func genericLineMigrator(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
hop.WithErrAbort(err)
|
hop.WithErrAbort(err)
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
slog.Error("Failed to truncate text part file", "path", hypha.TextFilePath(), "err", err)
|
log.Fatal("Something went wrong when truncating ", hypha.TextFilePath(), ": ", err.Error())
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = file.Seek(0, 0)
|
_, err = file.Seek(0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hop.WithErrAbort(err)
|
hop.WithErrAbort(err)
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
slog.Error("Failed to seek in text part file", "path", hypha.TextFilePath(), "err", err)
|
log.Fatal("Something went wrong when seeking in ", hypha.TextFilePath(), ": ", err.Error())
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = file.WriteString(newText)
|
_, err = file.WriteString(newText)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hop.WithErrAbort(err)
|
hop.WithErrAbort(err)
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
slog.Error("Failed to write to text part file", "path", hypha.TextFilePath(), "err", err)
|
log.Fatal("Something went wrong when writing to ", hypha.TextFilePath(), ": ", err.Error())
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
@ -89,8 +84,8 @@ func genericLineMigrator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hop.WithFiles(mycoFiles...).Apply().HasErrors() {
|
if hop.WithFiles(mycoFiles...).Apply().HasErrors() {
|
||||||
slog.Error(commitErrorMessage + hop.FirstErrorText())
|
log.Fatal(commitErrorMessage, hop.FirstErrorText())
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("Migrated Mycomarkup documents", "n", len(mycoFiles))
|
log.Println("Migrated", len(mycoFiles), "Mycomarkup documents")
|
||||||
}
|
}
|
||||||
@ -1,13 +1,11 @@
|
|||||||
package migration
|
package migration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
|
||||||
|
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/files"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rocketMarkerPath string
|
var rocketMarkerPath string
|
||||||
@ -35,8 +33,7 @@ func shouldMigrateRockets() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to check if rocket migration is needed", "err", err)
|
log.Fatalln("When checking if rocket migration is needed:", err.Error())
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
return false
|
return false
|
||||||
@ -49,7 +46,6 @@ func createRocketLinkMarker() {
|
|||||||
0766,
|
0766,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to create rocket link migration marker")
|
log.Fatalln(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,33 +40,20 @@ func DataFromFilename(fullPath string) (name string, isText bool, skip bool) {
|
|||||||
|
|
||||||
var mapMime2Ext = map[string]string{
|
var mapMime2Ext = map[string]string{
|
||||||
"application/octet-stream": "bin",
|
"application/octet-stream": "bin",
|
||||||
|
|
||||||
"image/jpeg": "jpg",
|
"image/jpeg": "jpg",
|
||||||
"image/gif": "gif",
|
"image/gif": "gif",
|
||||||
"image/png": "png",
|
"image/png": "png",
|
||||||
"image/webp": "webp",
|
"image/webp": "webp",
|
||||||
"image/svg+xml": "svg",
|
"image/svg+xml": "svg",
|
||||||
"image/x-icon": "ico",
|
"image/x-icon": "ico",
|
||||||
|
|
||||||
"application/ogg": "ogg",
|
"application/ogg": "ogg",
|
||||||
"video/webm": "webm",
|
"video/webm": "webm",
|
||||||
"audio/mp3": "mp3",
|
"audio/mp3": "mp3",
|
||||||
"audio/mpeg": "mp3",
|
|
||||||
"audio/mpeg3": "mp3",
|
|
||||||
"video/mp4": "mp4",
|
"video/mp4": "mp4",
|
||||||
"audio/flac": "flac",
|
|
||||||
|
|
||||||
"audio/wav": "wav",
|
|
||||||
"audio/vnd.wav": "wav",
|
|
||||||
"audio/vnd.wave": "wav",
|
|
||||||
"audio/wave": "wav",
|
|
||||||
"audio/x-pn-wav": "wav",
|
|
||||||
"audio/x-wav": "wav",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var mapExt2Mime = map[string]string{
|
var mapExt2Mime = map[string]string{
|
||||||
".bin": "application/octet-stream",
|
".bin": "application/octet-stream",
|
||||||
|
|
||||||
".jpg": "image/jpeg",
|
".jpg": "image/jpeg",
|
||||||
".jpeg": "image/jpeg",
|
".jpeg": "image/jpeg",
|
||||||
".gif": "image/gif",
|
".gif": "image/gif",
|
||||||
@ -74,12 +61,8 @@ var mapExt2Mime = map[string]string{
|
|||||||
".webp": "image/webp",
|
".webp": "image/webp",
|
||||||
".svg": "image/svg+xml",
|
".svg": "image/svg+xml",
|
||||||
".ico": "image/x-icon",
|
".ico": "image/x-icon",
|
||||||
|
|
||||||
".ogg": "application/ogg",
|
".ogg": "application/ogg",
|
||||||
".webm": "video/webm",
|
".webm": "video/webm",
|
||||||
".mp3": "audio/mpeg",
|
".mp3": "audio/mp3",
|
||||||
".mp4": "video/mp4",
|
".mp4": "video/mp4",
|
||||||
".flac": "audio/flac",
|
|
||||||
|
|
||||||
"wav": "audio/wav",
|
|
||||||
}
|
}
|
||||||
@ -1,15 +1,13 @@
|
|||||||
package misc
|
package misc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"text/template" // sic! TODO: make it html/template after the template library migration
|
|
||||||
|
|
||||||
"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/l18n"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/version"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"text/template" // sic!
|
||||||
)
|
)
|
||||||
|
|
||||||
type L10nEntry struct {
|
type L10nEntry struct {
|
||||||
@ -97,8 +95,7 @@ func AboutHTML(lc *l18n.Localizer) string {
|
|||||||
}
|
}
|
||||||
temp, err := template.New("about wiki").Funcs(template.FuncMap{"get": get}).Parse(aboutTemplateString)
|
temp, err := template.New("about wiki").Funcs(template.FuncMap{"get": get}).Parse(aboutTemplateString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to parse About template", "err", err)
|
log.Fatalln(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
data := aboutData
|
data := aboutData
|
||||||
data.Version = version.Short
|
data.Version = version.Short
|
||||||
@ -115,8 +112,7 @@ func AboutHTML(lc *l18n.Localizer) string {
|
|||||||
var out strings.Builder
|
var out strings.Builder
|
||||||
err = temp.Execute(&out, data)
|
err = temp.Execute(&out, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to execute About template", "err", err)
|
log.Println(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package misc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -11,16 +11,16 @@ import (
|
|||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
"github.com/bouncepaw/mycorrhiza/backlinks"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
"github.com/bouncepaw/mycorrhiza/files"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/shroom"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/l18n"
|
"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/util"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/static"
|
"github.com/bouncepaw/mycorrhiza/viewutil"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitAssetHandlers(rtr *mux.Router) {
|
func InitAssetHandlers(rtr *mux.Router) {
|
||||||
@ -73,11 +73,11 @@ func handlerReindex(w http.ResponseWriter, rq *http.Request) {
|
|||||||
if ok := user.CanProceed(rq, "reindex"); !ok {
|
if ok := user.CanProceed(rq, "reindex"); !ok {
|
||||||
var lc = l18n.FromRequest(rq)
|
var lc = l18n.FromRequest(rq)
|
||||||
viewutil.HttpErr(viewutil.MetaFrom(w, rq), http.StatusForbidden, cfg.HomeHypha, lc.Get("ui.reindex_no_rights"))
|
viewutil.HttpErr(viewutil.MetaFrom(w, rq), http.StatusForbidden, cfg.HomeHypha, lc.Get("ui.reindex_no_rights"))
|
||||||
slog.Info("No rights to reindex")
|
log.Println("Rejected", rq.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hyphae.ResetCount()
|
hyphae.ResetCount()
|
||||||
slog.Info("Reindexing hyphae", "hyphaeDir", files.HyphaeDir())
|
log.Println("Reindexing hyphae in", files.HyphaeDir())
|
||||||
hyphae.Index(files.HyphaeDir())
|
hyphae.Index(files.HyphaeDir())
|
||||||
backlinks.IndexBacklinks()
|
backlinks.IndexBacklinks()
|
||||||
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||||
@ -89,10 +89,9 @@ func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) {
|
|||||||
if ok := user.CanProceed(rq, "update-header-links"); !ok {
|
if ok := user.CanProceed(rq, "update-header-links"); !ok {
|
||||||
var lc = l18n.FromRequest(rq)
|
var lc = l18n.FromRequest(rq)
|
||||||
viewutil.HttpErr(viewutil.MetaFrom(w, rq), http.StatusForbidden, cfg.HomeHypha, lc.Get("ui.header_no_rights"))
|
viewutil.HttpErr(viewutil.MetaFrom(w, rq), http.StatusForbidden, cfg.HomeHypha, lc.Get("ui.header_no_rights"))
|
||||||
slog.Info("No rights to update header links")
|
log.Println("Rejected", rq.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
slog.Info("Updated header links")
|
|
||||||
shroom.SetHeaderLinks()
|
shroom.SetHeaderLinks()
|
||||||
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
@ -134,7 +133,7 @@ func handlerAbout(w http.ResponseWriter, rq *http.Request) {
|
|||||||
map[string]string{},
|
map[string]string{},
|
||||||
))
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to write About template", "err", err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +148,7 @@ func handlerStyle(w http.ResponseWriter, rq *http.Request) {
|
|||||||
}
|
}
|
||||||
_, err = io.Copy(w, file)
|
_, err = io.Copy(w, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to write stylesheet; proceeding anyway", "err", err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
}
|
}
|
||||||
@ -164,7 +163,7 @@ func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) {
|
|||||||
}
|
}
|
||||||
_, err = io.Copy(w, file)
|
_, err = io.Copy(w, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to write robots.txt; proceeding anyway", "err", err)
|
log.Println()
|
||||||
}
|
}
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,9 +2,8 @@ package misc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
"github.com/bouncepaw/mycorrhiza/viewutil"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@ -2,17 +2,11 @@ package mycoopts
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"html"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/interwiki"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/l18n"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
|
||||||
|
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/options"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/options"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/interwiki"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MarkupOptions(hyphaName string) options.Options {
|
func MarkupOptions(hyphaName string) options.Options {
|
||||||
@ -58,58 +52,3 @@ func MarkupOptions(hyphaName string) options.Options {
|
|||||||
ImgSrcFormatForInterwikiPrefix: interwiki.ImgSrcFormatFor,
|
ImgSrcFormatForInterwikiPrefix: interwiki.ImgSrcFormatFor,
|
||||||
}.FillTheRest()
|
}.FillTheRest()
|
||||||
}
|
}
|
||||||
|
|
||||||
func mediaRaw(h *hyphae.MediaHypha) string {
|
|
||||||
return Media(h, l18n.New("en", "en"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Media(h *hyphae.MediaHypha, lc *l18n.Localizer) string {
|
|
||||||
name := html.EscapeString(h.CanonicalName())
|
|
||||||
|
|
||||||
switch filepath.Ext(h.MediaFilePath()) {
|
|
||||||
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
|
|
||||||
return fmt.Sprintf(
|
|
||||||
`<div class="binary-container binary-container_with-img">
|
|
||||||
<a href="/binary/%s"><img src="/binary/%s"/></a>
|
|
||||||
</div>`,
|
|
||||||
name, name,
|
|
||||||
)
|
|
||||||
|
|
||||||
case ".ogg", ".webm", ".mp4":
|
|
||||||
return fmt.Sprintf(
|
|
||||||
`<div class="binary-container binary-container_with-video">
|
|
||||||
<video controls>
|
|
||||||
<source src="/binary/%s"/>
|
|
||||||
<p>%s <a href="/binary/%s">%s</a></p>
|
|
||||||
</video>
|
|
||||||
</div>`,
|
|
||||||
name,
|
|
||||||
html.EscapeString(lc.Get("ui.media_novideo")),
|
|
||||||
name,
|
|
||||||
html.EscapeString(lc.Get("ui.media_novideo_link")),
|
|
||||||
)
|
|
||||||
|
|
||||||
case ".mp3", ".wav", ".flac":
|
|
||||||
return fmt.Sprintf(
|
|
||||||
`<div class="binary-container binary-container_with-audio">
|
|
||||||
<audio controls>
|
|
||||||
<source src="/binary/%s"/>
|
|
||||||
<p>%s <a href="/binary/%s">%s</a></p>
|
|
||||||
</audio>
|
|
||||||
</div>`,
|
|
||||||
name,
|
|
||||||
html.EscapeString(lc.Get("ui.media_noaudio")),
|
|
||||||
name,
|
|
||||||
html.EscapeString(lc.Get("ui.media_noaudio_link")),
|
|
||||||
)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf(
|
|
||||||
`<div class="binary-container binary-container_with-nothing">
|
|
||||||
<p><a href="/binary/%s">%s</a></p>
|
|
||||||
</div>`,
|
|
||||||
name,
|
|
||||||
html.EscapeString(lc.Get("ui.media_download")),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
37
mycoopts/view.qtpl
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{% import "path/filepath" %}
|
||||||
|
|
||||||
|
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
|
||||||
|
{% import "github.com/bouncepaw/mycorrhiza/l18n" %}
|
||||||
|
|
||||||
|
{% func mediaRaw(h *hyphae.MediaHypha) %}{%= Media(h, l18n.New("en", "en")) %}{% endfunc %}
|
||||||
|
|
||||||
|
{% func Media(h *hyphae.MediaHypha, lc *l18n.Localizer) %}
|
||||||
|
{% switch filepath.Ext(h.MediaFilePath()) %}
|
||||||
|
|
||||||
|
{% case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico" %}
|
||||||
|
<div class="binary-container binary-container_with-img">
|
||||||
|
<a href="/binary/{%s= h.CanonicalName() %}"><img src="/binary/{%s= h.CanonicalName() %}"/></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% case ".ogg", ".webm", ".mp4" %}
|
||||||
|
<div class="binary-container binary-container_with-video">
|
||||||
|
<video controls>
|
||||||
|
<source src="/binary/{%s= h.CanonicalName() %}"/>
|
||||||
|
<p>{%s lc.Get("ui.media_novideo") %} <a href="/binary/{%s= h.CanonicalName() %}">{%s lc.Get("ui.media_novideo_link") %}</a></p>
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% case ".mp3" %}
|
||||||
|
<div class="binary-container binary-container_with-audio">
|
||||||
|
<audio controls>
|
||||||
|
<source src="/binary/{%s= h.CanonicalName() %}"/>
|
||||||
|
<p>{%s lc.Get("ui.media_noaudio") %} <a href="/binary/{%s= h.CanonicalName() %}">{%s lc.Get("ui.media_noaudio_link") %}</a></p>
|
||||||
|
</audio>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% default %}
|
||||||
|
<div class="binary-container binary-container_with-nothing">
|
||||||
|
<p><a href="/binary/{%s= h.CanonicalName() %}">{%s lc.Get("ui.media_download") %}</a></p>
|
||||||
|
</div>
|
||||||
|
{% endswitch %}
|
||||||
|
{% endfunc %}
|
||||||
190
mycoopts/view.qtpl.go
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
// Code generated by qtc from "view.qtpl". DO NOT EDIT.
|
||||||
|
// See https://github.com/valyala/quicktemplate for details.
|
||||||
|
|
||||||
|
//line mycoopts/view.qtpl:1
|
||||||
|
package mycoopts
|
||||||
|
|
||||||
|
//line mycoopts/view.qtpl:1
|
||||||
|
import "path/filepath"
|
||||||
|
|
||||||
|
//line mycoopts/view.qtpl:3
|
||||||
|
import "github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
|
|
||||||
|
//line mycoopts/view.qtpl:4
|
||||||
|
import "github.com/bouncepaw/mycorrhiza/l18n"
|
||||||
|
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
import (
|
||||||
|
qtio422016 "io"
|
||||||
|
|
||||||
|
qt422016 "github.com/valyala/quicktemplate"
|
||||||
|
)
|
||||||
|
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
var (
|
||||||
|
_ = qtio422016.Copy
|
||||||
|
_ = qt422016.AcquireByteBuffer
|
||||||
|
)
|
||||||
|
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
func streammediaRaw(qw422016 *qt422016.Writer, h *hyphae.MediaHypha) {
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
StreamMedia(qw422016, h, l18n.New("en", "en"))
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
}
|
||||||
|
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
func writemediaRaw(qq422016 qtio422016.Writer, h *hyphae.MediaHypha) {
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
streammediaRaw(qw422016, h)
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
}
|
||||||
|
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
func mediaRaw(h *hyphae.MediaHypha) string {
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
writemediaRaw(qb422016, h)
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
return qs422016
|
||||||
|
//line mycoopts/view.qtpl:6
|
||||||
|
}
|
||||||
|
|
||||||
|
//line mycoopts/view.qtpl:8
|
||||||
|
func StreamMedia(qw422016 *qt422016.Writer, h *hyphae.MediaHypha, lc *l18n.Localizer) {
|
||||||
|
//line mycoopts/view.qtpl:8
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line mycoopts/view.qtpl:9
|
||||||
|
switch filepath.Ext(h.MediaFilePath()) {
|
||||||
|
//line mycoopts/view.qtpl:11
|
||||||
|
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
|
||||||
|
//line mycoopts/view.qtpl:11
|
||||||
|
qw422016.N().S(`
|
||||||
|
<div class="binary-container binary-container_with-img">
|
||||||
|
<a href="/binary/`)
|
||||||
|
//line mycoopts/view.qtpl:13
|
||||||
|
qw422016.N().S(h.CanonicalName())
|
||||||
|
//line mycoopts/view.qtpl:13
|
||||||
|
qw422016.N().S(`"><img src="/binary/`)
|
||||||
|
//line mycoopts/view.qtpl:13
|
||||||
|
qw422016.N().S(h.CanonicalName())
|
||||||
|
//line mycoopts/view.qtpl:13
|
||||||
|
qw422016.N().S(`"/></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
`)
|
||||||
|
//line mycoopts/view.qtpl:16
|
||||||
|
case ".ogg", ".webm", ".mp4":
|
||||||
|
//line mycoopts/view.qtpl:16
|
||||||
|
qw422016.N().S(`
|
||||||
|
<div class="binary-container binary-container_with-video">
|
||||||
|
<video controls>
|
||||||
|
<source src="/binary/`)
|
||||||
|
//line mycoopts/view.qtpl:19
|
||||||
|
qw422016.N().S(h.CanonicalName())
|
||||||
|
//line mycoopts/view.qtpl:19
|
||||||
|
qw422016.N().S(`"/>
|
||||||
|
<p>`)
|
||||||
|
//line mycoopts/view.qtpl:20
|
||||||
|
qw422016.E().S(lc.Get("ui.media_novideo"))
|
||||||
|
//line mycoopts/view.qtpl:20
|
||||||
|
qw422016.N().S(` <a href="/binary/`)
|
||||||
|
//line mycoopts/view.qtpl:20
|
||||||
|
qw422016.N().S(h.CanonicalName())
|
||||||
|
//line mycoopts/view.qtpl:20
|
||||||
|
qw422016.N().S(`">`)
|
||||||
|
//line mycoopts/view.qtpl:20
|
||||||
|
qw422016.E().S(lc.Get("ui.media_novideo_link"))
|
||||||
|
//line mycoopts/view.qtpl:20
|
||||||
|
qw422016.N().S(`</a></p>
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
`)
|
||||||
|
//line mycoopts/view.qtpl:24
|
||||||
|
case ".mp3":
|
||||||
|
//line mycoopts/view.qtpl:24
|
||||||
|
qw422016.N().S(`
|
||||||
|
<div class="binary-container binary-container_with-audio">
|
||||||
|
<audio controls>
|
||||||
|
<source src="/binary/`)
|
||||||
|
//line mycoopts/view.qtpl:27
|
||||||
|
qw422016.N().S(h.CanonicalName())
|
||||||
|
//line mycoopts/view.qtpl:27
|
||||||
|
qw422016.N().S(`"/>
|
||||||
|
<p>`)
|
||||||
|
//line mycoopts/view.qtpl:28
|
||||||
|
qw422016.E().S(lc.Get("ui.media_noaudio"))
|
||||||
|
//line mycoopts/view.qtpl:28
|
||||||
|
qw422016.N().S(` <a href="/binary/`)
|
||||||
|
//line mycoopts/view.qtpl:28
|
||||||
|
qw422016.N().S(h.CanonicalName())
|
||||||
|
//line mycoopts/view.qtpl:28
|
||||||
|
qw422016.N().S(`">`)
|
||||||
|
//line mycoopts/view.qtpl:28
|
||||||
|
qw422016.E().S(lc.Get("ui.media_noaudio_link"))
|
||||||
|
//line mycoopts/view.qtpl:28
|
||||||
|
qw422016.N().S(`</a></p>
|
||||||
|
</audio>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
`)
|
||||||
|
//line mycoopts/view.qtpl:32
|
||||||
|
default:
|
||||||
|
//line mycoopts/view.qtpl:32
|
||||||
|
qw422016.N().S(`
|
||||||
|
<div class="binary-container binary-container_with-nothing">
|
||||||
|
<p><a href="/binary/`)
|
||||||
|
//line mycoopts/view.qtpl:34
|
||||||
|
qw422016.N().S(h.CanonicalName())
|
||||||
|
//line mycoopts/view.qtpl:34
|
||||||
|
qw422016.N().S(`">`)
|
||||||
|
//line mycoopts/view.qtpl:34
|
||||||
|
qw422016.E().S(lc.Get("ui.media_download"))
|
||||||
|
//line mycoopts/view.qtpl:34
|
||||||
|
qw422016.N().S(`</a></p>
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
//line mycoopts/view.qtpl:36
|
||||||
|
}
|
||||||
|
//line mycoopts/view.qtpl:36
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line mycoopts/view.qtpl:37
|
||||||
|
}
|
||||||
|
|
||||||
|
//line mycoopts/view.qtpl:37
|
||||||
|
func WriteMedia(qq422016 qtio422016.Writer, h *hyphae.MediaHypha, lc *l18n.Localizer) {
|
||||||
|
//line mycoopts/view.qtpl:37
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line mycoopts/view.qtpl:37
|
||||||
|
StreamMedia(qw422016, h, lc)
|
||||||
|
//line mycoopts/view.qtpl:37
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line mycoopts/view.qtpl:37
|
||||||
|
}
|
||||||
|
|
||||||
|
//line mycoopts/view.qtpl:37
|
||||||
|
func Media(h *hyphae.MediaHypha, lc *l18n.Localizer) string {
|
||||||
|
//line mycoopts/view.qtpl:37
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line mycoopts/view.qtpl:37
|
||||||
|
WriteMedia(qb422016, h, lc)
|
||||||
|
//line mycoopts/view.qtpl:37
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line mycoopts/view.qtpl:37
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line mycoopts/view.qtpl:37
|
||||||
|
return qs422016
|
||||||
|
//line mycoopts/view.qtpl:37
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package web
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -6,9 +6,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
"github.com/bouncepaw/mycorrhiza/viewutil"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) {
|
func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) {
|
||||||
@ -48,7 +49,6 @@ func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) {
|
|||||||
f = f.WithError(err)
|
f = f.WithError(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: handle first attempt different
|
|
||||||
err := fmt.Errorf("incorrect password")
|
err := fmt.Errorf("incorrect password")
|
||||||
f = f.WithError(err)
|
f = f.WithError(err)
|
||||||
}
|
}
|
||||||
@ -58,11 +58,5 @@ func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) {
|
|||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
|
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
|
||||||
|
|
||||||
_ = pageChangePassword.RenderTo(
|
changePasswordPage(viewutil.MetaFrom(w, rq), f, u)
|
||||||
viewutil.MetaFrom(w, rq),
|
|
||||||
map[string]any{
|
|
||||||
"Form": f,
|
|
||||||
"U": u,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
47
settings/view.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@ -3,9 +3,9 @@ package shroom
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/l18n"
|
"github.com/bouncepaw/mycorrhiza/l18n"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: get rid of this abomination
|
// TODO: get rid of this abomination
|
||||||
@ -2,12 +2,11 @@ package shroom
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/backlinks"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/categories"
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/categories"
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Delete deletes the hypha and makes a history record about that.
|
// Delete deletes the hypha and makes a history record about that.
|
||||||
@ -1,16 +1,14 @@
|
|||||||
package shroom
|
package shroom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/mycoopts"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
|
||||||
|
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/blocks"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/blocks"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/mycoopts"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/viewutil"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetHeaderLinks initializes header links by reading the configured hypha, if there is any, or resorting to default values.
|
// SetHeaderLinks initializes header links by reading the configured hypha, if there is any, or resorting to default values.
|
||||||
21
shroom/log.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package shroom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func rejectRenameLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
||||||
|
log.Printf("Reject rename ‘%s’ by @%s: %s\n", h.CanonicalName(), u.Name, errmsg)
|
||||||
|
}
|
||||||
|
func rejectRemoveMediaLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
||||||
|
log.Printf("Reject remove media ‘%s’ by @%s: %s\n", h.CanonicalName(), u.Name, errmsg)
|
||||||
|
}
|
||||||
|
func rejectEditLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
||||||
|
log.Printf("Reject edit ‘%s’ by @%s: %s\n", h.CanonicalName(), u.Name, errmsg)
|
||||||
|
}
|
||||||
|
func rejectUploadMediaLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
||||||
|
log.Printf("Reject upload media ‘%s’ by @%s: %s\n", h.CanonicalName(), u.Name, errmsg)
|
||||||
|
}
|
||||||
@ -3,18 +3,18 @@ package shroom
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/backlinks"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/categories"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/files"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/categories"
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -3,7 +3,7 @@ package shroom
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
"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.
|
// 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.
|
||||||
@ -4,19 +4,18 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"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"
|
"io"
|
||||||
"log/slog"
|
"log"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"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 {
|
func historyMessageForTextUpload(h hyphae.Hypha, userMessage string) string {
|
||||||
@ -201,7 +200,7 @@ func UploadBinary(h hyphae.Hypha, mime string, file multipart.File, u *user.User
|
|||||||
if err := history.Rename(prevFilePath, uploadedFilePath); err != nil {
|
if err := history.Rename(prevFilePath, uploadedFilePath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
slog.Info("Move file", "from", prevFilePath, "to", uploadedFilePath)
|
log.Printf("Move ‘%s’ to ‘%s’\n", prevFilePath, uploadedFilePath)
|
||||||
h.SetMediaFilePath(uploadedFilePath)
|
h.SetMediaFilePath(uploadedFilePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -271,7 +271,7 @@ mark { background: rgba(130, 80, 30, 5); color: inherit; }
|
|||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
.categories-card #_cat-input {
|
.categories-card #_cat-name {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 .5rem;
|
padding: 0 .5rem;
|
||||||
@ -317,7 +317,7 @@ kbd {
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1000px;
|
max-width: 800px;
|
||||||
margin: 96px auto;
|
margin: 96px auto;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
transform: translate(-50%, 0);
|
transform: translate(-50%, 0);
|
||||||
|
Before Width: | Height: | Size: 531 B After Width: | Height: | Size: 531 B |
|
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 477 B |
|
Before Width: | Height: | Size: 955 B After Width: | Height: | Size: 955 B |
|
Before Width: | Height: | Size: 636 B After Width: | Height: | Size: 636 B |