diff --git a/README.md b/README.md index e142d68..8f20c6e 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,13 @@ A wiki engine. This is the development branch for version 0.10. Features planned for this version: * [x] New file structure * [ ] Mycomarkup + * [x] 6 headings + * [x] Paragraph styling + * [ ] Inline links + * [ ] Better lists + * [ ] Better quotes * [x] CLI options -* [ ] CSS improvements +* [x] CSS improvements ## Building ```sh diff --git a/history/operations.go b/history/operations.go index dca0bd4..422d245 100644 --- a/history/operations.go +++ b/history/operations.go @@ -77,7 +77,7 @@ func (hop *HistoryOp) WithFilesRenamed(pairs map[string]string) *HistoryOp { for from, to := range pairs { if from != "" { os.MkdirAll(filepath.Dir(to), 0777) - hop.gitop(append([]string{"mv"}, from, to)...) + Rename(from, to) } } return hop diff --git a/http_mutators.go b/http_mutators.go index 5f5d6b4..beb43e6 100644 --- a/http_mutators.go +++ b/http_mutators.go @@ -118,7 +118,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) { } else { warning = `

You are creating a new hypha.

` } - util.HTTP200Page(w, base("Edit"+hyphaName, templates.EditHTML(hyphaName, textAreaFill, warning))) + util.HTTP200Page(w, base("Edit "+hyphaName, templates.EditHTML(hyphaName, textAreaFill, warning))) } // handlerUploadText uploads a new text part for the hypha. diff --git a/http_readers.go b/http_readers.go index d6cb9da..f05271c 100644 --- a/http_readers.go +++ b/http_readers.go @@ -9,8 +9,8 @@ import ( "path/filepath" "strings" - "github.com/bouncepaw/mycorrhiza/gemtext" "github.com/bouncepaw/mycorrhiza/history" + "github.com/bouncepaw/mycorrhiza/markup" "github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/tree" "github.com/bouncepaw/mycorrhiza/util" @@ -37,7 +37,7 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) { textContents, err = history.FileAtRevision(textPath, revHash) ) if err == nil { - contents = gemtext.ToHtml(hyphaName, textContents) + contents = markup.ToHtml(hyphaName, textContents) } page := templates.RevisionHTML( hyphaName, @@ -104,7 +104,7 @@ func handlerPage(w http.ResponseWriter, rq *http.Request) { fileContentsT, errT := ioutil.ReadFile(data.textPath) _, errB := os.Stat(data.binaryPath) if errT == nil { - contents = gemtext.ToHtml(hyphaName, string(fileContentsT)) + contents = markup.ToHtml(hyphaName, string(fileContentsT)) } if !os.IsNotExist(errB) { contents = binaryHtmlBlock(hyphaName, data) + contents diff --git a/hypha.go b/hypha.go index b95f5d1..d0f6cbc 100644 --- a/hypha.go +++ b/hypha.go @@ -10,17 +10,17 @@ import ( "path/filepath" "strings" - "github.com/bouncepaw/mycorrhiza/gemtext" "github.com/bouncepaw/mycorrhiza/history" + "github.com/bouncepaw/mycorrhiza/markup" "github.com/bouncepaw/mycorrhiza/util" ) func init() { - gemtext.HyphaExists = func(hyphaName string) bool { + markup.HyphaExists = func(hyphaName string) bool { _, hyphaExists := HyphaStorage[hyphaName] return hyphaExists } - gemtext.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) { + markup.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) { if hyphaData, ok := HyphaStorage[hyphaName]; ok { rawText, err = FetchTextPart(hyphaData) if hyphaData.binaryPath != "" { diff --git a/gemtext/lexer.go b/markup/lexer.go similarity index 82% rename from gemtext/lexer.go rename to markup/lexer.go index 5fd1c77..27ba479 100644 --- a/gemtext/lexer.go +++ b/markup/lexer.go @@ -1,4 +1,4 @@ -package gemtext +package markup import ( "fmt" @@ -13,7 +13,7 @@ var HyphaExists func(string) bool // HyphaAccess holds function that accesses a hypha by its name. var HyphaAccess func(string) (rawText, binaryHtml string, err error) -// GemLexerState is used by gemtext parser to remember what is going on. +// GemLexerState is used by markup parser to remember what is going on. type GemLexerState struct { // Name of hypha being parsed name string @@ -29,7 +29,7 @@ type Line struct { contents interface{} } -// Parse gemtext line starting with "=>" according to wikilink rules. +// Parse markup line starting with "=>" according to wikilink rules. // See http://localhost:1737/page/wikilink func wikilink(src string, state *GemLexerState) (href, text, class string) { src = strings.TrimSpace(remover("=>")(src)) @@ -79,7 +79,7 @@ func lex(name, content string) (ast []Line) { return ast } -// Lex `line` in gemtext and save it to `ast` using `state`. +// Lex `line` in markup and save it to `ast` using `state`. func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) { addLine := func(text interface{}) { *ast = append(*ast, Line{id: state.id, contents: text}) @@ -142,20 +142,29 @@ normalState: case startsWith("```"): state.where = "pre" state.buf = fmt.Sprintf("
", state.id, strings.TrimPrefix(line, "```"))
-	case startsWith("*"):
+	case startsWith("* "):
 		state.where = "list"
 		state.buf = fmt.Sprintf("`},
 		{10, `
=> preformatted text
-where gemtext is not lexed
`}, +where markup is not lexed
`}, {11, `

