~bouncepaw/mycorrhiza-devel

mycorrhiza: Create new hyphae from /edit v4 NEEDS REVISION

~roselandgoose: 2
 Create new hyphae from /edit
 start localizing errors in mutators.go

 10 files changed, 124 insertions(+), 43 deletions(-)
+	"cannot_create": "Cannot create Hypha named",

Do not capitalize.

+	"cannot_create": "TODO: translate(Cannot create Hypha named)",

That would be: Нельзя создать гифу, которая называется

+	"delete_empty": "TODO: translate(Cannot delete an empty hypha)",

That would be: Нельзя удалить пустую гифу

+	"rename_empty": "TODO: translate(Cannot rename an empty hypha)",

That would be: Нельзя переименовать пустую гифу
This patch series has been inactive for a long time. Are you still
on it?
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~bouncepaw/mycorrhiza-devel/patches/37591/mbox | git am -3
Learn more about email & git

[PATCH mycorrhiza v4 1/2] Create new hyphae from /edit Export this patch

From: Rosie K Languet <rkl@rosiesworkshop.net>

This modifies handlerEdit to include a Name input field when one is not
given in the url. It starts implementing the first part of bouncepaw's
first bullet point:
https://github.com/bouncepaw/mycorrhiza/issues/124#issuecomment-1219453093
---
 hypview/hypview.go     |  6 ++++--
 hypview/view_edit.html | 38 ++++++++++++++++++++++++++------------
 static/default.css     | 13 +++++++++++--
 util/util.go           | 18 +++++++++++++++---
 viewutil/err.go        | 20 ++++++++------------
 viewutil/viewutil.go   | 14 ++++++++++++++
 web/mutators.go        | 30 +++++++++++++++++++++++++++---
 7 files changed, 105 insertions(+), 34 deletions(-)

diff --git a/hypview/hypview.go b/hypview/hypview.go
index ea705db..e25621b 100644
--- a/hypview/hypview.go
+++ b/hypview/hypview.go
@@ -16,9 +16,11 @@ var (
	fs            embed.FS
	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 "editing [[hypha]]"}}Редактирование <a class="edit-form__name" href="/hypha/{{.}}">{{beautifulName .}}</a>{{end}}
{{define "create"}}Создать{{end}}
{{define "creating [[hypha]]"}}Создание <a class="edit-form__name" href="/hypha/{{.}}">{{beautifulName .}}</a>{{end}}
{{define "you're creating a new hypha"}}Вы создаёте новую гифу.{{end}}
{{define "new hypha name"}}Новое название{{end}}
{{define "describe your changes"}}Опишите ваши правки{{end}}
{{define "save"}}Сохранить{{end}}
{{define "preview"}}Предпросмотр{{end}}
diff --git a/hypview/view_edit.html b/hypview/view_edit.html
index 1bec2fe..fe5b5cb 100644
--- a/hypview/view_edit.html
+++ b/hypview/view_edit.html
@@ -42,31 +42,45 @@
<script src="/static/toolbar.js"></script>
{{end}}

{{/* this defines <title> */}}
{{define "editing hypha"}}Edit {{beautifulName .}}{{end}}
{{define "previewing hypha"}}Preview of {{beautifulName .}}{{end}}
{{define "title"}}
{{- if .Preview -}}
    {{template "previewing hypha" .HyphaName}}
{{- else -}}
    {{template "editing hypha" .HyphaName}}
{{- end -}}
	{{- if .Preview -}}
		{{template "previewing hypha" .HyphaName}}
	{{- else -}}
		{{template "editing hypha" .HyphaName}}
	{{- end -}}
{{end}}

{{define "body"}}
<main class="main-width edit {{if .Preview}}edit_with-preview{{else}}edit_no-preview{{end}}">
    <form method="post" class="edit-form" action="/upload-text/{{.HyphaName}}">
        <h1 class="edit__title">
            {{if .IsNew}}
                {{block "creating [[hypha]]" .HyphaName}}
                    Create <a href="/hypha/{{.}}">{{beautifulName .}}</a>
                {{if ne .HyphaName ""}}
                    {{block "creating [[hypha]]" .HyphaName}}
                        {{block "create" .}}Create{{end}} <a class="edit-form__name" href="/hypha/{{.}}">{{beautifulName .}}</a>
                    {{end}}
                {{else}}
                    {{template "create"}}
                    <input
                        id="name"
                        type="text"
                        name="name"
                        required
                        class="edit-form__message-zone edit-form__name-field edit-form__name"
                        placeholder="{{block "new hypha name" .}}New hypha name{{end}}"
                        aria-label="{{template "new hypha name" .}}">
                {{end}}
            {{else}}
                {{block "editing [[hypha]]" .HyphaName}}
                    Edit <a href="/hypha/{{.}}">{{beautifulName .}}</a>
                    Edit <a class="edit-form__name" href="/hypha/{{.}}">{{beautifulName .}}</a>
                {{end}}
            {{end}}
        </h1>
        <textarea name="text" class="edit-form__textarea" autofocus>{{.Content}}</textarea>
        <p class="edit-form__message-zone">
        <div class="edit-form__message-zone">
            <input
                id="text"
                type="text"
