1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2025-08-30 01:18:03 +00:00

New templates #117 (#236)

Didn't have the chance to migrate //all// templates just yet. We'll get there.

* Implement yet another template system

* Move orphans to the new system and fix a bug in it

* Link orphans in the admin panel

* Move the backlink handlers to the web package

* Move auth routing to web

* Move /user-list to the new system

* Move change password and translate it

* Move stuff

* Move admin-related stuff to the web

* Move a lot of files into internal dir

Outside of it are web and stuff that needs further refactoring

* Fix static not loading and de-qtpl tree

* Move tree to internal

* Keep the globe on the same line #230

* Revert "Keep the globe on the same line #230"

This reverts commit ae78e5e459b1e980ba89bf29e61f75c0625ed2c7.

* Migrate templates from hypview: delete, edit, start empty and existing WIP

The delete media view was removed, I didn't even know it still existed as a GET. A rudiment.

* Make views multi-file and break compilation

* Megarefactoring of hypha views

* Auth-related stuffs

* Fix some of those weird imports

* Migrate cat views

* Fix cat js

* Lower standards

* Internalize trauma
This commit is contained in:
Timur Ismagilov
2024-09-07 21:22:41 +03:00
committed by GitHub
parent a9ee700aad
commit 41733c50bd
129 changed files with 1766 additions and 3765 deletions

View File

@@ -1,126 +0,0 @@
package admin
import (
"embed"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/viewutil"
"github.com/gorilla/mux"
"net/http"
)
const adminTranslationRu = `
{{define "panel title"}}Панель админстратора{{end}}
{{define "panel safe section title"}}Безопасная секция{{end}}
{{define "panel link about"}}Об этой вики{{end}}
{{define "panel update header"}}Обновить ссылки в верхней панели{{end}}
{{define "panel link user list"}}Список пользователей{{end}}
{{define "panel users"}}Управление пользователями{{end}}
{{define "panel unsafe section title"}}Опасная секция{{end}}
{{define "panel shutdown"}}Выключить вики{{end}}
{{define "panel reindex hyphae"}}Переиндексировать гифы{{end}}
{{define "panel interwiki"}}Интервики{{end}}
{{define "manage users"}}Управление пользователями{{end}}
{{define "create user"}}Создать пользователя{{end}}
{{define "reindex users"}}Переиндексировать пользователей{{end}}
{{define "name"}}Имя{{end}}
{{define "group"}}Группа{{end}}
{{define "registered at"}}Зарегистрирован{{end}}
{{define "actions"}}Действия{{end}}
{{define "edit"}}Изменить{{end}}
{{define "new user"}}Новый пользователь{{end}}
{{define "password"}}Пароль{{end}}
{{define "confirm password"}}Подтвердить пароль{{end}}
{{define "change password"}}Изменить пароль{{end}}
{{define "non local password change"}}Поменять пароль можно только у локальных пользователей.{{end}}
{{define "create"}}Создать{{end}}
{{define "change group"}}Изменить группу{{end}}
{{define "user x"}}Пользователь {{.}}{{end}}
{{define "update"}}Обновить{{end}}
{{define "delete user"}}Удалить пользователя{{end}}
{{define "delete user tip"}}Удаляет пользователя из базы данных. Правки пользователя будут сохранены. Имя пользователя освободится для повторной регистрации.{{end}}
{{define "delete user?"}}Удалить пользователя {{.}}?{{end}}
{{define "delete user warning"}}Вы уверены, что хотите удалить этого пользователя из базы данных? Это действие нельзя отменить.{{end}}
`
var (
//go:embed *.html
fs embed.FS
panelChain, listChain, newUserChain, editUserChain, deleteUserChain viewutil.Chain
)
func Init(rtr *mux.Router) {
rtr.HandleFunc("/shutdown", handlerAdminShutdown).Methods(http.MethodPost)
rtr.HandleFunc("/reindex-users", handlerAdminReindexUsers).Methods(http.MethodPost)
rtr.HandleFunc("/new-user", handlerAdminUserNew).Methods(http.MethodGet, http.MethodPost)
rtr.HandleFunc("/users/{username}/edit", handlerAdminUserEdit).Methods(http.MethodGet, http.MethodPost)
rtr.HandleFunc("/users/{username}/change-password", handlerAdminUserChangePassword).Methods(http.MethodPost)
rtr.HandleFunc("/users/{username}/delete", handlerAdminUserDelete).Methods(http.MethodGet, http.MethodPost)
rtr.HandleFunc("/users", handlerAdminUsers)
rtr.HandleFunc("/", handlerAdmin)
panelChain = viewutil.CopyEnRuWith(fs, "view_panel.html", adminTranslationRu)
listChain = viewutil.CopyEnRuWith(fs, "view_user_list.html", adminTranslationRu)
newUserChain = viewutil.CopyEnRuWith(fs, "view_new_user.html", adminTranslationRu)
editUserChain = viewutil.CopyEnRuWith(fs, "view_edit_user.html", adminTranslationRu)
deleteUserChain = viewutil.CopyEnRuWith(fs, "view_delete_user.html", adminTranslationRu)
}
func viewPanel(meta viewutil.Meta) {
viewutil.ExecutePage(meta, panelChain, &viewutil.BaseData{})
}
type listData struct {
*viewutil.BaseData
UserHypha string
Users []*user.User
}
func viewList(meta viewutil.Meta, users []*user.User) {
viewutil.ExecutePage(meta, listChain, listData{
BaseData: &viewutil.BaseData{},
UserHypha: cfg.UserHypha,
Users: users,
})
}
type newUserData struct {
*viewutil.BaseData
Form util.FormData
}
func viewNewUser(meta viewutil.Meta, form util.FormData) {
viewutil.ExecutePage(meta, newUserChain, newUserData{
BaseData: &viewutil.BaseData{},
Form: form,
})
}
type editDeleteUserData struct {
*viewutil.BaseData
Form util.FormData
U *user.User
}
func viewEditUser(meta viewutil.Meta, form util.FormData, u *user.User) {
viewutil.ExecutePage(meta, editUserChain, editDeleteUserData{
BaseData: &viewutil.BaseData{},
Form: form,
U: u,
})
}
func viewDeleteUser(meta viewutil.Meta, form util.FormData, u *user.User) {
viewutil.ExecutePage(meta, deleteUserChain, editDeleteUserData{
BaseData: &viewutil.BaseData{},
Form: form,
U: u,
})
}

View File

@@ -1,213 +0,0 @@
{% import "net/http" %}
{% import "sort" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
{% import "github.com/bouncepaw/mycorrhiza/l18n" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% func Register(rq *http.Request) %}
{% code
lc := l18n.FromRequest(rq)
%}
<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 %}

View File

@@ -1,805 +0,0 @@
// Code generated by qtc from "auth.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line auth/auth.qtpl:1
package auth
//line auth/auth.qtpl:1
import "net/http"
//line auth/auth.qtpl:2
import "sort"
//line auth/auth.qtpl:3
import "github.com/bouncepaw/mycorrhiza/cfg"
//line auth/auth.qtpl:4
import "github.com/bouncepaw/mycorrhiza/l18n"
//line auth/auth.qtpl:5
import "github.com/bouncepaw/mycorrhiza/user"
//line auth/auth.qtpl:7
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line auth/auth.qtpl:7
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line auth/auth.qtpl:7
func StreamRegister(qw422016 *qt422016.Writer, rq *http.Request) {
//line auth/auth.qtpl:7
qw422016.N().S(`
`)
//line auth/auth.qtpl:9
lc := l18n.FromRequest(rq)
//line auth/auth.qtpl:10
qw422016.N().S(`
<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
}

View File

@@ -1,22 +0,0 @@
package auth
type L10nEntry struct {
_en string
_ru string
}
func En(v string) L10nEntry {
return L10nEntry{_en: v}
}
func (e L10nEntry) Ru(v string) L10nEntry {
e._ru = v
return e
}
func (e L10nEntry) Get(lang string) string {
if lang == "ru" && e._ru != "" {
return e._ru
}
return e._en
}

View File

@@ -1,225 +0,0 @@
package auth
import (
"errors"
"fmt"
"io"
"log"
"mime"
"net/http"
"strings"
"github.com/bouncepaw/mycorrhiza/viewutil"
"github.com/gorilla/mux"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/l18n"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
)
func InitAuth(r *mux.Router) {
r.HandleFunc("/user-list", handlerUserList)
r.HandleFunc("/lock", handlerLock)
// The check below saves a lot of extra checks and lines of codes in other places in this file.
if !cfg.UseAuth {
return
}
if cfg.AllowRegistration {
r.HandleFunc("/register", handlerRegister).Methods(http.MethodPost, http.MethodGet)
}
if cfg.TelegramEnabled {
r.HandleFunc("/telegram-login", handlerTelegramLogin)
}
r.HandleFunc("/login", handlerLogin)
r.HandleFunc("/logout", handlerLogout)
}
func handlerUserList(w http.ResponseWriter, rq *http.Request) {
lc := l18n.FromRequest(rq)
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
w.WriteHeader(http.StatusOK)
w.Write([]byte(viewutil.Base(viewutil.MetaFrom(w, rq), lc.Get("ui.users_title"), UserList(lc), map[string]string{})))
}
func handlerLock(w http.ResponseWriter, rq *http.Request) {
_, _ = io.WriteString(w, Lock(l18n.FromRequest(rq)))
}
// handlerRegister displays the register form (GET) or registers the user (POST).
func handlerRegister(w http.ResponseWriter, rq *http.Request) {
lc := l18n.FromRequest(rq)
util.PrepareRq(rq)
if rq.Method == http.MethodGet {
_, _ = io.WriteString(
w,
viewutil.Base(
viewutil.MetaFrom(w, rq),
lc.Get("auth.register_title"),
Register(rq),
map[string]string{},
),
)
return
}
var (
username = rq.PostFormValue("username")
password = rq.PostFormValue("password")
err = user.Register(username, password, "editor", "local", false)
)
if err != nil {
log.Printf("Failed to register %s: %s", username, err.Error())
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
w.WriteHeader(http.StatusBadRequest)
_, _ = io.WriteString(
w,
viewutil.Base(
viewutil.MetaFrom(w, rq),
lc.Get("auth.register_title"),
fmt.Sprintf(
`<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)
}

View File

@@ -1,85 +0,0 @@
package backlinks
import (
"embed"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/viewutil"
"github.com/gorilla/mux"
"net/http"
"sort"
)
func InitHandlers(rtr *mux.Router) {
rtr.PathPrefix("/backlinks/").HandlerFunc(handlerBacklinks)
rtr.PathPrefix("/orphans").HandlerFunc(handlerOrphans)
chainBacklinks = viewutil.CopyEnRuWith(fs, "view_backlinks.html", ruTranslation)
chainOrphans = viewutil.CopyEnRuWith(fs, "view_orphans.html", ruTranslation)
}
// handlerBacklinks lists all backlinks to a hypha.
func handlerBacklinks(w http.ResponseWriter, rq *http.Request) {
var (
hyphaName = util.HyphaNameFromRq(rq, "backlinks")
backlinks []string
)
for b := range yieldHyphaBacklinks(hyphaName) {
backlinks = append(backlinks, b)
}
viewBacklinks(viewutil.MetaFrom(w, rq), hyphaName, backlinks)
}
func handlerOrphans(w http.ResponseWriter, rq *http.Request) {
var orphans []string
for h := range hyphae.YieldExistingHyphae() {
if BacklinksCount(h.CanonicalName()) == 0 {
orphans = append(orphans, h.CanonicalName())
}
}
sort.Strings(orphans)
viewOrphans(viewutil.MetaFrom(w, rq), orphans)
}
var (
//go:embed *.html
fs embed.FS
ruTranslation = `
{{define "backlinks to text"}}Обратные ссылки на {{.}}{{end}}
{{define "backlinks to link"}}Обратные ссылки на <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,
})
}

View File

@@ -1,37 +0,0 @@
{{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-input" class="js-add-cat-name" autocomplete="off"
placeholder="{{block `placeholder` .}}Category n&zwnj;ame...{{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}}

View File

@@ -1,37 +0,0 @@
{{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}}

View File

@@ -1,107 +0,0 @@
package categories
import (
"embed"
"github.com/bouncepaw/mycorrhiza/viewutil"
"log"
"sort"
"strings"
)
const ruTranslation = `
{{define "empty cat"}}Эта категория пуста.{{end}}
{{define "cat"}}Категория{{end}}
{{define "hypha name"}}Название гифы{{end}}
{{define "categories"}}Категории{{end}}
{{define "placeholder"}}Название категории...{{end}}
{{define "remove from category title"}}Убрать гифу из этой категории{{end}}
{{define "add to category title"}}Добавить гифу в эту категорию{{end}}
{{define "category list"}}Список категорий{{end}}
{{define "no categories"}}В этой вики нет категорий.{{end}}
{{define "category x"}}Категория {{. | beautifulName}}{{end}}
{{define "edit category x"}}Редактирование категории {{beautifulName .}}{{end}}
{{define "edit category heading"}}Редактирование категории <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,
})
}

14
flag.go
View File