linking

`}, {12, "

text

"}, {13, `
()
diff --git a/markup/mycomarkup.go b/markup/mycomarkup.go
new file mode 100644
index 0000000..fa47e2b
--- /dev/null
+++ b/markup/mycomarkup.go
@@ -0,0 +1,102 @@
+// This is not done yet
+package markup
+
+import (
+	"html"
+	"strings"
+)
+
+// A Mycomarkup-formatted document
+type MycoDoc struct {
+	// data
+	hyphaName string
+	contents  string
+
+	// state
+	recursionDepth int
+
+	// results
+}
+
+// Constructor
+func Doc(hyphaName, contents string) *MycoDoc {
+	return &MycoDoc{
+		hyphaName: hyphaName,
+		contents:  contents,
+	}
+}
+
+// AsHtml returns an html representation of the document
+func (md *MycoDoc) AsHtml() string {
+	return ""
+}
+
+type BlockType int
+
+const (
+	BlockH1 = iota
+	BlockH2
+	BlockH3
+	BlockH4
+	BlockH5
+	BlockH6
+	BlockRocket
+	BlockPre
+	BlockQuote
+	BlockPara
+)
+
+type CrawlWhere int
+
+const (
+	inSomewhere = iota
+	inPre
+	inEnd
+)
+
+func crawl(name, content string) []string {
+	stateStack := []CrawlWhere{inSomewhere}
+
+	startsWith := func(token string) bool {
+		return strings.HasPrefix(content, token)
+	}
+
+	pop := func() {
+		stateStack = stateStack[:len(stateStack)-1]
+	}
+
+	push := func(s CrawlWhere) {
+		stateStack = append(stateStack, s)
+	}
+
+	readln := func(c string) (string, string) {
+		parts := strings.SplitN(c, "\n", 1)
+		return parts[0], parts[1]
+	}
+
+	preAcc := ""
+	line := ""
+
+	for {
+		switch stateStack[0] {
+		case inSomewhere:
+			switch {
+			case startsWith("```"):
+				push(inPre)
+				_, content = readln(content)
+			default:
+			}
+		case inPre:
+			switch {
+			case startsWith("```"):
+				pop()
+				_, content = readln(content)
+			default:
+				line, content = readln(content)
+				preAcc += html.EscapeString(line)
+			}
+		}
+	}
+
+	return []string{}
+}
diff --git a/markup/paragraph.go b/markup/paragraph.go
new file mode 100644
index 0000000..af1aa2c
--- /dev/null
+++ b/markup/paragraph.go
@@ -0,0 +1,108 @@
+package markup
+
+import (
+	"bytes"
+	"fmt"
+	"html"
+	"strings"
+)
+
+type spanTokenType int
+
+const (
+	spanTextNode = iota
+	spanItalic
+	spanBold
+	spanMono
+	spanSuper
+	spanSub
+	spanMark
+)
+
+func tagFromState(stt spanTokenType, tagState map[spanTokenType]bool, tagName, originalForm string) string {
+	if tagState[spanMono] && (stt != spanMono) {
+		return originalForm
+	}
+	if tagState[stt] {
+		tagState[stt] = false
+		return fmt.Sprintf("", tagName)
+	} else {
+		tagState[stt] = true
+		return fmt.Sprintf("<%s>", tagName)
+	}
+}
+
+// getTextNode splits the `p` into two parts `textNode` and `rest` by the first encountered rune that resembles a span tag. If there is none, `textNode = p`, `rest = ""`. It handles escaping with backslash.
+func getTextNode(input *bytes.Buffer) string {
+	var (
+		textNodeBuffer = bytes.Buffer{}
+		escaping       = false
+	)
+	// Always read the first byte in advance to avoid endless loops that kill computers (sad experience)
+	if input.Len() != 0 {
+		b, _ := input.ReadByte()
+		textNodeBuffer.WriteByte(b)
+	}
+	for input.Len() != 0 {
+		// Assume no error is possible because we check for length
+		b, _ := input.ReadByte()
+		if escaping {
+			textNodeBuffer.WriteByte(b)
+			escaping = false
+		} else if b == '\\' {
+			escaping = true
+		} else if strings.IndexByte("/*`^,!", b) >= 0 {
+			input.UnreadByte()
+			break
+		} else {
+			textNodeBuffer.WriteByte(b)
+		}
+	}
+	return textNodeBuffer.String()
+}
+
+func ParagraphToHtml(input string) string {
+	var (
+		p   = bytes.NewBufferString(input)
+		ret strings.Builder
+		// true = tag is opened, false = tag is not opened
+		tagState = map[spanTokenType]bool{
+			spanItalic: false,
+			spanBold:   false,
+			spanMono:   false,
+			spanSuper:  false,
+			spanSub:    false,
+			spanMark:   false,
+		}
+		startsWith = func(t string) bool {
+			return bytes.HasPrefix(p.Bytes(), []byte(t))
+		}
+	)
+
+	for p.Len() != 0 {
+		switch {
+		case startsWith("//"):
+			ret.WriteString(tagFromState(spanItalic, tagState, "em", "//"))
+			p.Next(2)
+		case startsWith("**"):
+			ret.WriteString(tagFromState(spanBold, tagState, "strong", "**"))
+			p.Next(2)
+		case startsWith("`"):
+			ret.WriteString(tagFromState(spanMono, tagState, "code", "`"))
+			p.Next(1)
+		case startsWith("^"):
+			ret.WriteString(tagFromState(spanSuper, tagState, "sup", "^"))
+			p.Next(1)
+		case startsWith(",,"):
+			ret.WriteString(tagFromState(spanSub, tagState, "sub", ",,"))
+			p.Next(2)
+		case startsWith("!!"):
+			ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
+			p.Next(2)
+		default:
+			ret.WriteString(html.EscapeString(getTextNode(p)))
+		}
+	}
+
+	return ret.String()
+}
diff --git a/markup/paragraph_test.go b/markup/paragraph_test.go
new file mode 100644
index 0000000..7ef9c07
--- /dev/null
+++ b/markup/paragraph_test.go
@@ -0,0 +1,44 @@
+package markup
+
+import (
+	"fmt"
+	"testing"
+)
+
+/*
+func TestGetTextNode(t *testing.T) {
+	tests := [][]string{
+		// input   textNode  rest
+		{"barab", "barab", ""},
+		{"test, ", "test", ", "},
+		{"/test/", "", "/test/"},
+		{"\\/test/", "/test", "/"},
+		{"test \\/ar", "test /ar", ""},
+		{"test //italian// test", "test ", "//italian// test"},
+	}
+	for _, triplet := range tests {
+		a, b := getTextNode([]byte(triplet[0]))
+		if a != triplet[1] || string(b) != triplet[2] {
+			t.Error(fmt.Sprintf("Wanted: %q\nGot: %q %q", triplet, a, b))
+		}
+	}
+}
+*/
+
+func TestParagraphToHtml(t *testing.T) {
+	tests := [][]string{
+		{"a simple paragraph", "a simple paragraph"},
+		{"//italic//", "italic"},
+		{"Embedded //italic//", "Embedded italic"},
+		{"double //italian// //text//", "double italian text"},
+		{"it has `mono`", "it has mono"},
+		{"this is a left **bold", "this is a left bold"},
+		{"this line has a ,comma, two of them", "this line has a ,comma, two of them"},
+		{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."},
+	}
+	for _, test := range tests {
+		if ParagraphToHtml(test[0]) != test[1] {
+			t.Error(fmt.Sprintf("%q: Wanted %q, got %q", test[0], test[1], ParagraphToHtml(test[0])))
+		}
+	}
+}
diff --git a/gemtext/parser.go b/markup/parser.go
similarity index 97%
rename from gemtext/parser.go
rename to markup/parser.go
index dae7682..0a77a67 100644
--- a/gemtext/parser.go
+++ b/markup/parser.go
@@ -1,4 +1,4 @@
-package gemtext
+package markup
 
 import ()
 
diff --git a/gemtext/testdata/test.gmi b/markup/testdata/test.myco
similarity index 87%
rename from gemtext/testdata/test.gmi
rename to markup/testdata/test.myco
index c1dc9c7..6646148 100644
--- a/gemtext/testdata/test.gmi
+++ b/markup/testdata/test.myco
@@ -12,7 +12,7 @@ more text
 * li\n"+
 ```alt text goes here
 => preformatted text
-where gemtext is not lexed
+where markup is not lexed
 ```it ends here"
 =>linking
 
diff --git a/gemtext/utils.go b/markup/utils.go
similarity index 97%
rename from gemtext/utils.go
rename to markup/utils.go
index 65b8c41..420697a 100644
--- a/gemtext/utils.go
+++ b/markup/utils.go
@@ -1,4 +1,4 @@
-package gemtext
+package markup
 
 import (
 	"strings"
diff --git a/gemtext/xclusion.go b/markup/xclusion.go
similarity index 96%
rename from gemtext/xclusion.go
rename to markup/xclusion.go
index 32514d7..b4a4370 100644
--- a/gemtext/xclusion.go
+++ b/markup/xclusion.go
@@ -1,4 +1,4 @@
-package gemtext
+package markup
 
 import (
 	"fmt"
@@ -9,7 +9,7 @@ import (
 
 const xclError = -9
 
-// Transclusion is used by gemtext parser to remember what hyphae shall be transcluded.
+// Transclusion is used by markup parser to remember what hyphae shall be transcluded.
 type Transclusion struct {
 	name string
 	from int // inclusive
diff --git a/gemtext/xclusion_test.go b/markup/xclusion_test.go
similarity index 88%
rename from gemtext/xclusion_test.go
rename to markup/xclusion_test.go
index a1531a9..d61a2ad 100644
--- a/gemtext/xclusion_test.go
+++ b/markup/xclusion_test.go
@@ -1,4 +1,4 @@
-package gemtext
+package markup
 
 import (
 	"testing"
@@ -6,7 +6,7 @@ import (
 
 func TestParseTransclusion(t *testing.T) {
 	check := func(line string, expectedXclusion Transclusion) {
-		if xcl := parseTransclusion(line); xcl != expectedXclusion {
+		if xcl := parseTransclusion(line, "t"); xcl != expectedXclusion {
 			t.Error(line, "; got:", xcl, "wanted:", expectedXclusion)
 		}
 	}
diff --git a/metarrhiza b/metarrhiza
index 46cc59e..58d000e 160000
--- a/metarrhiza
+++ b/metarrhiza
@@ -1 +1 @@
-Subproject commit 46cc59eeca16e873b47fdda84009adabb4e82d2b
+Subproject commit 58d000edaa4f06586accc6199190ca69bebf2ad4
diff --git a/templates/css.qtpl b/templates/css.qtpl
index 981c47c..16555dc 100644
--- a/templates/css.qtpl
+++ b/templates/css.qtpl
@@ -9,17 +9,21 @@
 html {height:100%; padding:0; background-color:#ddd;
 background-image: url("data:image/svg+xml,%3Csvg width='42' height='44' viewBox='0 0 42 44' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='Page-1' fill='none' fill-rule='evenodd'%3E%3Cg id='brick-wall' fill='%23bbbbbb' fill-opacity='0.4'%3E%3Cpath d='M0 0h42v44H0V0zm1 1h40v20H1V1zM0 23h20v20H0V23zm22 0h20v20H22V23z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");} /* heropatterns.com */
 body {height:100%; margin:0; font-size:16px; font-family:sans-serif;}
-main {padding:1rem; background-color: white; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2);}
+main {padding:1rem; background-color: white; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); }
 main > form {margin-bottom:1rem;}
 textarea {font-size:15px;}
-.upload-text-form {height:90%;}
-.upload-text-form textarea {width:100%;height:90%;}
+.edit {height:100%;}
+.edit-form {height:90%;}
+.edit-form textarea {width:100%;height:90%;}
 
+main h1:not(.navi-title) {font-size:1.7rem;}
+blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;}
 .wikilink_new {color:#a55858;}
 .wikilink_new:visited {color:#a55858;}
 .wikilink_external::after {content:"🌐"; margin-left: .5rem; font-size: small; text-decoration: none; align: bottom;}
+p code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; }
 .codeblock {background-color:#eee; padding:.5rem; font-size:16px; white-space: pre-wrap;}
-.transclusion .codeblock {background-color:#ddd;}
+.transclusion code, .transclusion .codeblock {background-color:#ddd;}
 .transclusion {background-color:#eee; padding:0 0 0 1.5rem;}
 .transclusion__link {display: block; position: absolute; transform-origin: 0 0; transform: rotate(90deg); margin-top: 0.5rem; color: black; text-decoration: none;}
 
diff --git a/templates/css.qtpl.go b/templates/css.qtpl.go
index 90e4c85..09902a5 100644
--- a/templates/css.qtpl.go
+++ b/templates/css.qtpl.go
@@ -31,17 +31,21 @@ func StreamDefaultCSS(qw422016 *qt422016.Writer) {
 html {height:100%; padding:0; background-color:#ddd;
 background-image: url("data:image/svg+xml,%3Csvg width='42' height='44' viewBox='0 0 42 44' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='Page-1' fill='none' fill-rule='evenodd'%3E%3Cg id='brick-wall' fill='%23bbbbbb' fill-opacity='0.4'%3E%3Cpath d='M0 0h42v44H0V0zm1 1h40v20H1V1zM0 23h20v20H0V23zm22 0h20v20H22V23z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");} /* heropatterns.com */
 body {height:100%; margin:0; font-size:16px; font-family:sans-serif;}
-main {padding:1rem; background-color: white; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2);}
+main {padding:1rem; background-color: white; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); }
 main > form {margin-bottom:1rem;}
 textarea {font-size:15px;}
-.upload-text-form {height:90%;}
-.upload-text-form textarea {width:100%;height:90%;}
+.edit {height:100%;}
+.edit-form {height:90%;}
+.edit-form textarea {width:100%;height:90%;}
 
+main h1:not(.navi-title) {font-size:1.7rem;}
+blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;}
 .wikilink_new {color:#a55858;}
 .wikilink_new:visited {color:#a55858;}
 .wikilink_external::after {content:"🌐"; margin-left: .5rem; font-size: small; text-decoration: none; align: bottom;}
+p code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; }
 .codeblock {background-color:#eee; padding:.5rem; font-size:16px; white-space: pre-wrap;}
-.transclusion .codeblock {background-color:#ddd;}
+.transclusion code, .transclusion .codeblock {background-color:#ddd;}
 .transclusion {background-color:#eee; padding:0 0 0 1.5rem;}
 .transclusion__link {display: block; position: absolute; transform-origin: 0 0; transform: rotate(90deg); margin-top: 0.5rem; color: black; text-decoration: none;}
 
@@ -55,31 +59,31 @@ nav ul li {list-style-type:none;margin-right:1rem;}
 
 #new-name {width:100%;}
 `)
-//line templates/css.qtpl:35
+//line templates/css.qtpl:39
 }
 
-//line templates/css.qtpl:35
+//line templates/css.qtpl:39
 func WriteDefaultCSS(qq422016 qtio422016.Writer) {
-//line templates/css.qtpl:35
+//line templates/css.qtpl:39
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line templates/css.qtpl:35
+//line templates/css.qtpl:39
 	StreamDefaultCSS(qw422016)
-//line templates/css.qtpl:35
+//line templates/css.qtpl:39
 	qt422016.ReleaseWriter(qw422016)
-//line templates/css.qtpl:35
+//line templates/css.qtpl:39
 }
 
-//line templates/css.qtpl:35
+//line templates/css.qtpl:39
 func DefaultCSS() string {
-//line templates/css.qtpl:35
+//line templates/css.qtpl:39
 	qb422016 := qt422016.AcquireByteBuffer()
-//line templates/css.qtpl:35
+//line templates/css.qtpl:39
 	WriteDefaultCSS(qb422016)
-//line templates/css.qtpl:35
+//line templates/css.qtpl:39
 	qs422016 := string(qb422016.B)
-//line templates/css.qtpl:35
+//line templates/css.qtpl:39
 	qt422016.ReleaseByteBuffer(qb422016)
-//line templates/css.qtpl:35
+//line templates/css.qtpl:39
 	return qs422016
-//line templates/css.qtpl:35
+//line templates/css.qtpl:39
 }
diff --git a/templates/http_mutators.qtpl b/templates/http_mutators.qtpl
index e488f94..b7f9a0f 100644
--- a/templates/http_mutators.qtpl
+++ b/templates/http_mutators.qtpl
@@ -1,8 +1,8 @@
 {% func EditHTML(hyphaName, textAreaFill, warning string) %}
-		
+

Edit {%s hyphaName %}

{%s= warning %} -

diff --git a/templates/http_mutators.qtpl.go b/templates/http_mutators.qtpl.go index ec48774..c57d482 100644 --- a/templates/http_mutators.qtpl.go +++ b/templates/http_mutators.qtpl.go @@ -21,7 +21,7 @@ var ( func StreamEditHTML(qw422016 *qt422016.Writer, hyphaName, textAreaFill, warning string) { //line templates/http_mutators.qtpl:1 qw422016.N().S(` -
+

Edit `) //line templates/http_mutators.qtpl:3 qw422016.E().S(hyphaName) @@ -32,7 +32,7 @@ func StreamEditHTML(qw422016 *qt422016.Writer, hyphaName, textAreaFill, warning qw422016.N().S(warning) //line templates/http_mutators.qtpl:4 qw422016.N().S(` -