@@ -75,8 +89,8 @@
                value="{{.Message}}"
                placeholder="{{block "describe your changes" .}}Describe your changes{{end}}"
                aria-label="{{template "describe your changes" .}}">
        </p>
        <p class="edit-form__buttons">
        </div>
        <div class="edit-form__buttons">
            <button type="submit" name="action" class="btn btn_accent edit-form__save" value="save">
                {{template "save" .}}
            </button>
@@ -86,7 +100,7 @@
            <a href="/hypha/{{.HyphaName}}" class="btn btn_weak">
                {{template "cancel" .}}
            </a>
        </p>
        </div>
    </form>
    {{if .Preview}}
        <p class="warning">
diff --git a/static/default.css b/static/default.css
index de717cb..0986c93 100644
--- a/static/default.css
+++ b/static/default.css
@@ -93,10 +93,19 @@ textarea {font-size:16px; font-family: inherit; line-height: 150%; }

.edit_no-preview { height: 90vh; }
.edit_with-preview .edit-form { height: 90vh; }
.edit__title { margin-top: 0; }
.edit__title {
    margin-top: 0;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: start;
}
.edit-form .edit-form__name { margin: 0.25em; }
.edit-form .edit-form__name-field { margin: 0.25em; margin-left: .5em; }
.edit__preview { border: 2px dashed #ddd; padding: 10px; margin: 0 -10px; }
.edit-form__textarea { width: 100%; height: 80vh; min-height: 4rem; }
.edit-form p { margin: .25rem 0; }
.edit-form div { margin: .25rem 0; }
.edit-form__message-zone { font-size:16px; font-weight: normal; }
.edit-form__message { width: 100%; margin: 0.25em 0; }
.edit-form__save { font-weight: bold; }
.edit-toolbar__buttons, .edit-toolbar__help { margin: .5rem; }
diff --git a/util/util.go b/util/util.go
index a00230c..04fac06 100644
--- a/util/util.go
+++ b/util/util.go
@@ -71,16 +71,28 @@ func IsProfileName(hyphaName string) bool {
	return strings.HasPrefix(hyphaName, cfg.UserHypha+"/") && strings.Count(hyphaName, "/") == 1
}

// HyphaNameFromRq extracts hypha name from http request. You have to also pass the action which is embedded in the url or several actions. For url /hypha/hypha, the action would be "hypha".
 // HyphaNameFromRq extracts hypha name from http request. You have to also pass the action which is embedded in the url or several actions. For url /hypha/hypha, the action would be "hypha". When the url contains no hypha name, the configured HomeHypha is returned.
func HyphaNameFromRq(rq *http.Request, actions ...string) string {
	name := OptionalHyphaNameFromRq(rq, actions...)
	if name == "" {
		log.Println("HyphaNameFromRq: this request is invalid, fall back to home hypha")
		return cfg.HomeHypha
	}
	return name
}

// OptionalHyphaNameFromRq extracts hypha name from http request. You have to also pass the action which is embedded in the url or several actions. For url /hypha/hypha, the action would be "hypha".
func OptionalHyphaNameFromRq(rq *http.Request, actions ...string) string {
	p := rq.URL.Path
	for _, action := range actions {
		if strings.HasPrefix(p, "/"+action+"/") {
			return CanonicalName(strings.TrimPrefix(p, "/"+action+"/"))
		}
		if p == "/"+action {
			break
		}
	}
	log.Println("HyphaNameFromRq: this request is invalid, fall back to home hypha")
	return cfg.HomeHypha
	return CanonicalName("")
}

// FormData is a convenient struct for passing user input and errors to HTML
diff --git a/viewutil/err.go b/viewutil/err.go
index 4620709..f0b37bc 100644
--- a/viewutil/err.go
+++ b/viewutil/err.go
@@ -11,18 +11,14 @@ import (
func HttpErr(meta Meta, status int, name, errMsg string) {
	meta.W.(http.ResponseWriter).Header().Set("Content-Type", mime.TypeByExtension(".html"))
	meta.W.(http.ResponseWriter).WriteHeader(status)
	fmt.Fprint(
		meta.W,
		Base(
			meta,
			"Error",
			fmt.Sprintf(
				`<main class="main-width"><p>%s. <a href="/hypha/%s">%s<a></p></main>`,
				errMsg,
				name,
				meta.Lc.Get("ui.error_go_back"),
			),
			map[string]string{},
	WriteBase(
    	meta,
		"Error",
		fmt.Sprintf(
			`<main class="main-width"><p>%s. <a href="/hypha/%s">%s<a></p></main>`,
			errMsg,
			name,
			meta.Lc.Get("ui.error_go_back"),
		),
	)
}
diff --git a/viewutil/viewutil.go b/viewutil/viewutil.go
index 25de03b..fbe4da7 100644
--- a/viewutil/viewutil.go
+++ b/viewutil/viewutil.go
@@ -130,6 +130,20 @@ func Base(meta Meta, title, body string, bodyAttributes map[string]string, headE
	return w.String()
}

// TODO: get rid of this
func WriteBase(meta Meta, title, innerHTML string) error {
	_, err := fmt.Fprint(
    	meta.W,
    	Base(
			meta,
			title,
			innerHTML,
			nil,
    	),
	)
	return err
}

func CopyEnRuWith(fsys fs.FS, filename, ruTranslation string) Chain {
	return en(copyEnWith(fsys, filename)).
		ru(template.Must(copyRuWith(fsys, filename).Parse(ruTranslation)))
diff --git a/web/mutators.go b/web/mutators.go
index e85ddda..ecabcac 100644
--- a/web/mutators.go
+++ b/web/mutators.go
@@ -1,6 +1,7 @@
package web

import (
    "fmt"
	"git.sr.ht/~bouncepaw/mycomarkup/v5"
	"html/template"
	"log"
@@ -22,7 +23,7 @@ import (
)

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("/delete/").HandlerFunc(handlerDelete).Methods("GET", "POST")
	r.PathPrefix("/remove-media/").HandlerFunc(handlerRemoveMedia).Methods("GET", "POST")
@@ -145,7 +146,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
		lc   = l18n.FromRequest(rq)
		meta = viewutil.MetaFrom(w, rq)

		hyphaName = util.HyphaNameFromRq(rq, "edit")
		hyphaName = util.OptionalHyphaNameFromRq(rq, "edit")
		h         = hyphae.ByName(hyphaName)

		isNew   bool
@@ -179,10 +180,33 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
		u    = user.FromRequest(rq)
		meta = viewutil.MetaFrom(w, rq)

		hyphaName = util.HyphaNameFromRq(rq, "upload-text")
		hyphaName = util.OptionalHyphaNameFromRq(rq, "upload-text")
	)

	nameInURL := (hyphaName != "")
	if !nameInURL {
		hyphaName = util.CanonicalName(rq.PostFormValue("name"))
	}

	var (
		h         = hyphae.ByName(hyphaName)
		_, isNew  = h.(*hyphae.EmptyHypha)
	)

	if !nameInURL && !isNew {
    	w.Header().Set("Content-Type", "text/html;charset=utf-8")
    	w.WriteHeader(http.StatusConflict)
    	viewutil.WriteBase(meta, "Error",
    		fmt.Sprintf(
    			`<main class="main-width"><p>Cannot create Hypha named %[1]s; <a href="%[1]s">Name already taken.</a></p></main>`,
    			hyphaName,
			),
			// ToDo localize
		)
		return
	}

	var (
		textData = rq.PostFormValue("text")
		action   = rq.PostFormValue("action")
		message  = rq.PostFormValue("message")
-- 
2.34.5
Hello! Sorry for the late review.

I was testing if you have resolved the issues from the previous
version of the patch. Quoting my previous message here:
Indeed, you now show an error, but the hypha still becomes emptied.

[PATCH mycorrhiza v4 2/2] start localizing errors in mutators.go Export this patch

From: Rosie K Languet <rkl@rosiesworkshop.net>

This commit moves some non-localized error strings into l18n/en/ui.json
with somewhat sensible keys. There are entries in the corresponding ru/
file which need to be translated.

grep -rn "TODO: translate(.*)" l18n/
---
 l18n/en/ui.json |  5 +++++
 l18n/ru/ui.json |  5 +++++
 web/mutators.go | 18 +++++++++---------
 3 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/l18n/en/ui.json b/l18n/en/ui.json
index 8350b05..66e5bfc 100644
--- a/l18n/en/ui.json
+++ b/l18n/en/ui.json
@@ -25,6 +25,10 @@
	"error_text_fetch": "Could not fetch text data",
	"error_try_again": "Try again",
	"error_go_back": "Go back to the hypha.",

	"cannot_create": "Cannot create Hypha named",
	"delete_empty": "Cannot delete an empty hypha",
	"rename_empty": "Cannot rename an empty hypha",
	
	"ask_rename": "Rename %s",
	"rename_to": "New name",
@@ -38,6 +42,7 @@
	"rename_badname_tip": "Invalid new name. Names cannot contain characters {{.chars}}.",

	"act_no_media": "No media",
	"act_no_media_remove": "no media to remove",
	"act_no_media_tip": "Cannot remove media because this is not a media hypha",
	"act_norights": "Not enough rights",
	"act_notexist": "Does not exist",
diff --git a/l18n/ru/ui.json b/l18n/ru/ui.json
index 05b3c3a..6c363b6 100644
--- a/l18n/ru/ui.json
+++ b/l18n/ru/ui.json
@@ -26,6 +26,10 @@
	"random_no_hyphae": "В этой вики нет гиф",
	"random_no_hyphae_tip": "Невозможно отобразить случайную гифу, потому что вики не содержит ни одной гифы",

	"cannot_create": "TODO: translate(Cannot create Hypha named)",
	"delete_empty": "TODO: translate(Cannot delete an empty hypha)",
	"rename_empty": "TODO: translate(Cannot rename an empty hypha)",

	"error": "Ошибка",
	"error_text_fetch": "Не удалось получить текстовые данные",
	"error_try_again": "Попробуйте ещё раз",
@@ -43,6 +47,7 @@
	"rename_badname_tip": "Новое название некорректно. Названия не могут содержать символы {{.chars}}.",

	"act_no_media": "Нет медиа",
	"act_no_media_remove": "TODO: translate(no media to remove)",
	"act_no_media_tip": "Невозможно убрать медиа, потому что медиа нету",
	"act_norights": "Недостаточно прав",
	"act_notexist": "Не существует",
diff --git a/web/mutators.go b/web/mutators.go
index ecabcac..bee8137 100644
--- a/web/mutators.go
+++ b/web/mutators.go
@@ -41,7 +41,7 @@ func handlerRemoveMedia(w http.ResponseWriter, rq *http.Request) {
		meta = viewutil.MetaFrom(w, rq)
	)
	if !u.CanProceed("remove-media") {
		viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "no rights")
		viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), meta.Lc.Get("ui.act_norights"))
		return
	}
	if rq.Method == "GET" {
@@ -50,7 +50,7 @@ func handlerRemoveMedia(w http.ResponseWriter, rq *http.Request) {
	}
	switch h := h.(type) {
	case *hyphae.EmptyHypha, *hyphae.TextualHypha:
		viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "no media to remove")
		viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), meta.Lc.Get("ui.no_media_remove"))
		return
	case *hyphae.MediaHypha:
		if err := shroom.RemoveMedia(u, h); err != nil {
@@ -71,15 +71,14 @@ func handlerDelete(w http.ResponseWriter, rq *http.Request) {

	if !u.CanProceed("delete") {
		log.Printf("%s has no rights to delete ‘%s’\n", u.Name, h.CanonicalName())
		viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "No rights")
		viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), meta.Lc.Get("ui.act_norights"))
		return
	}

	switch h.(type) {
	case *hyphae.EmptyHypha:
		log.Printf("%s tries to delete empty hypha ‘%s’\n", u.Name, h.CanonicalName())
		// TODO: localize
		viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "Cannot delete an empty hypha")
		viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), meta.Lc.Get("ui.delete_empty"))
		return
	}