@@ -12,17 +12,17 @@ import (
"golang.org/x/term" "golang.org/x/term"
"github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/internal/cfg"
"github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/internal/files"
"github.com/bouncepaw/mycorrhiza/user" user2 "github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/version" "github.com/bouncepaw/mycorrhiza/internal/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],
@@ -70,13 +70,13 @@ func createAdminCommand(name string) {
} }
cfg.UseAuth = true cfg.UseAuth = true
cfg.AllowRegistration = true cfg.AllowRegistration = true
user.InitUserDatabase() user2.InitUserDatabase()
password, err := askPass("Password") password, err := askPass("Password")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := user.Register(name, password, "admin", "local", true); err != nil { if err := user2.Register(name, password, "admin", "local", true); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@@ -2,15 +2,17 @@ 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 (

View File

@@ -3,12 +3,11 @@ package history
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/bouncepaw/mycorrhiza/internal/cfg"
"net/url" "net/url"
"strings" "strings"
"time" "time"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/gorilla/feeds" "github.com/gorilla/feeds"
) )

View File

@@ -9,7 +9,7 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/internal/files"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )

View File

@@ -4,12 +4,12 @@ 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/history"
"github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/internal/cfg"
"github.com/bouncepaw/mycorrhiza/internal/files"
hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/viewutil" viewutil2 "github.com/bouncepaw/mycorrhiza/web/viewutil"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"html/template" "html/template"
"log" "log"
@@ -30,9 +30,9 @@ func InitHandlers(rtr *mux.Router) {
rtr.HandleFunc("/recent-changes-atom", handlerRecentChangesAtom) rtr.HandleFunc("/recent-changes-atom", handlerRecentChangesAtom)
rtr.HandleFunc("/recent-changes-json", handlerRecentChangesJSON) rtr.HandleFunc("/recent-changes-json", handlerRecentChangesJSON)
chainPrimitiveDiff = viewutil.CopyEnRuWith(fs, "view_primitive_diff.html", ruTranslation) chainPrimitiveDiff = viewutil2.CopyEnRuWith(fs, "view_primitive_diff.html", ruTranslation)
chainRecentChanges = viewutil.CopyEnRuWith(fs, "view_recent_changes.html", ruTranslation) chainRecentChanges = viewutil2.CopyEnRuWith(fs, "view_recent_changes.html", ruTranslation)
chainHistory = viewutil.CopyEnRuWith(fs, "view_history.html", ruTranslation) chainHistory = viewutil2.CopyEnRuWith(fs, "view_history.html", ruTranslation)
} }
func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
@@ -45,12 +45,12 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
} }
var ( var (
mycoFilePath string mycoFilePath string
h = hyphae.ByName(util.CanonicalName(slug)) h = hyphae2.ByName(util.CanonicalName(slug))
) )
switch h := h.(type) { switch h := h.(type) {
case hyphae.ExistingHypha: case hyphae2.ExistingHypha:
mycoFilePath = h.TextFilePath() mycoFilePath = h.TextFilePath()
case *hyphae.EmptyHypha: case *hyphae2.EmptyHypha:
mycoFilePath = filepath.Join(files.HyphaeDir(), h.CanonicalName()+".myco") mycoFilePath = filepath.Join(files.HyphaeDir(), h.CanonicalName()+".myco")
} }
text, err := history.PrimitiveDiffAtRevision(mycoFilePath, revHash) text, err := history.PrimitiveDiffAtRevision(mycoFilePath, revHash)
@@ -58,7 +58,7 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
primitiveDiff(viewutil.MetaFrom(w, rq), h, revHash, text) primitiveDiff(viewutil2.MetaFrom(w, rq), h, revHash, text)
} }
// handlerRecentChanges displays the /recent-changes/ page. // handlerRecentChanges displays the /recent-changes/ page.
@@ -68,7 +68,7 @@ func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) {
if editCount > 100 { if editCount > 100 {
return return
} }
recentChanges(viewutil.MetaFrom(w, rq), editCount, history.RecentChanges(editCount)) recentChanges(viewutil2.MetaFrom(w, rq), editCount, history.RecentChanges(editCount))
} }
// handlerHistory lists all revisions of a hypha. // handlerHistory lists all revisions of a hypha.
@@ -83,7 +83,7 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) {
} }
log.Println("Found", len(revs), "revisions for", hyphaName) log.Println("Found", len(revs), "revisions for", hyphaName)
historyView(viewutil.MetaFrom(w, rq), hyphaName, list) historyView(viewutil2.MetaFrom(w, rq), hyphaName, list)
} }
// genericHandlerOfFeeds is a helper function for the web feed handlers. // genericHandlerOfFeeds is a helper function for the web feed handlers.
@@ -135,20 +135,20 @@ var (
{{define "n recent changes"}}{{.}} свеж{{if eq . 1}}ая правка{{else if le . 4}}их правок{{else}}их правок{{end}}{{end}} {{define "n recent changes"}}{{.}} свеж{{if eq . 1}}ая правка{{else if le . 4}}их правок{{else}}их правок{{end}}{{end}}
{{define "recent empty"}}Правки не найдены.{{end}} {{define "recent empty"}}Правки не найдены.{{end}}
` `
chainPrimitiveDiff, chainRecentChanges, chainHistory viewutil.Chain chainPrimitiveDiff, chainRecentChanges, chainHistory viewutil2.Chain
) )
type recentChangesData struct { type recentChangesData struct {
*viewutil.BaseData *viewutil2.BaseData
EditCount int EditCount int
Changes []history.Revision Changes []history.Revision
UserHypha string UserHypha string
Stops []int Stops []int
} }
func recentChanges(meta viewutil.Meta, editCount int, changes []history.Revision) { func recentChanges(meta viewutil2.Meta, editCount int, changes []history.Revision) {
viewutil.ExecutePage(meta, chainRecentChanges, recentChangesData{ viewutil2.ExecutePage(meta, chainRecentChanges, recentChangesData{
BaseData: &viewutil.BaseData{}, BaseData: &viewutil2.BaseData{},
EditCount: editCount, EditCount: editCount,
Changes: changes, Changes: changes,
UserHypha: cfg.UserHypha, UserHypha: cfg.UserHypha,
@@ -157,13 +157,13 @@ func recentChanges(meta viewutil.Meta, editCount int, changes []history.Revision
} }
type primitiveDiffData struct { type primitiveDiffData struct {
*viewutil.BaseData *viewutil2.BaseData
HyphaName string HyphaName string
Hash string Hash string
Text template.HTML Text template.HTML
} }
func primitiveDiff(meta viewutil.Meta, h hyphae.Hypha, hash, text string) { func primitiveDiff(meta viewutil2.Meta, h hyphae2.Hypha, hash, text string) {
hunks := history.SplitPrimitiveDiff(text) hunks := history.SplitPrimitiveDiff(text)
if len(hunks) > 0 { if len(hunks) > 0 {
var buf strings.Builder var buf strings.Builder
@@ -198,8 +198,8 @@ func primitiveDiff(meta viewutil.Meta, h hyphae.Hypha, hash, text string) {
text = fmt.Sprintf( text = fmt.Sprintf(
`<pre class="codeblock"><code>%s</code></pre>`, text) `<pre class="codeblock"><code>%s</code></pre>`, text)
} }
viewutil.ExecutePage(meta, chainPrimitiveDiff, primitiveDiffData{ viewutil2.ExecutePage(meta, chainPrimitiveDiff, primitiveDiffData{
BaseData: &viewutil.BaseData{}, BaseData: &viewutil2.BaseData{},
HyphaName: h.CanonicalName(), HyphaName: h.CanonicalName(),
Hash: hash, Hash: hash,
Text: template.HTML(text), Text: template.HTML(text),
@@ -207,14 +207,14 @@ func primitiveDiff(meta viewutil.Meta, h hyphae.Hypha, hash, text string) {
} }
type historyData struct { type historyData struct {
*viewutil.BaseData *viewutil2.BaseData
HyphaName string HyphaName string
Contents string Contents string
} }
func historyView(meta viewutil.Meta, hyphaName, contents string) { func historyView(meta viewutil2.Meta, hyphaName, contents string) {
viewutil.ExecutePage(meta, chainHistory, historyData{ viewutil2.ExecutePage(meta, chainHistory, historyData{
BaseData: &viewutil.BaseData{ BaseData: &viewutil2.BaseData{
Addr: "/history/" + util.CanonicalName(hyphaName), Addr: "/history/" + util.CanonicalName(hyphaName),
}, },
HyphaName: hyphaName, HyphaName: hyphaName,

View File

@@ -4,11 +4,11 @@ package history
// Things related to writing history. // Things related to writing history.
import ( import (
"fmt" "fmt"
"github.com/bouncepaw/mycorrhiza/internal/user"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )

View File

@@ -8,7 +8,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/internal/files"
) )
// Revision represents a revision, duh. Hash is usually short. Username is extracted from email. // Revision represents a revision, duh. Hash is usually short. Username is extracted from email.

View File

@@ -1,5 +1,5 @@
{% import "fmt" %} {% import "fmt" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %} {% import "github.com/bouncepaw/mycorrhiza/internal/cfg" %}
HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string. HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string.
{% func (rev Revision) HyphaeLinksHTML() %} {% func (rev Revision) HyphaeLinksHTML() %}
@@ -56,22 +56,18 @@ WithRevisions returns an html representation of `revs` that is meant to be inser
</a> </a>
<ul class="history__entries"> <ul class="history__entries">
{% for _, rev := range grp %} {% for _, rev := range grp %}
{%= rev.asHistoryEntry(hyphaName) %} <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>
{% endfor %} {% endfor %}
</ul> </ul>
</section> </section>
{% endfor %} {% endfor %}
{% endfunc %} {% 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 %}

View File

@@ -8,7 +8,7 @@ package history
import "fmt" import "fmt"
//line history/view.qtpl:2 //line history/view.qtpl:2
import "github.com/bouncepaw/mycorrhiza/cfg" import "github.com/bouncepaw/mycorrhiza/internal/cfg"
// HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string. // HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string.
@@ -283,141 +283,102 @@ func StreamWithRevisions(qw422016 *qt422016.Writer, hyphaName string, revs []Rev
for _, rev := range grp { for _, rev := range grp {
//line history/view.qtpl:58 //line history/view.qtpl:58
qw422016.N().S(` qw422016.N().S(`
`) <li class="history__entry">
//line history/view.qtpl:59 <a class="history-entry" href="/rev/`)
rev.streamasHistoryEntry(qw422016, hyphaName) //line history/view.qtpl:60
//line history/view.qtpl:59 qw422016.E().S(rev.Hash)
//line history/view.qtpl:60
qw422016.N().S(`/`)
//line history/view.qtpl:60
qw422016.E().S(hyphaName)
//line history/view.qtpl:60
qw422016.N().S(`">
<time class="history-entry__time">`)
//line history/view.qtpl:61
qw422016.E().S(rev.timeToDisplay())
//line history/view.qtpl:61
qw422016.N().S(`</time>
</a>
<span class="history-entry__hash"><a href="/primitive-diff/`)
//line history/view.qtpl:63
qw422016.E().S(rev.Hash)
//line history/view.qtpl:63
qw422016.N().S(`/`)
//line history/view.qtpl:63
qw422016.E().S(hyphaName)
//line history/view.qtpl:63
qw422016.N().S(`">`)
//line history/view.qtpl:63
qw422016.E().S(rev.Hash)
//line history/view.qtpl:63
qw422016.N().S(`</a></span>
<span class="history-entry__msg">`)
//line history/view.qtpl:64
qw422016.E().S(rev.Message)
//line history/view.qtpl:64
qw422016.N().S(`</span>
`)
//line history/view.qtpl:65
if rev.Username != "anon" {
//line history/view.qtpl:65
qw422016.N().S(`
<span class="history-entry__author">by <a href="/hypha/`)
//line history/view.qtpl:66
qw422016.E().S(cfg.UserHypha)
//line history/view.qtpl:66
qw422016.N().S(`/`)
//line history/view.qtpl:66
qw422016.E().S(rev.Username)
//line history/view.qtpl:66
qw422016.N().S(`" rel="author">`)
//line history/view.qtpl:66
qw422016.E().S(rev.Username)
//line history/view.qtpl:66
qw422016.N().S(`</a></span>
`)
//line history/view.qtpl:67
}
//line history/view.qtpl:67
qw422016.N().S(` qw422016.N().S(`
</li>
`) `)
//line history/view.qtpl:60 //line history/view.qtpl:69
} }
//line history/view.qtpl:60 //line history/view.qtpl:69
qw422016.N().S(` qw422016.N().S(`
</ul> </ul>
</section> </section>
`) `)
//line history/view.qtpl:63 //line history/view.qtpl:72
} }
//line history/view.qtpl:63 //line history/view.qtpl:72
qw422016.N().S(` qw422016.N().S(`
`) `)
//line history/view.qtpl:64 //line history/view.qtpl:73
} }
//line history/view.qtpl:64 //line history/view.qtpl:73
func WriteWithRevisions(qq422016 qtio422016.Writer, hyphaName string, revs []Revision) { func WriteWithRevisions(qq422016 qtio422016.Writer, hyphaName string, revs []Revision) {
//line history/view.qtpl:64 //line history/view.qtpl:73
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line history/view.qtpl:64 //line history/view.qtpl:73
StreamWithRevisions(qw422016, hyphaName, revs) StreamWithRevisions(qw422016, hyphaName, revs)
//line history/view.qtpl:64 //line history/view.qtpl:73
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line history/view.qtpl:64 //line history/view.qtpl:73
} }
//line history/view.qtpl:64 //line history/view.qtpl:73
func WithRevisions(hyphaName string, revs []Revision) string { func WithRevisions(hyphaName string, revs []Revision) string {
//line history/view.qtpl:64 //line history/view.qtpl:73
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line history/view.qtpl:64 //line history/view.qtpl:73
WriteWithRevisions(qb422016, hyphaName, revs) 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 //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) qs422016 := string(qb422016.B)
//line history/view.qtpl:77 //line history/view.qtpl:73
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line history/view.qtpl:77 //line history/view.qtpl:73
return qs422016 return qs422016
//line history/view.qtpl:77 //line history/view.qtpl:73
} }

View File

@@ -1,14 +1,13 @@
package main package main
import ( import (
"github.com/bouncepaw/mycorrhiza/internal/cfg"
"log" "log"
"net" "net"
"net/http" "net/http"
"os" "os"
"strings" "strings"
"time" "time"
"github.com/bouncepaw/mycorrhiza/cfg"
) )
func serveHTTP(handler http.Handler) { func serveHTTP(handler http.Handler) {

View File

@@ -2,72 +2,19 @@ package hypview
import ( import (
"embed" "embed"
"github.com/bouncepaw/mycorrhiza/backlinks"
"html/template" "html/template"
"log" "log"
"strings" "strings"
"github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/internal/backlinks"
"github.com/bouncepaw/mycorrhiza/viewutil" "github.com/bouncepaw/mycorrhiza/internal/cfg"
"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}}
@@ -75,48 +22,15 @@ 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 {
@@ -135,49 +49,6 @@ func RenameHypha(meta viewutil.Meta, hyphaName string) {
}) })
} }
type deleteRemoveMediaData struct {
*viewutil.BaseData
HyphaName string
}
func DeleteHypha(meta viewutil.Meta, hyphaName string) {
viewutil.ExecutePage(meta, chainDeleteHypha, deleteRemoveMediaData{
BaseData: &viewutil.BaseData{
Addr: "/delete/" + hyphaName,
},
HyphaName: hyphaName,
})
}
func RemoveMedia(meta viewutil.Meta, hyphaName string) {
viewutil.ExecutePage(meta, chainRemoveMedia, deleteRemoveMediaData{
BaseData: &viewutil.BaseData{
Addr: "/remove-media/" + hyphaName,
},
HyphaName: hyphaName,
})
}
type emptyHyphaData struct {
Meta viewutil.Meta
HyphaName string
AllowRegistration bool
UseAuth bool
}
func EmptyHypha(meta viewutil.Meta, hyphaName string) string {
var buf strings.Builder
if err := chainEmptyHypha.Get(meta).ExecuteTemplate(&buf, "empty hypha card", emptyHyphaData{
Meta: meta,
HyphaName: hyphaName,
AllowRegistration: cfg.AllowRegistration,
UseAuth: cfg.UseAuth,
}); err != nil {
log.Println(err)
}
return buf.String()
}
type naviTitleData struct { type naviTitleData struct {
HyphaNameParts []string HyphaNameParts []string
HyphaNamePartsWithParents []string HyphaNamePartsWithParents []string
@@ -185,7 +56,7 @@ type naviTitleData struct {
HomeHypha string HomeHypha string
} }
func NaviTitle(meta viewutil.Meta, hyphaName string) string { func NaviTitle(meta viewutil.Meta, hyphaName string) template.HTML {
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{
@@ -197,7 +68,7 @@ func NaviTitle(meta viewutil.Meta, hyphaName string) string {
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
return buf.String() return template.HTML(buf.String())
} }
func naviTitleify(hyphaName string) ([]string, []string) { func naviTitleify(hyphaName string) ([]string, []string) {

View File

@@ -1,50 +0,0 @@
{% import "github.com/bouncepaw/mycorrhiza/backlinks" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
{% import "github.com/bouncepaw/mycorrhiza/viewutil" %}
{% func hyphaInfoEntry(h hyphae.Hypha, u *user.User, action string, hasToExist bool, displayText string) %}
{% code flag := true %}
{% switch h.(type) %}
{% case *hyphae.EmptyHypha %}
{% code flag = !hasToExist %}
{% endswitch %}
{% if u.CanProceed(action) && flag %}
<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 %}

View File

@@ -1,311 +0,0 @@
// Code generated by qtc from "nav.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line hypview/nav.qtpl:1
package hypview
//line hypview/nav.qtpl:1
import "github.com/bouncepaw/mycorrhiza/backlinks"
//line hypview/nav.qtpl:2
import "github.com/bouncepaw/mycorrhiza/cfg"
//line hypview/nav.qtpl:3
import "github.com/bouncepaw/mycorrhiza/hyphae"
//line hypview/nav.qtpl:4
import "github.com/bouncepaw/mycorrhiza/user"
//line hypview/nav.qtpl:5
import "github.com/bouncepaw/mycorrhiza/util"
//line hypview/nav.qtpl:6
import "github.com/bouncepaw/mycorrhiza/viewutil"
//line hypview/nav.qtpl:8
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line hypview/nav.qtpl:8
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line hypview/nav.qtpl:8
func streamhyphaInfoEntry(qw422016 *qt422016.Writer, h hyphae.Hypha, u *user.User, action string, hasToExist bool, displayText string) {
//line hypview/nav.qtpl:8
qw422016.N().S(`
`)
//line hypview/nav.qtpl:9
flag := true
//line hypview/nav.qtpl:9
qw422016.N().S(`
`)
//line hypview/nav.qtpl:10
switch h.(type) {
//line hypview/nav.qtpl:11
case *hyphae.EmptyHypha:
//line hypview/nav.qtpl:11
qw422016.N().S(`
`)
//line hypview/nav.qtpl:12
flag = !hasToExist
//line hypview/nav.qtpl:12
qw422016.N().S(`
`)
//line hypview/nav.qtpl:13
}
//line hypview/nav.qtpl:13
qw422016.N().S(`
`)
//line hypview/nav.qtpl:14
if u.CanProceed(action) && flag {
//line hypview/nav.qtpl:14
qw422016.N().S(`
<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
}

View File

@@ -1,161 +0,0 @@
{% import "net/http" %}
{% import "strings" %}
{% import "path" %}
{% import "os" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
{% import "github.com/bouncepaw/mycorrhiza/categories" %}
{% import "github.com/bouncepaw/mycorrhiza/l18n" %}
{% import "github.com/bouncepaw/mycorrhiza/mimetype" %}
{% import "github.com/bouncepaw/mycorrhiza/tree" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
{% import "github.com/bouncepaw/mycorrhiza/viewutil" %}
{% func MediaMenu(rq *http.Request, h hyphae.Hypha, u *user.User) %}
{% code
lc := l18n.FromRequest(rq)
%}
<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 %}

View File

@@ -1,651 +0,0 @@
// Code generated by qtc from "readers.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line hypview/readers.qtpl:1
package hypview
//line hypview/readers.qtpl:1
import "net/http"
//line hypview/readers.qtpl:2
import "strings"
//line hypview/readers.qtpl:3
import "path"
//line hypview/readers.qtpl:4
import "os"
//line hypview/readers.qtpl:6
import "github.com/bouncepaw/mycorrhiza/cfg"
//line hypview/readers.qtpl:7
import "github.com/bouncepaw/mycorrhiza/hyphae"
//line hypview/readers.qtpl:8
import "github.com/bouncepaw/mycorrhiza/categories"
//line hypview/readers.qtpl:9
import "github.com/bouncepaw/mycorrhiza/l18n"
//line hypview/readers.qtpl:10
import "github.com/bouncepaw/mycorrhiza/mimetype"
//line hypview/readers.qtpl:11
import "github.com/bouncepaw/mycorrhiza/tree"
//line hypview/readers.qtpl:12
import "github.com/bouncepaw/mycorrhiza/user"
//line hypview/readers.qtpl:13
import "github.com/bouncepaw/mycorrhiza/util"
//line hypview/readers.qtpl:14
import "github.com/bouncepaw/mycorrhiza/viewutil"
//line hypview/readers.qtpl:16
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line hypview/readers.qtpl:16
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line hypview/readers.qtpl:16
func StreamMediaMenu(qw422016 *qt422016.Writer, rq *http.Request, h hyphae.Hypha, u *user.User) {
//line hypview/readers.qtpl:16
qw422016.N().S(`
`)
//line hypview/readers.qtpl:18
lc := l18n.FromRequest(rq)
//line hypview/readers.qtpl:19
qw422016.N().S(`
<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
}

View File

@@ -1,32 +0,0 @@
{{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}}

View File

@@ -1,22 +0,0 @@
{{define "remove media from x?"}}Remove media from {{beautifulName .}}?{{end}}
{{define "title"}}{{template "remove media from x?" .HyphaName}}{{end}}
{{define "body"}}
<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}}

View File

@@ -2,10 +2,11 @@
package backlinks package backlinks
import ( import (
hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"log" "log"
"os" "os"
"sort"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
@@ -13,7 +14,7 @@ import (
func yieldHyphaBacklinks(hyphaName string) <-chan string { func yieldHyphaBacklinks(hyphaName string) <-chan string {
hyphaName = util.CanonicalName(hyphaName) hyphaName = util.CanonicalName(hyphaName)
out := make(chan string) out := make(chan string)
sorted := hyphae.PathographicSort(out) sorted := hyphae2.PathographicSort(out)
go func() { go func() {
backlinks, exists := backlinkIndex[hyphaName] backlinks, exists := backlinkIndex[hyphaName]
if exists { if exists {
@@ -42,7 +43,7 @@ var backlinkIndex = make(map[string]linkSet)
// IndexBacklinks traverses all text hyphae, extracts links from them and forms an initial index. Call it when indexing and reindexing hyphae. // IndexBacklinks traverses all text hyphae, extracts links from them and forms an initial index. Call it when indexing and reindexing hyphae.
func IndexBacklinks() { func IndexBacklinks() {
// It is safe to ignore the mutex, because there is only one worker. // It is safe to ignore the mutex, because there is only one worker.
for h := range hyphae.FilterHyphaeWithText(hyphae.YieldExistingHyphae()) { for h := range hyphae2.FilterHyphaeWithText(hyphae2.YieldExistingHyphae()) {
foundLinks := extractHyphaLinksFromContent(h.CanonicalName(), fetchText(h)) foundLinks := extractHyphaLinksFromContent(h.CanonicalName(), fetchText(h))
for _, link := range foundLinks { for _, link := range foundLinks {
if _, exists := backlinkIndex[link]; !exists { if _, exists := backlinkIndex[link]; !exists {
@@ -61,6 +62,25 @@ 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 hyphae2.YieldExistingHyphae() {
if BacklinksCount(h.CanonicalName()) == 0 {
orphans = append(orphans, h.CanonicalName())
}
}
sort.Strings(orphans)
return orphans
}
// Using set here seems like the most appropriate solution // Using set here seems like the most appropriate solution
type linkSet map[string]struct{} type linkSet map[string]struct{}
@@ -72,14 +92,14 @@ func toLinkSet(xs []string) linkSet {
return result return result
} }
func fetchText(h hyphae.Hypha) string { func fetchText(h hyphae2.Hypha) string {
var path string var path string
switch h := h.(type) { switch h := h.(type) {
case *hyphae.EmptyHypha: case *hyphae2.EmptyHypha:
return "" return ""
case *hyphae.TextualHypha: case *hyphae2.TextualHypha:
path = h.TextFilePath() path = h.TextFilePath()
case *hyphae.MediaHypha: case *hyphae2.MediaHypha:
if !h.HasTextFile() { if !h.HasTextFile() {
return "" return ""
} }

View File

@@ -5,7 +5,7 @@ import (
"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/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/mycoopts" "github.com/bouncepaw/mycorrhiza/mycoopts"
) )

View File

@@ -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)

View File

@@ -2,13 +2,14 @@ package categories
import ( import (
"encoding/json" "encoding/json"
"github.com/bouncepaw/mycorrhiza/files"
"github.com/bouncepaw/mycorrhiza/util"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"log" "log"
"os" "os"
"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{}

View File

@@ -2,12 +2,11 @@
package files package files
import ( import (
"github.com/bouncepaw/mycorrhiza/internal/cfg"
"github.com/bouncepaw/mycorrhiza/web/static"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/static"
) )
var paths struct { var paths struct {

View File

@@ -1,12 +1,11 @@
package hyphae package hyphae
import ( import (
"github.com/bouncepaw/mycorrhiza/internal/mimetype"
"log" "log"
"log/slog" "log/slog"
"os" "os"
"path/filepath" "path/filepath"
"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.

View File

@@ -1,9 +1,10 @@
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 {

View File

@@ -2,7 +2,7 @@ package migration
import ( import (
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools" "git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
"github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/internal/files"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"

View File

@@ -8,13 +8,14 @@
package migration package migration
import ( import (
"github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/user"
"io" "io"
"log" "log"
"os" "os"
"strings" "strings"
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
) )
func genericLineMigrator( func genericLineMigrator(

View File

@@ -2,7 +2,7 @@ package migration
import ( import (
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools" "git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
"github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/internal/files"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"

View File

@@ -2,23 +2,23 @@ package shroom
import ( import (
"errors" "errors"
hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/l18n" "github.com/bouncepaw/mycorrhiza/l18n"
"github.com/bouncepaw/mycorrhiza/user"
) )
// TODO: get rid of this abomination // TODO: get rid of this abomination
func canFactory( func canFactory(
rejectLogger func(hyphae.Hypha, *user.User, string), rejectLogger func(hyphae2.Hypha, *user.User, string),
action string, action string,
dispatcher func(hyphae.Hypha, *user.User, *l18n.Localizer) (string, string), dispatcher func(hyphae2.Hypha, *user.User, *l18n.Localizer) (string, string),
noRightsMsg string, noRightsMsg string,
notExistsMsg string, notExistsMsg string,
mustExist bool, mustExist bool,
) func(*user.User, hyphae.Hypha, *l18n.Localizer) error { ) func(*user.User, hyphae2.Hypha, *l18n.Localizer) error {
return func(u *user.User, h hyphae.Hypha, lc *l18n.Localizer) error { return func(u *user.User, h hyphae2.Hypha, lc *l18n.Localizer) error {
if !u.CanProceed(action) { if !u.CanProceed(action) {
rejectLogger(h, u, "no rights") rejectLogger(h, u, "no rights")
return errors.New(noRightsMsg) return errors.New(noRightsMsg)
@@ -26,7 +26,7 @@ func canFactory(
if mustExist { if mustExist {
switch h.(type) { switch h.(type) {
case *hyphae.EmptyHypha: case *hyphae2.EmptyHypha:
rejectLogger(h, u, "does not exist") rejectLogger(h, u, "does not exist")
return errors.New(notExistsMsg) return errors.New(notExistsMsg)
} }

View File

@@ -2,29 +2,29 @@ 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/hyphae" "github.com/bouncepaw/mycorrhiza/internal/backlinks"
"github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/internal/categories"
hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/internal/user"
) )
// Delete deletes the hypha and makes a history record about that. // Delete deletes the hypha and makes a history record about that.
func Delete(u *user.User, h hyphae.ExistingHypha) error { func Delete(u *user.User, h hyphae2.ExistingHypha) error {
hop := history. hop := history.
Operation(history.TypeDeleteHypha). Operation(history.TypeDeleteHypha).
WithMsg(fmt.Sprintf("Delete %s", h.CanonicalName())). WithMsg(fmt.Sprintf("Delete %s", h.CanonicalName())).
WithUser(u) WithUser(u)
originalText, _ := hyphae.FetchMycomarkupFile(h) originalText, _ := hyphae2.FetchMycomarkupFile(h)
switch h := h.(type) { switch h := h.(type) {
case *hyphae.MediaHypha: case *hyphae2.MediaHypha:
if h.HasTextFile() { if h.HasTextFile() {
hop.WithFilesRemoved(h.MediaFilePath(), h.TextFilePath()) hop.WithFilesRemoved(h.MediaFilePath(), h.TextFilePath())
} else { } else {
hop.WithFilesRemoved(h.MediaFilePath()) hop.WithFilesRemoved(h.MediaFilePath())
} }
case *hyphae.TextualHypha: case *hyphae2.TextualHypha:
hop.WithFilesRemoved(h.TextFilePath()) hop.WithFilesRemoved(h.TextFilePath())
} }
if hop.Apply().HasErrors() { if hop.Apply().HasErrors() {
@@ -32,6 +32,6 @@ func Delete(u *user.User, h hyphae.ExistingHypha) error {
} }
backlinks.UpdateBacklinksAfterDelete(h, originalText) backlinks.UpdateBacklinksAfterDelete(h, originalText)
categories.RemoveHyphaFromAllCategories(h.CanonicalName()) categories.RemoveHyphaFromAllCategories(h.CanonicalName())
hyphae.DeleteHypha(h) hyphae2.DeleteHypha(h)
return nil return nil
} }

View File

@@ -4,19 +4,19 @@ import (
"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/internal/cfg"
"github.com/bouncepaw/mycorrhiza/hyphae" hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/mycoopts" "github.com/bouncepaw/mycorrhiza/mycoopts"
"github.com/bouncepaw/mycorrhiza/viewutil" "github.com/bouncepaw/mycorrhiza/web/viewutil"
"os" "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.
func SetHeaderLinks() { func SetHeaderLinks() {
switch userLinksHypha := hyphae.ByName(cfg.HeaderLinksHypha).(type) { switch userLinksHypha := hyphae2.ByName(cfg.HeaderLinksHypha).(type) {
case *hyphae.EmptyHypha: case *hyphae2.EmptyHypha:
setDefaultHeaderLinks() setDefaultHeaderLinks()
case hyphae.ExistingHypha: case hyphae2.ExistingHypha:
contents, err := os.ReadFile(userLinksHypha.TextFilePath()) contents, err := os.ReadFile(userLinksHypha.TextFilePath())
if err != nil || len(contents) == 0 { if err != nil || len(contents) == 0 {
setDefaultHeaderLinks() setDefaultHeaderLinks()

View File

@@ -1,10 +1,9 @@
package shroom package shroom
import ( import (
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/internal/user"
"log" "log"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/user"
) )
func rejectRenameLog(h hyphae.Hypha, u *user.User, errmsg string) { func rejectRenameLog(h hyphae.Hypha, u *user.User, errmsg string) {

View File

@@ -3,36 +3,36 @@ package shroom
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/bouncepaw/mycorrhiza/backlinks" "github.com/bouncepaw/mycorrhiza/internal/backlinks"
"github.com/bouncepaw/mycorrhiza/categories" "github.com/bouncepaw/mycorrhiza/internal/categories"
"github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/internal/cfg"
"github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/internal/files"
hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/internal/user"
"path" "path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
// Rename renames the old hypha to the new name and makes a history record about that. Call if and only if the user has the permission to rename. // Rename renames the old hypha to the new name and makes a history record about that. Call if and only if the user has the permission to rename.
func Rename(oldHypha hyphae.ExistingHypha, newName string, recursive bool, leaveRedirections bool, u *user.User) error { func Rename(oldHypha hyphae2.ExistingHypha, newName string, recursive bool, leaveRedirections bool, u *user.User) error {
// * bouncepaw hates this function and related renaming functions // * bouncepaw hates this function and related renaming functions
if newName == "" { if newName == "" {
rejectRenameLog(oldHypha, u, "no new name given") rejectRenameLog(oldHypha, u, "no new name given")
return errors.New("ui.rename_noname_tip") return errors.New("ui.rename_noname_tip")
} }
if !hyphae.IsValidName(newName) { if !hyphae2.IsValidName(newName) {
rejectRenameLog(oldHypha, u, fmt.Sprintf("new name %s invalid", newName)) rejectRenameLog(oldHypha, u, fmt.Sprintf("new name %s invalid", newName))
return errors.New("ui.rename_badname_tip") // FIXME: There is a bug related to this. return errors.New("ui.rename_badname_tip") // FIXME: There is a bug related to this.
} }
switch targetHypha := hyphae.ByName(newName); targetHypha.(type) { switch targetHypha := hyphae2.ByName(newName); targetHypha.(type) {
case hyphae.ExistingHypha: case hyphae2.ExistingHypha:
if targetHypha.CanonicalName() == oldHypha.CanonicalName() { if targetHypha.CanonicalName() == oldHypha.CanonicalName() {
return nil return nil
} }
@@ -81,7 +81,7 @@ func Rename(oldHypha hyphae.ExistingHypha, newName string, recursive bool, leave
oldName = h.CanonicalName() oldName = h.CanonicalName()
newName = re.ReplaceAllString(oldName, newName) newName = re.ReplaceAllString(oldName, newName)
) )
hyphae.RenameHyphaTo(h, newName, replaceName) hyphae2.RenameHyphaTo(h, newName, replaceName)
backlinks.UpdateBacklinksAfterRename(h, oldName) backlinks.UpdateBacklinksAfterRename(h, oldName)
categories.RenameHyphaInAllCategories(oldName, newName) categories.RenameHyphaInAllCategories(oldName, newName)
if leaveRedirections { if leaveRedirections {
@@ -104,12 +104,12 @@ const redirectionTemplate = `=> %[1]s | 👁️➡️ %[2]s
func leaveRedirection(oldName, newName string, hop *history.Op) error { func leaveRedirection(oldName, newName string, hop *history.Op) error {
var ( var (
text = fmt.Sprintf(redirectionTemplate, newName, util.BeautifulName(newName)) text = fmt.Sprintf(redirectionTemplate, newName, util.BeautifulName(newName))
emptyHypha = hyphae.ByName(oldName) emptyHypha = hyphae2.ByName(oldName)
) )
switch emptyHypha := emptyHypha.(type) { switch emptyHypha := emptyHypha.(type) {
case *hyphae.EmptyHypha: case *hyphae2.EmptyHypha:
h := hyphae.ExtendEmptyToTextual(emptyHypha, filepath.Join(files.HyphaeDir(), oldName+".myco")) h := hyphae2.ExtendEmptyToTextual(emptyHypha, filepath.Join(files.HyphaeDir(), oldName+".myco"))
hyphae.Insert(h) hyphae2.Insert(h)
categories.AddHyphaToCategory(oldName, cfg.RedirectionCategory) categories.AddHyphaToCategory(oldName, cfg.RedirectionCategory)
defer backlinks.UpdateBacklinksAfterEdit(h, "") defer backlinks.UpdateBacklinksAfterEdit(h, "")
return writeTextToDisk(h, []byte(text), hop) return writeTextToDisk(h, []byte(text), hop)
@@ -118,15 +118,15 @@ func leaveRedirection(oldName, newName string, hop *history.Op) error {
} }
} }
func findHyphaeToRename(superhypha hyphae.ExistingHypha, recursive bool) []hyphae.ExistingHypha { func findHyphaeToRename(superhypha hyphae2.ExistingHypha, recursive bool) []hyphae2.ExistingHypha {
hyphaList := []hyphae.ExistingHypha{superhypha} hyphaList := []hyphae2.ExistingHypha{superhypha}
if recursive { if recursive {
hyphaList = append(hyphaList, hyphae.Subhyphae(superhypha)...) hyphaList = append(hyphaList, hyphae2.Subhyphae(superhypha)...)
} }
return hyphaList return hyphaList
} }
func renamingPairs(hyphaeToRename []hyphae.ExistingHypha, replaceName func(string) string) (map[string]string, error) { func renamingPairs(hyphaeToRename []hyphae2.ExistingHypha, replaceName func(string) string) (map[string]string, error) {
var ( var (
renameMap = make(map[string]string) renameMap = make(map[string]string)
newNames = make([]string, len(hyphaeToRename)) newNames = make([]string, len(hyphaeToRename))
@@ -138,12 +138,12 @@ func renamingPairs(hyphaeToRename []hyphae.ExistingHypha, replaceName func(strin
renameMap[h.TextFilePath()] = replaceName(h.TextFilePath()) renameMap[h.TextFilePath()] = replaceName(h.TextFilePath())
} }
switch h := h.(type) { switch h := h.(type) {
case *hyphae.MediaHypha: case *hyphae2.MediaHypha:
renameMap[h.MediaFilePath()] = replaceName(h.MediaFilePath()) renameMap[h.MediaFilePath()] = replaceName(h.MediaFilePath())
} }
h.Unlock() h.Unlock()
} }
if firstFailure, ok := hyphae.AreFreeNames(newNames...); !ok { if firstFailure, ok := hyphae2.AreFreeNames(newNames...); !ok {
return nil, errors.New("Hypha " + firstFailure + " already exists") return nil, errors.New("Hypha " + firstFailure + " already exists")
} }
return renameMap, nil return renameMap, nil

View File

@@ -1,9 +1,9 @@
package shroom package shroom
import ( import (
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
"strings" "strings"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )

View File

@@ -2,14 +2,14 @@ package shroom
import ( import (
"fmt" "fmt"
hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/user"
) )
// RemoveMedia removes media from the media hypha and makes a history record about that. If it only had media, the hypha will be deleted. If it also had text, the hypha will become textual. // RemoveMedia removes media from the media hypha and makes a history record about that. If it only had media, the hypha will be deleted. If it also had text, the hypha will become textual.
func RemoveMedia(u *user.User, h *hyphae.MediaHypha) error { func RemoveMedia(u *user.User, h *hyphae2.MediaHypha) error {
hop := history. hop := history.
Operation(history.TypeRemoveMedia). Operation(history.TypeRemoveMedia).
WithFilesRemoved(h.MediaFilePath()). WithFilesRemoved(h.MediaFilePath()).
@@ -24,9 +24,9 @@ func RemoveMedia(u *user.User, h *hyphae.MediaHypha) error {
} }
if h.HasTextFile() { if h.HasTextFile() {
hyphae.Insert(hyphae.ShrinkMediaToTextual(h)) hyphae2.Insert(hyphae2.ShrinkMediaToTextual(h))
} else { } else {
hyphae.DeleteHypha(h) hyphae2.DeleteHypha(h)
} }
return nil return nil
} }

View File

@@ -4,18 +4,19 @@ 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" "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 {

View File

@@ -1,14 +1,18 @@
package tree package tree
import ( import (
"github.com/bouncepaw/mycorrhiza/hyphae" "fmt"
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/util"
"html/template"
"io"
"path" "path"
"sort" "sort"
"strings" "strings"
) )
// Tree returns the subhypha matrix as HTML and names of the next and previous hyphae (or empty strings). // Tree returns the subhypha matrix as HTML and names of the next and previous hyphae (or empty strings).
func Tree(hyphaName string) (childrenHTML, prev, next string) { func Tree(hyphaName string) (childrenHTML template.HTML, prev, next string) {
var ( var (
root = child{hyphaName, true, make([]child, 0)} root = child{hyphaName, true, make([]child, 0)}
descendantPrefix = hyphaName + "/" descendantPrefix = hyphaName + "/"
@@ -44,6 +48,41 @@ type child struct {
children []child children []child
} }
/*
Subhyphae links are recursive. It may end up looking like that if drawn with
pseudographics:
Foo The presented hyphae are ./foo and ./foo/bar
Bar
*/
func childHTML(c *child, w io.Writer) {
sort.Slice(c.children, func(i, j int) bool {
return c.children[i].name < c.children[j].name
})
_, _ = io.WriteString(w, "<li class=\"subhyphae__entry\">\n<a class=\"subhyphae__link")
if !c.exists {
_, _ = io.WriteString(w, " wikilink_new")
}
_, _ = io.WriteString(w, fmt.Sprintf(
"\" href=\"/hypha/%s\">%s</a>\n",
c.name,
util.BeautifulName(path.Base(c.name)),
))
if len(c.children) > 0 {
_, _ = io.WriteString(w, "<ul>\n")
for _, child := range c.children {
childHTML(&child, w)
}
_, _ = io.WriteString(w, "</ul>\n")
}
_, _ = io.WriteString(w, "</li>\n")
}
func addHyphaToChild(hyphaName, subPath string, child *child) { func addHyphaToChild(hyphaName, subPath string, child *child) {
// when hyphaName = "root/a/b", subPath = "a/b", and child.name = "root" // when hyphaName = "root/a/b", subPath = "a/b", and child.name = "root"
// addHyphaToChild("root/a/b", "b", child{"root/a"}) // addHyphaToChild("root/a/b", "b", child{"root/a"})
@@ -78,12 +117,13 @@ func findOrCreateSubchild(name string, baseChild *child) *child {
return &baseChild.children[len(baseChild.children)-1] return &baseChild.children[len(baseChild.children)-1]
} }
func subhyphaeMatrix(children []child) (html string) { func subhyphaeMatrix(children []child) template.HTML {
sort.Slice(children, func(i, j int) bool { sort.Slice(children, func(i, j int) bool {
return children[i].name < children[j].name return children[i].name < children[j].name
}) })
var buf strings.Builder
for _, child := range children { for _, child := range children {
html += childHTML(&child) childHTML(&child, &buf)
} }
return html return template.HTML(buf.String())
} }

View File

@@ -3,11 +3,11 @@ package user
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/bouncepaw/mycorrhiza/internal/cfg"
"log" "log"
"os" "os"
"github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/internal/files"
"github.com/bouncepaw/mycorrhiza/files"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )

View File

@@ -6,6 +6,7 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"github.com/bouncepaw/mycorrhiza/internal/cfg"
"log" "log"
"net/http" "net/http"
"sort" "sort"
@@ -14,7 +15,6 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
@@ -79,6 +79,11 @@ func Register(username, password, group, source string, force bool) error {
return SaveUserDatabase() return SaveUserDatabase()
} }
var (
ErrUnknownUsername = errors.New("unknown username")
ErrWrongPassword = errors.New("wrong password")
)
// LoginDataHTTP logs such user in and returns string representation of an error if there is any. // LoginDataHTTP logs such user in and returns string representation of an error if there is any.
// //
// The HTTP parameters are used for setting header status (bad request, if it is bad) and saving a cookie. // The HTTP parameters are used for setting header status (bad request, if it is bad) and saving a cookie.
@@ -87,12 +92,12 @@ func LoginDataHTTP(w http.ResponseWriter, username, password string) error {
if !HasUsername(username) { if !HasUsername(username) {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
log.Println("Unknown username", username, "was entered") log.Println("Unknown username", username, "was entered")
return errors.New("unknown username") return ErrUnknownUsername
} }
if !CredentialsOK(username, password) { if !CredentialsOK(username, password) {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
log.Println("A wrong password was entered for username", username) log.Println("A wrong password was entered for username", username)
return errors.New("wrong password") return ErrWrongPassword
} }
token, err := AddSession(username) token, err := AddSession(username)
if err != nil { if err != nil {

View File

@@ -2,12 +2,12 @@ package user
import ( import (
"fmt" "fmt"
"github.com/bouncepaw/mycorrhiza/internal/cfg"
"net/http" "net/http"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/bouncepaw/mycorrhiza/cfg"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@@ -37,8 +37,8 @@ var minimalRights = map[string]int{
"upload-binary": 1, "upload-binary": 1,
"rename": 1, "rename": 1,
"upload-text": 1, "upload-text": 1,
"add-to-category": 2, "add-to-category": 1,
"remove-from-category": 2, "remove-from-category": 1,
"remove-media": 2, "remove-media": 2,
"update-header-links": 3, "update-header-links": 3,
"delete": 3, "delete": 3,

View File

@@ -1,6 +1,9 @@
package user package user
import "sync" import (
"sort"
"sync"
)
var users sync.Map var users sync.Map
var tokens sync.Map var tokens sync.Map
@@ -99,3 +102,24 @@ func terminateSession(token string) {
tokens.Delete(token) tokens.Delete(token)
dumpTokens() dumpTokens()
} }
func UsersInGroups() (admins []string, moderators []string, editors []string, readers []string) {
for u := range YieldUsers() {
switch u.Group {
// What if we place the users into sorted slices?
case "admin":
admins = append(admins, u.Name)
case "moderator":
moderators = append(moderators, u.Name)
case "editor", "trusted":
editors = append(editors, u.Name)
case "reader":
readers = append(readers, u.Name)
}
}
sort.Strings(admins)
sort.Strings(moderators)
sort.Strings(editors)
sort.Strings(readers)
return
}

View File

@@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"git.sr.ht/~bouncepaw/mycomarkup/v5/options" "git.sr.ht/~bouncepaw/mycomarkup/v5/options"
"github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/internal/files"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
"log" "log"
"os" "os"

View File

@@ -2,7 +2,7 @@ package interwiki
import ( import (
"embed" "embed"
"github.com/bouncepaw/mycorrhiza/viewutil" "github.com/bouncepaw/mycorrhiza/web/viewutil"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"log" "log"
"net/http" "net/http"

View File

@@ -3,33 +3,29 @@
"password": "Password", "password": "Password",
"register_title": "Register", "register_title": "Register",
"register_header": "Register on {{.name}}", "register_header": "",
"register_button": "Register", "register_button": "Register",
"login_title": "Login",
"login_header": "Log in to {{.name}}",
"login_button": "Log in",
"logout_title": "Logout?", "logout_title": "",
"logout_header": "Log out?", "logout_header": "Log out?",
"logout_button": "Confirm", "logout_button": "Confirm",
"logout_anon": "You cannot log out because you are not logged in.", "logout_anon": "",
"lock_title": "Locked", "lock_title": "Locked",
"password_tip": "The server stores your password in an encrypted form; even administrators cannot read it.", "password_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.", "cookie_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.", "telegram_tip": "",
"noauth": "Authentication is disabled. You can make edits anonymously.", "noauth": "",
"noregister": "Registrations are currently closed. Administrators can make an account for you by hand; contact them.", "noregister": "Registrations are currently closed. Administrators can make an account for you by hand; contact them.",
"error_username": "Unknown username.", "error_username": "",
"error_password": "Wrong password.", "error_password": "",
"error_telegram": "Could not authorize using Telegram.", "error_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"
} }

View File

@@ -4,18 +4,6 @@
"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",
@@ -57,9 +45,9 @@
"diff_title": "Diff of {{.name}} at {{.rev}}", "diff_title": "Diff of {{.name}} at {{.rev}}",
"revision_title": "{{.name}} at {{.rev}}", "revision_title": "",
"revision_warning": "Please note that viewing media is not supported in history for now.", "revision_warning": "",
"revision_link": "Get Mycomarkup source of this revision", "revision_link": "",
"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}}",
@@ -76,25 +64,6 @@
"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"
} }

View File

@@ -2,31 +2,31 @@
"username": "Логин", "username": "Логин",
"password": "Пароль", "password": "Пароль",
"register_title": "Регистрация", "register_title": "",
"register_header": "Регистрация на «{{.name}}»", "register_header": "",
"register_button": "Зарегистрироваться", "register_button": "",
"login_title": "Вход", "login_title": "Вход",
"login_header": "Вход в «{{.name}}»", "login_header": "",
"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 в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.", "cookie_tip": "",
"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": "Домой",

View File

@@ -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": "{{.name}} из {{.rev}}", "revision_title": "",
"revision_warning": "Обратите внимание, просмотр медиа в истории пока что недоступен.", "revision_warning": "",
"revision_link": "Посмотреть Микоразметку для этой ревизии", "revision_link": "",
"revision_no_text": "В этой ревизии гифы не было текста.", "revision_no_text": "В этой ревизии гифы не было текста.",
"about_title": "О {{.name}}", "about_title": "О {{.name}}",
@@ -78,26 +78,6 @@
"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": "Отмена"
} }

27
main.go
View File

@@ -1,30 +1,27 @@
// 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=history
//go:generate go run github.com/valyala/quicktemplate/qtc -dir=mycoopts //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 (
"github.com/bouncepaw/mycorrhiza/backlinks" "github.com/bouncepaw/mycorrhiza/internal/categories"
"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" "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/hyphae" "github.com/bouncepaw/mycorrhiza/internal/backlinks"
"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() {

View File

@@ -1,10 +1,10 @@
package misc package misc
import ( import (
"github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/internal/cfg"
"github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/internal/version"
"github.com/bouncepaw/mycorrhiza/l18n" "github.com/bouncepaw/mycorrhiza/l18n"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/version"
"log" "log"
"strings" "strings"
"text/template" // sic! "text/template" // sic!

View File

@@ -11,16 +11,16 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/bouncepaw/mycorrhiza/backlinks" "github.com/bouncepaw/mycorrhiza/internal/backlinks"
"github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/internal/cfg"
"github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/internal/files"
"github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/internal/shroom"
"github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/l18n" "github.com/bouncepaw/mycorrhiza/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/viewutil" "github.com/bouncepaw/mycorrhiza/web/static"
"github.com/bouncepaw/mycorrhiza/web/viewutil"
) )
func InitAssetHandlers(rtr *mux.Router) { func InitAssetHandlers(rtr *mux.Router) {

View File

@@ -2,8 +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 (

View File

@@ -3,8 +3,8 @@ package mycoopts
import ( import (
"errors" "errors"
"git.sr.ht/~bouncepaw/mycomarkup/v5/options" "git.sr.ht/~bouncepaw/mycomarkup/v5/options"
"github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/internal/cfg"
"github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/interwiki" "github.com/bouncepaw/mycorrhiza/interwiki"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )

View File

@@ -1,6 +1,6 @@
{% import "path/filepath" %} {% import "path/filepath" %}
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %} {% import "github.com/bouncepaw/mycorrhiza/internal/hyphae" %}
{% import "github.com/bouncepaw/mycorrhiza/l18n" %} {% import "github.com/bouncepaw/mycorrhiza/l18n" %}
{% func mediaRaw(h *hyphae.MediaHypha) %}{%= Media(h, l18n.New("en", "en")) %}{% endfunc %} {% func mediaRaw(h *hyphae.MediaHypha) %}{%= Media(h, l18n.New("en", "en")) %}{% endfunc %}

View File

@@ -8,7 +8,7 @@ package mycoopts
import "path/filepath" import "path/filepath"
//line mycoopts/view.qtpl:3 //line mycoopts/view.qtpl:3
import "github.com/bouncepaw/mycorrhiza/hyphae" import "github.com/bouncepaw/mycorrhiza/internal/hyphae"
//line mycoopts/view.qtpl:4 //line mycoopts/view.qtpl:4
import "github.com/bouncepaw/mycorrhiza/l18n" import "github.com/bouncepaw/mycorrhiza/l18n"

View File

@@ -1,47 +0,0 @@
package settings
import (
"embed"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/viewutil"
"github.com/gorilla/mux"
"net/http"
"github.com/bouncepaw/mycorrhiza/user"
)
// TODO: translate untranslated strings
const settingsTranslationRu = `
{{define "change password"}}Change password{{end}}
{{define "confirm password"}}Confirm password{{end}}
{{define "current password"}}Current password{{end}}
{{define "non local password change"}}Non-local accounts cannot have their passwords changed.{{end}}
{{define "password"}}Password{{end}}
{{define "submit"}}Submit{{end}}
`
var (
//go:embed *.html
fs embed.FS
changePassowrdChain viewutil.Chain
)
func Init(rtr *mux.Router) {
rtr.HandleFunc("/change-password", handlerUserChangePassword).Methods(http.MethodGet, http.MethodPost)
changePassowrdChain = viewutil.CopyEnRuWith(fs, "view_change_password.html", settingsTranslationRu)
}
func changePasswordPage(meta viewutil.Meta, form util.FormData, u *user.User) {
viewutil.ExecutePage(meta, changePassowrdChain, changePasswordData{
BaseData: &viewutil.BaseData{},
Form: form,
U: u,
})
}
type changePasswordData struct {
*viewutil.BaseData
Form util.FormData
U *user.User
}

View File

@@ -1,32 +0,0 @@
{% import "sort" %}
{% import "path" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
Subhyphae links are recursive. It may end up looking like that if drawn with
pseudographics:
╔══════════════╗
║Foo ║ The presented hyphae are ./foo and ./foo/bar
║╔════════════╗║
║║Bar ║║
║╚════════════╝║
╚══════════════╝
{% func childHTML(c *child) %}
{% code
sort.Slice(c.children, func(i, j int) bool {
return c.children[i].name < c.children[j].name
})
%}
<li class="subhyphae__entry">
<a class="subhyphae__link {% if !c.exists %}wikilink_new{% endif %}" href="/hypha/{%s c.name %}">
{%s util.BeautifulName(path.Base(c.name)) %}
</a>
{% if len(c.children) > 0 %}
<ul>
{% for _, child := range c.children %}
{%s= childHTML(&child) %}
{% endfor %}
</ul>
{% endif %}
</li>
{% endfunc %}

View File

@@ -1,126 +0,0 @@
// Code generated by qtc from "view.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line tree/view.qtpl:1
package tree
//line tree/view.qtpl:1
import "sort"
//line tree/view.qtpl:2
import "path"
//line tree/view.qtpl:3
import "github.com/bouncepaw/mycorrhiza/util"
// Subhyphae links are recursive. It may end up looking like that if drawn with
// pseudographics:
// ╔══════════════╗
// ║Foo ║ The presented hyphae are ./foo and ./foo/bar
// ║╔════════════╗║
// ║║Bar ║║
// ║╚════════════╝║
// ╚══════════════╝
//line tree/view.qtpl:13
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line tree/view.qtpl:13
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line tree/view.qtpl:13
func streamchildHTML(qw422016 *qt422016.Writer, c *child) {
//line tree/view.qtpl:13
qw422016.N().S(`
`)
//line tree/view.qtpl:15
sort.Slice(c.children, func(i, j int) bool {
return c.children[i].name < c.children[j].name
})
//line tree/view.qtpl:18
qw422016.N().S(`
<li class="subhyphae__entry">
<a class="subhyphae__link `)
//line tree/view.qtpl:20
if !c.exists {
//line tree/view.qtpl:20
qw422016.N().S(`wikilink_new`)
//line tree/view.qtpl:20
}
//line tree/view.qtpl:20
qw422016.N().S(`" href="/hypha/`)
//line tree/view.qtpl:20
qw422016.E().S(c.name)
//line tree/view.qtpl:20
qw422016.N().S(`">
`)
//line tree/view.qtpl:21
qw422016.E().S(util.BeautifulName(path.Base(c.name)))
//line tree/view.qtpl:21
qw422016.N().S(`
</a>
`)
//line tree/view.qtpl:23
if len(c.children) > 0 {
//line tree/view.qtpl:23
qw422016.N().S(`
<ul>
`)
//line tree/view.qtpl:25
for _, child := range c.children {
//line tree/view.qtpl:25
qw422016.N().S(`
`)
//line tree/view.qtpl:26
qw422016.N().S(childHTML(&child))
//line tree/view.qtpl:26
qw422016.N().S(`
`)
//line tree/view.qtpl:27
}
//line tree/view.qtpl:27
qw422016.N().S(`
</ul>
`)
//line tree/view.qtpl:29
}
//line tree/view.qtpl:29
qw422016.N().S(`
</li>
`)
//line tree/view.qtpl:31
}
//line tree/view.qtpl:31
func writechildHTML(qq422016 qtio422016.Writer, c *child) {
//line tree/view.qtpl:31
qw422016 := qt422016.AcquireWriter(qq422016)
//line tree/view.qtpl:31
streamchildHTML(qw422016, c)
//line tree/view.qtpl:31
qt422016.ReleaseWriter(qw422016)
//line tree/view.qtpl:31
}
//line tree/view.qtpl:31
func childHTML(c *child) string {
//line tree/view.qtpl:31
qb422016 := qt422016.AcquireByteBuffer()
//line tree/view.qtpl:31
writechildHTML(qb422016, c)
//line tree/view.qtpl:31
qs422016 := string(qb422016.B)
//line tree/view.qtpl:31
qt422016.ReleaseByteBuffer(qb422016)
//line tree/view.qtpl:31
return qs422016
//line tree/view.qtpl:31
}

View File

@@ -3,13 +3,14 @@ package util
import ( import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"github.com/bouncepaw/mycorrhiza/files"
"log" "log"
"net/http" "net/http"
"strings" "strings"
"github.com/bouncepaw/mycorrhiza/internal/cfg"
"github.com/bouncepaw/mycorrhiza/internal/files"
"git.sr.ht/~bouncepaw/mycomarkup/v5/util" "git.sr.ht/~bouncepaw/mycomarkup/v5/util"
"github.com/bouncepaw/mycorrhiza/cfg"
) )
// PrepareRq strips the trailing / in rq.URL.Path. In the future it might do more stuff for making all request structs uniform. // PrepareRq strips the trailing / in rq.URL.Path. In the future it might do more stuff for making all request structs uniform.
@@ -31,6 +32,7 @@ func ShorterPath(path string) string {
} }
// HTTP404Page writes a 404 error in the status, needed when no content is found on the page. // HTTP404Page writes a 404 error in the status, needed when no content is found on the page.
// TODO: demolish
func HTTP404Page(w http.ResponseWriter, page string) { func HTTP404Page(w http.ResponseWriter, page string) {
w.Header().Set("Content-Type", "text/html;charset=utf-8") w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
@@ -38,6 +40,7 @@ func HTTP404Page(w http.ResponseWriter, page string) {
} }
// HTTP200Page wraps some frequently used things for successful 200 responses. // HTTP200Page wraps some frequently used things for successful 200 responses.
// TODO: demolish
func HTTP200Page(w http.ResponseWriter, page string) { func HTTP200Page(w http.ResponseWriter, page string) {
w.Header().Set("Content-Type", "text/html;charset=utf-8") w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)

View File

@@ -1,20 +1,110 @@
package admin package web
import ( import (
"fmt" "fmt"
"github.com/bouncepaw/mycorrhiza/viewutil"
"github.com/gorilla/mux"
"log" "log"
"mime" "mime"
"net/http" "net/http"
"os" "os"
"sort" "sort"
"github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/internal/cfg"
"github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/internal/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")

View File

@@ -1,40 +1,46 @@
package categories package web
import ( import (
"github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/internal/categories"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/viewutil"
"github.com/gorilla/mux"
"io" "io"
"log" "log"
"log/slog"
"net/http" "net/http"
"sort"
"strings" "strings"
)
// InitHandlers initializes HTTP handlers for the given router. Call somewhere in package web. "github.com/bouncepaw/mycorrhiza/internal/user"
func InitHandlers(r *mux.Router) { "github.com/bouncepaw/mycorrhiza/util"
r.PathPrefix("/add-to-category").HandlerFunc(handlerAddToCategory).Methods("POST") "github.com/bouncepaw/mycorrhiza/web/viewutil"
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 == "" {
http.Error(w, "No category name", http.StatusBadRequest) viewutil.HandlerNotFound(w, rq)
return return
} }
log.Println("Editing category", catName)
categoryEdit(viewutil.MetaFrom(w, rq), catName) slog.Info("Editing category", "name", catName)
_ = pageCatEdit.RenderTo(meta, map[string]any{
"Addr": "/edit-category/" + catName,
"CatName": catName,
"Hyphae": categories.HyphaeInCategory(catName),
"GivenPermissionToModify": meta.U.CanProceed("add-to-category"),
})
} }
func handlerListCategory(w http.ResponseWriter, rq *http.Request) { func handlerListCategory(w http.ResponseWriter, rq *http.Request) {
log.Println("Viewing list of categories") slog.Info("Viewing list of categories")
categoryList(viewutil.MetaFrom(w, rq)) cats := categories.ListOfCategories()
sort.Strings(cats)
_ = pageCatList.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
"Addr": "/category",
"Categories": cats,
})
} }
func handlerCategory(w http.ResponseWriter, rq *http.Request) { func handlerCategory(w http.ResponseWriter, rq *http.Request) {
@@ -44,8 +50,15 @@ func handlerCategory(w http.ResponseWriter, rq *http.Request) {
handlerListCategory(w, rq) handlerListCategory(w, rq)
return return
} }
log.Println("Viewing category", catName)
categoryPage(viewutil.MetaFrom(w, rq), catName) meta := viewutil.MetaFrom(w, rq)
slog.Info("Viewing category", "name", catName)
_ = pageCatPage.RenderTo(meta, map[string]any{
"Addr": "/category/" + catName,
"CatName": catName,
"Hyphae": categories.HyphaeInCategory(catName),
"GivenPermissionToModify": meta.U.CanProceed("add-to-category"),
})
} }
// A request for removal of hyphae can either remove one hypha (used in the card on /hypha) or many hyphae (used in /edit-category). Both approaches are handled by /remove-from-category. This function finds all passed hyphae. // A request for removal of hyphae can either remove one hypha (used in the card on /hypha) or many hyphae (used in /edit-category). Both approaches are handled by /remove-from-category. This function finds all passed hyphae.
@@ -93,7 +106,7 @@ func handlerRemoveFromCategory(w http.ResponseWriter, rq *http.Request) {
} }
for _, hyphaName := range hyphaNames { for _, hyphaName := range hyphaNames {
// TODO: Make it more effective. // TODO: Make it more effective.
removeHyphaFromCategory(hyphaName, catName) categories.RemoveHyphaFromCategory(hyphaName, catName)
} }
log.Printf("%s removed %q from category %s\n", u.Name, hyphaNames, catName) log.Printf("%s removed %q from category %s\n", u.Name, hyphaNames, catName)
http.Redirect(w, rq, redirectTo, http.StatusSeeOther) http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
@@ -115,7 +128,7 @@ func handlerAddToCategory(w http.ResponseWriter, rq *http.Request) {
http.Redirect(w, rq, redirectTo, http.StatusSeeOther) http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
return return
} }
log.Println(user.FromRequest(rq).Name, "added", hyphaName, "to", catName) slog.Info(user.FromRequest(rq).Name, "added", hyphaName, "to", catName)
AddHyphaToCategory(hyphaName, catName) categories.AddHyphaToCategory(hyphaName, catName)
http.Redirect(w, rq, redirectTo, http.StatusSeeOther) http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
} }

View File

@@ -2,22 +2,21 @@ package web
import ( import (
"git.sr.ht/~bouncepaw/mycomarkup/v5" "git.sr.ht/~bouncepaw/mycomarkup/v5"
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/internal/shroom"
"github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/web/viewutil"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
"github.com/bouncepaw/mycorrhiza/hypview" "github.com/bouncepaw/mycorrhiza/hypview"
"github.com/bouncepaw/mycorrhiza/mycoopts" "github.com/bouncepaw/mycorrhiza/mycoopts"
"github.com/bouncepaw/mycorrhiza/viewutil"
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/l18n" "github.com/bouncepaw/mycorrhiza/l18n"
"github.com/bouncepaw/mycorrhiza/shroom"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
@@ -25,7 +24,7 @@ func initMutators(r *mux.Router) {
r.PathPrefix("/edit/").HandlerFunc(handlerEdit) r.PathPrefix("/edit/").HandlerFunc(handlerEdit)
r.PathPrefix("/rename/").HandlerFunc(handlerRename).Methods("GET", "POST") r.PathPrefix("/rename/").HandlerFunc(handlerRename).Methods("GET", "POST")
r.PathPrefix("/delete/").HandlerFunc(handlerDelete).Methods("GET", "POST") r.PathPrefix("/delete/").HandlerFunc(handlerDelete).Methods("GET", "POST")
r.PathPrefix("/remove-media/").HandlerFunc(handlerRemoveMedia).Methods("GET", "POST") r.PathPrefix("/remove-media/").HandlerFunc(handlerRemoveMedia).Methods("POST")
r.PathPrefix("/upload-binary/").HandlerFunc(handlerUploadBinary) r.PathPrefix("/upload-binary/").HandlerFunc(handlerUploadBinary)
r.PathPrefix("/upload-text/").HandlerFunc(handlerUploadText) r.PathPrefix("/upload-text/").HandlerFunc(handlerUploadText)
} }
@@ -43,10 +42,6 @@ func handlerRemoveMedia(w http.ResponseWriter, rq *http.Request) {
viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "no rights") viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "no rights")
return return
} }
if rq.Method == "GET" {
hypview.RemoveMedia(viewutil.MetaFrom(w, rq), h.CanonicalName())
return
}
switch h := h.(type) { switch h := h.(type) {
case *hyphae.EmptyHypha, *hyphae.TextualHypha: case *hyphae.EmptyHypha, *hyphae.TextualHypha:
viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "no media to remove") viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "no media to remove")
@@ -83,7 +78,11 @@ func handlerDelete(w http.ResponseWriter, rq *http.Request) {
} }
if rq.Method == "GET" { if rq.Method == "GET" {
hypview.DeleteHypha(meta, h.CanonicalName()) _ = pageHyphaDelete.RenderTo(
viewutil.MetaFrom(w, rq),
map[string]any{
"HyphaName": h.CanonicalName(),
})
return return
} }
@@ -169,7 +168,15 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
return return
} }
} }
hypview.EditHypha(meta, hyphaName, isNew, content, "", "") _ = pageHyphaEdit.RenderTo(
viewutil.MetaFrom(w, rq),
map[string]any{
"HyphaName": hyphaName,
"Content": content,
"IsNew": isNew,
"Message": "",
"Preview": "",
})
} }
// handlerUploadText uploads a new text part for the hypha. // handlerUploadText uploads a new text part for the hypha.
@@ -191,7 +198,16 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
if action == "preview" { if action == "preview" {
ctx, _ := mycocontext.ContextFromStringInput(textData, mycoopts.MarkupOptions(hyphaName)) ctx, _ := mycocontext.ContextFromStringInput(textData, mycoopts.MarkupOptions(hyphaName))
preview := template.HTML(mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx))) preview := template.HTML(mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx)))
hypview.EditHypha(meta, hyphaName, isNew, textData, message, preview)
_ = pageHyphaEdit.RenderTo(
viewutil.MetaFrom(w, rq),
map[string]any{
"HyphaName": hyphaName,
"Content": textData,
"IsNew": isNew,
"Message": message,
"Preview": preview,
})
return return
} }

118
web/newtmpl/newtmpl.go Normal file
View File

@@ -0,0 +1,118 @@
package newtmpl
import (
"embed"
"fmt"
"github.com/bouncepaw/mycorrhiza/internal/cfg"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/web/viewutil"
"html/template"
"strings"
)
//go:embed *.html
var fs embed.FS
var base = template.Must(template.ParseFS(fs, "base.html"))
type Page struct {
TemplateEnglish *template.Template
TemplateRussian *template.Template
}
func NewPage(fs embed.FS, russianTranslation map[string]string, tmpls ...string) *Page {
must := template.Must
en := must(must(must(
base.Clone()).
Funcs(template.FuncMap{
"beautifulName": util.BeautifulName,
"inc": func(i int) int { return i + 1 },
"base": func(hyphaName string) string {
parts := strings.Split(hyphaName, "/")
return parts[len(parts)-1]
},
"beautifulLink": func(hyphaName string) template.HTML {
return template.HTML(
fmt.Sprintf(
`<a href="/hypha/%s">%s</a>`, hyphaName, hyphaName))
},
}).
Parse(fmt.Sprintf(`
{{define "wiki name"}}%s{{end}}
{{define "user hypha"}}%s{{end}}
`, cfg.WikiName, cfg.UserHypha))).
ParseFS(fs, tmpls...))
if cfg.UseAuth {
en = must(en.Parse(`
{{define "auth"}}
<ul class="top-bar__auth auth-links">
<li class="auth-links__box auth-links__user-box">
{{if .Meta.U.Group | eq "anon" }}
<a href="/login" class="auth-links__link auth-links__login-link">
{{block "login" .}}Login{{end}}
</a>
{{else}}
<a href="/hypha/{{block "user hypha" .}}{{end}}/{{.Meta.U.Name}}" class="auth-links__link auth-links__user-link">
{{beautifulName .Meta.U.Name}}
</a>
{{end}}
</li>
{{block "registration" .}}{{end}}
</ul>
{{end}}
`))
}
if cfg.AllowRegistration {
must(en.Parse(`{{define "registration"}}
{{if .Meta.U.Group | eq "anon"}}
<li class="auth-links__box auth-links__register-box">
<a href="/register" class="auth-links__link auth-links__register-link">
{{block "register" .}}Register{{end}}
</a>
</li>
{{end}}
{{end}}`))
}
russianTranslation["search by title"] = "Поиск по названию"
russianTranslation["login"] = "Войти"
russianTranslation["register"] = "Регистрация"
russianTranslation["cancel"] = "Отмена"
russianTranslation["categories"] = "Категории"
russianTranslation["remove from category title"] = "Убрать гифу из этой категории"
russianTranslation["placeholder"] = "Название категории..."
russianTranslation["add to category title"] = "Добавить гифу в эту категорию"
return &Page{
TemplateEnglish: en,
TemplateRussian: must(must(en.Clone()).
Parse(translationsIntoTemplates(russianTranslation))),
}
}
func translationsIntoTemplates(m map[string]string) string {
var sb strings.Builder
for k, v := range m {
sb.WriteString(fmt.Sprintf(`{{define "%s"}}%s{{end}}
`, k, v))
}
return sb.String()
}
func (p *Page) RenderTo(meta viewutil.Meta, data map[string]any) error {
data["Meta"] = meta
data["HeadElements"] = meta.HeadElements
data["BodyAttributes"] = meta.BodyAttributes
data["CommonScripts"] = cfg.CommonScripts
data["EditScripts"] = cfg.EditScripts
data["HeaderLinks"] = viewutil.HeaderLinks
data["UseAuth"] = cfg.UseAuth
tmpl := p.TemplateEnglish
if meta.LocaleIsRussian() {
tmpl = p.TemplateRussian
}
return tmpl.ExecuteTemplate(meta.W, "page", data)
}

202
web/pages.go Normal file
View File

@@ -0,0 +1,202 @@
package web
import (
"embed"
"github.com/bouncepaw/mycorrhiza/web/newtmpl"
"github.com/bouncepaw/mycorrhiza/web/viewutil"
)
//go:embed views/*.html
var fs embed.FS
var pageOrphans, pageBacklinks, pageUserList, pageChangePassword *newtmpl.Page
var pageHyphaDelete, pageHyphaEdit, pageHyphaEmpty, pageHypha *newtmpl.Page
var pageRevision, pageMedia *newtmpl.Page
var pageAuthLock, pageAuthLogin, pageAuthLogout, pageAuthRegister *newtmpl.Page
var pageCatPage, pageCatList, pageCatEdit *newtmpl.Page
var panelChain, listChain, newUserChain, editUserChain, deleteUserChain viewutil.Chain
func initPages() {
panelChain = viewutil.CopyEnRuWith(fs, "views/admin-panel.html", adminTranslationRu)
listChain = viewutil.CopyEnRuWith(fs, "views/admin-user-list.html", adminTranslationRu)
newUserChain = viewutil.CopyEnRuWith(fs, "views/admin-new-user.html", adminTranslationRu)
editUserChain = viewutil.CopyEnRuWith(fs, "views/admin-edit-user.html", adminTranslationRu)
deleteUserChain = viewutil.CopyEnRuWith(fs, "views/admin-delete-user.html", adminTranslationRu)
pageOrphans = newtmpl.NewPage(fs, map[string]string{
"orphaned hyphae": "Гифы-сироты",
"orphan description": "Ниже перечислены гифы без ссылок на них.",
}, "views/orphans.html")
pageBacklinks = newtmpl.NewPage(fs, map[string]string{
"backlinks to text": `Обратные ссылки на {{.}}`,
"backlinks to link": `Обратные ссылки на <a href="/hypha/{{.}}">{{beautifulName .}}</a>`,
"description": `Ниже перечислены гифы, на которых есть ссылка на эту гифу, трансклюзия этой гифы или эта гифа вставлена как изображение.`,
}, "views/backlinks.html")
pageUserList = newtmpl.NewPage(fs, map[string]string{
"title": "Список пользователей",
"administrators": "Администраторы",
"moderators": "Модераторы",
"editors": "Редакторы",
"readers": "Читатели",
}, "views/user-list.html")
pageChangePassword = newtmpl.NewPage(fs, map[string]string{
"change password": "Сменить пароль",
"confirm password": "Повторите пароль",
"current password": "Текущий пароль",
"non local password change": "Пароль можно поменять только местным аккаунтам. Telegram-аккаунтам нельзя.",
"password": "Пароль",
"submit": "Поменять",
}, "views/change-password.html")
pageHyphaDelete = newtmpl.NewPage(fs, map[string]string{
"delete hypha?": "Удалить {{beautifulName .}}?",
"delete [[hypha]]?": "Удалить <a href=\"/hypha/{{.}}\">{{beautifulName .}}</a>?",
"want to delete?": "Вы действительно хотите удалить эту гифу?",
"delete tip": "Нельзя отменить удаление гифы, но её история останется доступной.",
}, "views/hypha-delete.html")
pageHyphaEdit = newtmpl.NewPage(fs, map[string]string{
"editing hypha": `Редактирование {{beautifulName .}}`,
"editing [[hypha]]": `Редактирование <a href="/hypha/{{.}}">{{beautifulName .}}</a>`,
"creating [[hypha]]": `Создание <a href="/hypha/{{.}}">{{beautifulName .}}</a>`,
"you're creating a new hypha": `Вы создаёте новую гифу.`,
"describe your changes": `Опишите ваши правки`,
"save": `Сохранить`,
"preview": `Предпросмотр`,
"previewing hypha": `Предпросмотр {{beautifulName .}}`,
"preview tip": `Заметьте, эта гифа ещё не сохранена. Вот её предпросмотр:`,
"markup": `Разметка`,
"link": `Ссылка`,
"link title": `Текст`,
"heading": `Заголовок`,
"bold": `Жирный`,
"italic": `Курсив`,
"highlight": `Выделение`,
"underline": `Подчеркивание`,
"mono": `Моноширинный`,
"super": `Надстрочный`,
"sub": `Подстрочный`,
"strike": `Зачёркнутый`,
"rocket": `Ссылка-ракета`,
"transclude": `Трансклюзия`,
"hr": `Гориз. черта`,
"code": `Код-блок`,
"bullets": `Маркир. список`,
"numbers": `Нумер. список`,
"mycomarkup help": `<a href="/help/en/mycomarkup" class="shy-link">Подробнее</a> о Микоразметке`,
"actions": `Действия`,
"current date local": `Местная дата`,
"current time local": `Местное время`,
"current date utc": "Дата UTC",
"current time utc": "Время UTC",
"selflink": `Ссылка на вас`,
}, "views/hypha-edit.html")
pageHypha = newtmpl.NewPage(fs, map[string]string{
"edit text": "Редактировать",
"log out": "Выйти",
"admin panel": "Админка",
"subhyphae": "Подгифы",
"history": "История",
"rename": "Переименовать",
"delete": "Удалить",
"view markup": "Посмотреть разметку",
"manage media": "Медиа",
"turn to media": "Превратить в медиа-гифу",
"backlinks": "{{.BacklinkCount}} обратн{{if eq .BacklinkCount 1}}ая ссылка{{else if and (le .BacklinkCount 4) (gt .BacklinkCount 1)}}ые ссылки{{else}}ых ссылок{{end}}",
"empty heading": `Эта гифа не существует`,
"empty no rights": `У вас нет прав для создания новых гиф. Вы можете:`,
"empty log in": `Войти в свою учётную запись, если она у вас есть`,
"empty register": `Создать новую учётную запись`,
"write a text": `Написать текст`,
"write a text tip": `Напишите заметку, дневник, статью, рассказ или иной текст с помощью <a href="/help/en/mycomarkup" class="shy-link">Микоразметки</a>. Сохраняется полная история правок документа.`,
"write a text writing conventions": `Не забывайте следовать правилам оформления этой вики, если они имеются.`,
"write a text btn": `Создать`,
"upload a media": `Загрузить медиа`,
"upload a media tip": `Загрузите изображение, видео или аудио. Распространённые форматы можно просматривать из браузера, остальные можно только скачать и просмотреть локально. Позже вы можете дописать пояснение к этому медиа.`,
"upload a media btn": `Загрузить`,
}, "views/hypha.html")
pageRevision = newtmpl.NewPage(fs, map[string]string{
"revision warning": "Обратите внимание, просмотр медиа в истории пока что недоступен.",
"revision link": "Посмотреть Микоразметку для этой ревизии",
"hypha at rev": "{{.HyphaName}} на {{.RevHash}}",
}, "views/hypha-revision.html")
pageMedia = newtmpl.NewPage(fs, map[string]string{ // TODO: сделать новый перевод
"media title": "Медиа «{{.HyphaName | beautifulLink}}»",
"tip": "На этой странице вы можете управлять медиа.",
"empty": "Эта гифа не имеет медиа, здесь вы можете его загрузить.",
"what is media?": "Что такое медиа?",
"stat": "Свойства",
"stat size": "Размер файла:",
"stat mime": "MIME-тип:",
"upload title": "Прикрепить",
"upload tip": "Вы можете загрузить новое медиа. Пожалуйста, не загружайте слишком большие изображения без необходимости, чтобы впоследствии не ждать её долгую загрузку.",
"upload btn": "Загрузить",
"remove title": "Открепить",
"remove tip": "Заметьте, чтобы заменить медиа, вам не нужно его перед этим откреплять.",
"remove btn": "Открепить",
}, "views/hypha-media.html")
pageAuthLock = newtmpl.NewPage(fs, map[string]string{
"lock title": "Доступ закрыт",
"username": "Логин",
"password": "Пароль",
"log in": "Войти",
}, "views/auth-telegram.html", "views/auth-lock.html")
pageAuthLogin = newtmpl.NewPage(fs, map[string]string{
"username": "Логин",
"password": "Пароль",
"log in": "Войти",
"cookie tip": "Отправляя эту форму, вы разрешаете вики хранить cookie в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.",
"log in to x": "Войти в {{.}}",
"auth disabled": "Аутентификация отключена. Вы можете делать правки анонимно.",
"error username": "Неизвестное имя пользователя.",
"error password": "Неправильный пароль.",
"error telegram": "Не удалось войти через Телеграм.",
"go home": "Домой",
}, "views/auth-telegram.html", "views/auth-login.html")
pageAuthLogout = newtmpl.NewPage(fs, map[string]string{
"log out?": "Выйти?",
"log out": "Выйти",
"cannot log out anon": "Вы не можете выйти, потому что ещё не вошли.",
"log in": "Войти",
"go home": "Домой",
}, "views/auth-logout.html")
pageAuthRegister = newtmpl.NewPage(fs, map[string]string{
"username": "Логин",
"password": "Пароль",
"cookie tip": "Отправляя эту форму, вы разрешаете вики хранить cookie в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.",
"password tip": "Сервер хранит ваш пароль в зашифрованном виде, даже администраторы не смогут его прочесть.",
"register btn": "Зарегистрироваться",
"register on x": "Регистрация на {{.}}",
}, "views/auth-telegram.html", "views/auth-register.html")
pageCatPage = newtmpl.NewPage(fs, map[string]string{
"category x": "Категория {{. | beautifulName}}",
"edit": "Редактировать",
"cat": "Категория",
"empty cat": "Эта категория пуста.",
}, "views/cat-page.html")
pageCatEdit = newtmpl.NewPage(fs, map[string]string{
"edit category x": "Редактирование категории {{beautifulName .}}",
"edit category heading": "Редактирование категории <a href=\"/category/{{.}}\">{{beautifulName .}}</a>",
"empty cat": "Эта категория пуста.",
"add to category title": "Добавить гифу в эту категорию",
"hypha name": "Название гифы",
"add": "Добавить",
"remove hyphae": "Убрать гифы из этой категории",
"remove": "Убрать",
}, "views/cat-edit.html")
pageCatList = newtmpl.NewPage(fs, map[string]string{
"category list": "Список категорий",
"no categories": "В этой вики нет категорий.",
}, "views/cat-list.html")
}

View File

@@ -1,15 +1,13 @@
package settings package web
import ( import (
"fmt" "fmt"
"github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/web/viewutil"
"mime" "mime"
"net/http" "net/http"
"reflect" "reflect"
"github.com/bouncepaw/mycorrhiza/viewutil"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
) )
func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) { func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) {
@@ -49,6 +47,7 @@ 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,5 +57,11 @@ func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) {
} }
w.Header().Set("Content-Type", mime.TypeByExtension(".html")) w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
changePasswordPage(viewutil.MetaFrom(w, rq), f, u) _ = pageChangePassword.RenderTo(
viewutil.MetaFrom(w, rq),
map[string]any{
"Form": f,
"U": u,
},
)
} }

View File

@@ -3,15 +3,24 @@ package web
import ( import (
"fmt" "fmt"
"git.sr.ht/~bouncepaw/mycomarkup/v5" "git.sr.ht/~bouncepaw/mycomarkup/v5"
"github.com/bouncepaw/mycorrhiza/categories" "github.com/bouncepaw/mycorrhiza/hypview"
"github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/internal/backlinks"
views2 "github.com/bouncepaw/mycorrhiza/hypview" "github.com/bouncepaw/mycorrhiza/internal/categories"
"github.com/bouncepaw/mycorrhiza/internal/cfg"
"github.com/bouncepaw/mycorrhiza/internal/files"
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/internal/mimetype"
"github.com/bouncepaw/mycorrhiza/internal/tree"
"github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/mycoopts" "github.com/bouncepaw/mycorrhiza/mycoopts"
"github.com/bouncepaw/mycorrhiza/viewutil" "github.com/bouncepaw/mycorrhiza/web/viewutil"
"html/template"
"io" "io"
"log" "log"
"log/slog"
"net/http" "net/http"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@@ -21,10 +30,7 @@ import (
"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/history" "github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/l18n" "github.com/bouncepaw/mycorrhiza/l18n"
"github.com/bouncepaw/mycorrhiza/mimetype"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
@@ -38,6 +44,10 @@ func initReaders(r *mux.Router) {
r.PathPrefix("/media/").HandlerFunc(handlerMedia) r.PathPrefix("/media/").HandlerFunc(handlerMedia)
r.Path("/today").HandlerFunc(handlerToday) r.Path("/today").HandlerFunc(handlerToday)
r.Path("/edit-today").HandlerFunc(handlerEditToday) r.Path("/edit-today").HandlerFunc(handlerEditToday)
// Backlinks
r.PathPrefix("/backlinks/").HandlerFunc(handlerBacklinks)
r.PathPrefix("/orphans").HandlerFunc(handlerOrphans)
} }
func handlerEditToday(w http.ResponseWriter, rq *http.Request) { func handlerEditToday(w http.ResponseWriter, rq *http.Request) {
@@ -56,15 +66,31 @@ func handlerMedia(w http.ResponseWriter, rq *http.Request) {
hyphaName = util.HyphaNameFromRq(rq, "media") hyphaName = util.HyphaNameFromRq(rq, "media")
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq) u = user.FromRequest(rq)
lc = l18n.FromRequest(rq) isMedia = false
mime string
fileSize int64
) )
util.HTTP200Page(w, switch h := h.(type) {
viewutil.Base( case *hyphae.MediaHypha:
viewutil.MetaFrom(w, rq), isMedia = true
lc.Get("ui.media_title", &l18n.Replacements{"name": util.BeautifulName(hyphaName)}), mime = mimetype.FromExtension(path.Ext(h.MediaFilePath()))
views2.MediaMenu(rq, h, u),
map[string]string{}, fileinfo, err := os.Stat(h.MediaFilePath())
)) if err != nil {
slog.Error("failed to stat media file", "err", err)
// no return
}
fileSize = fileinfo.Size()
}
_ = pageMedia.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
"HyphaName": h.CanonicalName(),
"U": u,
"IsMediaHypha": isMedia,
"MimeType": mime,
"FileSize": fileSize,
})
} }
// handlerRevisionText sends Mycomarkup text of the hypha at the given revision. See also: handlerRevision, handlerText. // handlerRevisionText sends Mycomarkup text of the hypha at the given revision. See also: handlerRevision, handlerText.
@@ -129,7 +155,7 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
var ( var (
hyphaName = util.CanonicalName(slug) hyphaName = util.CanonicalName(slug)
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
contents = fmt.Sprintf(`<p>%s</p>`, lc.Get("ui.revision_no_text")) contents = template.HTML(fmt.Sprintf(`<p>%s</p>`, lc.Get("ui.revision_no_text")))
textContents string textContents string
err error err error
mycoFilePath string mycoFilePath string
@@ -143,26 +169,17 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
textContents, err = history.FileAtRevision(mycoFilePath, revHash) textContents, err = history.FileAtRevision(mycoFilePath, revHash)
if err == nil { if err == nil {
ctx, _ := mycocontext.ContextFromStringInput(textContents, mycoopts.MarkupOptions(hyphaName)) ctx, _ := mycocontext.ContextFromStringInput(textContents, mycoopts.MarkupOptions(hyphaName))
contents = mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx)) contents = template.HTML(mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx)))
} }
page := views2.Revision( meta := viewutil.MetaFrom(w, rq)
viewutil.MetaFrom(w, rq), _ = pageRevision.RenderTo(meta, map[string]any{
h, "ViewScripts": cfg.ViewScripts,
contents, "Contents": contents,
revHash, "RevHash": revHash,
) "NaviTitle": hypview.NaviTitle(meta, h.CanonicalName()),
w.Header().Set("Content-Type", "text/html;charset=utf-8") "HyphaName": h.CanonicalName(),
w.WriteHeader(http.StatusOK) })
_, _ = fmt.Fprint(
w,
viewutil.Base(
viewutil.MetaFrom(w, rq),
lc.Get("ui.revision_title", &l18n.Replacements{"name": util.BeautifulName(hyphaName), "rev": revHash}),
page,
map[string]string{},
),
)
} }
// handlerText serves raw source text of the hypha. // handlerText serves raw source text of the hypha.
@@ -182,8 +199,7 @@ func handlerBinary(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq) util.PrepareRq(rq)
hyphaName := util.HyphaNameFromRq(rq, "binary") hyphaName := util.HyphaNameFromRq(rq, "binary")
switch h := hyphae.ByName(hyphaName).(type) { switch h := hyphae.ByName(hyphaName).(type) {
case *hyphae.EmptyHypha: case *hyphae.EmptyHypha, *hyphae.TextualHypha:
case *hyphae.TextualHypha:
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
log.Printf("Textual hypha %s has no media, cannot serve\n", h.CanonicalName()) log.Printf("Textual hypha %s has no media, cannot serve\n", h.CanonicalName())
case *hyphae.MediaHypha: case *hyphae.MediaHypha:
@@ -197,44 +213,80 @@ func handlerBinary(w http.ResponseWriter, rq *http.Request) {
func handlerHypha(w http.ResponseWriter, rq *http.Request) { func handlerHypha(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq) util.PrepareRq(rq)
var ( var (
hyphaName = util.HyphaNameFromRq(rq, "page", "hypha") hyphaName = util.HyphaNameFromRq(rq, "page", "hypha")
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
contents string contents template.HTML
openGraph string openGraph template.HTML
lc = l18n.FromRequest(rq) lc = l18n.FromRequest(rq)
meta = viewutil.MetaFrom(w, rq)
subhyphae, prevHyphaName, nextHyphaName = tree.Tree(h.CanonicalName())
cats = categories.CategoriesWithHypha(h.CanonicalName())
category_list = ":" + strings.Join(cats, ":") + ":"
isMyProfile = cfg.UseAuth && util.IsProfileName(h.CanonicalName()) && meta.U.Name == strings.TrimPrefix(h.CanonicalName(), cfg.UserHypha+"/")
data = map[string]any{
"HyphaName": h.CanonicalName(),
"SubhyphaeHTML": subhyphae,
"PrevHyphaName": prevHyphaName,
"NextHyphaName": nextHyphaName,
"IsMyProfile": isMyProfile,
"NaviTitle": hypview.NaviTitle(meta, h.CanonicalName()),
"BacklinkCount": backlinks.BacklinksCount(h.CanonicalName()),
"GivenPermissionToModify": user.CanProceed(rq, "edit"),
"Categories": cats,
"IsMediaHypha": false,
}
) )
slog.Info("reading hypha", "name", h.CanonicalName(), "can edit", data["GivenPermissionToModify"])
meta.BodyAttributes = map[string]string{
"cats": category_list,
}
switch h := h.(type) { switch h := h.(type) {
case *hyphae.EmptyHypha: case *hyphae.EmptyHypha:
util.HTTP404Page(w, w.WriteHeader(http.StatusNotFound)
viewutil.Base( data["Contents"] = ""
viewutil.MetaFrom(w, rq), _ = pageHypha.RenderTo(meta, data)
util.BeautifulName(hyphaName),
views2.Hypha(viewutil.MetaFrom(w, rq), h, contents),
map[string]string{},
openGraph))
case hyphae.ExistingHypha: case hyphae.ExistingHypha:
fileContentsT, errT := os.ReadFile(h.TextFilePath()) fileContentsT, err := os.ReadFile(h.TextFilePath())
if errT == nil { if err == nil {
ctx, _ := mycocontext.ContextFromStringInput(string(fileContentsT), mycoopts.MarkupOptions(hyphaName)) ctx, _ := mycocontext.ContextFromStringInput(string(fileContentsT), mycoopts.MarkupOptions(hyphaName))
getOpenGraph, descVisitor, imgVisitor := tools.OpenGraphVisitors(ctx) getOpenGraph, descVisitor, imgVisitor := tools.OpenGraphVisitors(ctx)
openGraph = template.HTML(getOpenGraph())
ast := mycomarkup.BlockTree(ctx, descVisitor, imgVisitor) ast := mycomarkup.BlockTree(ctx, descVisitor, imgVisitor)
contents = mycomarkup.BlocksToHTML(ctx, ast) contents = template.HTML(mycomarkup.BlocksToHTML(ctx, ast))
openGraph = getOpenGraph()
} }
switch h := h.(type) { switch h := h.(type) {
case *hyphae.MediaHypha: case *hyphae.MediaHypha:
contents = mycoopts.Media(h, lc) + contents contents = template.HTML(mycoopts.Media(h, lc)) + contents
data["IsMediaHypha"] = true
} }
category_list := ":" + strings.Join(categories.CategoriesWithHypha(h.CanonicalName()), ":") + ":" data["Contents"] = contents
meta.HeadElements = append(meta.HeadElements, openGraph)
_ = pageHypha.RenderTo(meta, data)
util.HTTP200Page(w, // TODO: check head cats
viewutil.Base( // TODO: check opengraph
viewutil.MetaFrom(w, rq),
util.BeautifulName(hyphaName),
views2.Hypha(viewutil.MetaFrom(w, rq), h, contents),
map[string]string{"cats": category_list},
openGraph))
} }
} }
// handlerBacklinks lists all backlinks to a hypha.
func handlerBacklinks(w http.ResponseWriter, rq *http.Request) {
hyphaName := util.HyphaNameFromRq(rq, "backlinks")
_ = pageBacklinks.RenderTo(viewutil.MetaFrom(w, rq),
map[string]any{
"Addr": "/backlinks/" + hyphaName,
"HyphaName": hyphaName,
"Backlinks": backlinks.BacklinksFor(hyphaName),
})
}
func handlerOrphans(w http.ResponseWriter, rq *http.Request) {
_ = pageOrphans.RenderTo(viewutil.MetaFrom(w, rq),
map[string]any{
"Addr": "/orphans",
"Orphans": backlinks.Orphans(),
})
}

View File

@@ -354,7 +354,7 @@ kbd {
margin: 0; margin: 0;
padding: 8px; padding: 8px;
border: none; border: none;
background: url(/static/icon/x.svg) no-repeat 8px 8px / 16px 16px; background: url(/web/static/icon/x.svg) no-repeat 8px 8px / 16px 16px;
width: 32px; width: 32px;
height: 32px; height: 32px;
cursor: pointer; cursor: pointer;

View File

Before

Width:  |  Height:  |  Size: 531 B

After

Width:  |  Height:  |  Size: 531 B

View File

Before

Width:  |  Height:  |  Size: 477 B

After

Width:  |  Height:  |  Size: 477 B

View File

Before

Width:  |  Height:  |  Size: 955 B

After

Width:  |  Height:  |  Size: 955 B

View File

Before

Width:  |  Height:  |  Size: 636 B

After

Width:  |  Height:  |  Size: 636 B

View File

Before

Width:  |  Height:  |  Size: 270 B

After

Width:  |  Height:  |  Size: 270 B

View File

Before

Width:  |  Height:  |  Size: 888 B

After

Width:  |  Height:  |  Size: 888 B

View File

Before

Width:  |  Height:  |  Size: 139 B

After

Width:  |  Height:  |  Size: 139 B

Some files were not shown because too many files have changed in this diff Show More