@@ -108,13 +107,13 @@ func handlerRename(w http.ResponseWriter, rq *http.Request) {
	switch h.(type) {
	case *hyphae.EmptyHypha:
		log.Printf("%s tries to rename empty hypha ‘%s’", u.Name, h.CanonicalName())
		viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "Cannot rename an empty hypha") // TODO: localize
		viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), lc.Get("ui.rename_empty"))
		return
	}

	if !u.CanProceed("rename") {
		log.Printf("%s has no rights to rename ‘%s’\n", u.Name, h.CanonicalName())
		viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "No rights")
		viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), lc.Get("ui.act_norights"))
		return
	}

@@ -198,10 +197,11 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
    	w.WriteHeader(http.StatusConflict)
    	viewutil.WriteBase(meta, "Error",
    		fmt.Sprintf(
    			`<main class="main-width"><p>Cannot create Hypha named %[1]s; <a href="%[1]s">Name already taken.</a></p></main>`,
    			`<main class="main-width"><p>%[1]s %[2]s; <a href="%[2]s">%[3]s.</a></p></main>`,
    			meta.Lc.Get("ui.cannot_create"),
    			hyphaName,
    			meta.Lc.Get("ui.rename_taken"),
			),
			// ToDo localize
		)
		return
	}
-- 
2.34.5
+	"cannot_create": "Cannot create Hypha named",

Do not capitalize.

+	"cannot_create": "TODO: translate(Cannot create Hypha named)",

That would be: Нельзя создать гифу, которая называется

+	"delete_empty": "TODO: translate(Cannot delete an empty hypha)",

That would be: Нельзя удалить пустую гифу

+	"rename_empty": "TODO: translate(Cannot rename an empty hypha)",

That would be: Нельзя переименовать пустую гифу