diff --git a/cfg/config.go b/cfg/config.go
index 4597353..7c0db8f 100644
--- a/cfg/config.go
+++ b/cfg/config.go
@@ -26,12 +26,13 @@ var (
TitleTemplate = `%s`
GenericErrorMsg = `Sorry, something went wrong `
SiteTitle = `MycorrhizaWiki`
+ Theme = `default-light`
)
func InitConfig(wd string) bool {
log.Println("WikiDir is", wd)
WikiDir = wd
- TemplatesDir = filepath.Join(filepath.Dir(WikiDir), "templates")
+ TemplatesDir = "Templates"
configJsonPath = filepath.Join(filepath.Dir(WikiDir), "config.json")
if _, err := os.Stat(configJsonPath); os.IsNotExist(err) {
@@ -53,6 +54,7 @@ func readConfig() bool {
cfg := struct {
Address string `json:"address"`
+ Theme string `json:"theme"`
SiteTitle string `json:"site-title"`
TitleTemplates struct {
EditHypha string `json:"edit-hypha"`
@@ -67,6 +69,7 @@ func readConfig() bool {
}
Address = cfg.Address
+ Theme = cfg.Theme
SiteTitle = cfg.SiteTitle
TitleEditTemplate = cfg.TitleTemplates.EditHypha
TitleTemplate = cfg.TitleTemplates.ViewHypha
diff --git a/fs/fs.go b/fs/fs.go
index e140b58..b10dc74 100644
--- a/fs/fs.go
+++ b/fs/fs.go
@@ -14,15 +14,16 @@ type Storage struct {
root string
}
+var Hs *Storage
+
// InitStorage initiates filesystem-based hypha storage. It has to be called after configuration was inited.
-func InitStorage() *Storage {
- s := &Storage{
+func InitStorage() {
+ Hs = &Storage{
paths: make(map[string]string),
root: cfg.WikiDir,
}
- s.indexHyphae(s.root)
- log.Printf("Indexed %v hyphae\n", len(s.paths))
- return s
+ Hs.indexHyphae(Hs.root)
+ log.Printf("Indexed %v hyphae\n", len(Hs.paths))
}
// hyphaName gets name of a hypha by stripping path to the hypha in `fullPath`
diff --git a/fs/genealogy.go b/fs/genealogy.go
new file mode 100644
index 0000000..3addd3d
--- /dev/null
+++ b/fs/genealogy.go
@@ -0,0 +1,91 @@
+package fs
+
+import (
+ "fmt"
+ "log"
+ "path/filepath"
+ "sort"
+ "strings"
+)
+
+func (s *Storage) RenderHypha(h *Hypha) {
+}
+
+// If Name == "", the tree is empty.
+type Tree struct {
+ Name string
+ Ancestors []string
+ Siblings []string
+ Descendants []*Tree
+ Root bool
+}
+
+// GetTree generates a Tree for the given hypha name.
+// It can also generate trees for non-existent hyphae, that's why we use `name string` instead of making it a method on `Hypha`.
+// In `root` is `false`, siblings will not be fetched.
+func (s *Storage) GetTree(name string, root bool) *Tree {
+ t := &Tree{Name: name, Root: root}
+ for hyphaName, _ := range s.paths {
+ s.compareNamesAndAppend(t, hyphaName)
+ }
+ sort.Slice(t.Ancestors, func(i, j int) bool {
+ return strings.Count(t.Ancestors[i], "/") < strings.Count(t.Ancestors[j], "/")
+ })
+ sort.Strings(t.Siblings)
+ sort.Slice(t.Descendants, func(i, j int) bool {
+ a := t.Descendants[i].Name
+ b := t.Descendants[j].Name
+ return len(a) < len(b)
+ })
+ log.Printf("Generate tree for %v: %v %v\n", t.Name, t.Ancestors, t.Siblings)
+ return t
+}
+
+// Compares names appends name2 to an array of `t`:
+func (s *Storage) compareNamesAndAppend(t *Tree, name2 string) {
+ switch {
+ case t.Name == name2:
+ case strings.HasPrefix(t.Name, name2):
+ t.Ancestors = append(t.Ancestors, name2)
+ case t.Root && (strings.Count(t.Name, "/") == strings.Count(name2, "/") &&
+ (filepath.Dir(t.Name) == filepath.Dir(name2))):
+ t.Siblings = append(t.Siblings, name2)
+ case strings.HasPrefix(name2, t.Name):
+ t.Descendants = append(t.Descendants, s.GetTree(name2, false))
+ }
+}
+
+// asHtml returns HTML representation of a tree.
+// It recursively itself on the tree's children.
+// TODO: redo with templates. I'm not in mood for it now.
+func (t *Tree) AsHtml() (html string) {
+ if t.Name == "" {
+ return ""
+ }
+ html += `
`
+ if t.Root {
+ for _, ancestor := range t.Ancestors {
+ html += navitreeEntry(ancestor, "navitree__ancestor")
+ }
+ for _, siblingName := range t.Siblings {
+ html += navitreeEntry(siblingName, "navitree__sibling")
+ }
+ }
+ html += navitreeEntry(t.Name, "navitree__name")
+
+ for _, subtree := range t.Descendants {
+ html += subtree.AsHtml()
+ }
+
+ html += ` `
+ return html
+}
+
+// navitreeEntry is a small utility function that makes generating html easier.
+// Someone please redo it in templates.
+func navitreeEntry(name, class string) string {
+ return fmt.Sprintf(`
+ %s
+
+`, class, name, filepath.Base(name))
+}
diff --git a/fs/hypha.go b/fs/hypha.go
index 225acd0..737a070 100644
--- a/fs/hypha.go
+++ b/fs/hypha.go
@@ -23,6 +23,10 @@ type Hypha struct {
actual *Revision `json:"-"`
}
+func (h *Hypha) TextPath() string {
+ return h.actual.TextPath
+}
+
func (s *Storage) Open(name string) (*Hypha, error) {
h := &Hypha{
Exists: true,
@@ -98,7 +102,11 @@ func (h *Hypha) NewestId() string {
}
func (h *Hypha) PlainLog(s string) {
- log.Println(h.FullName, h.actual.Id, s)
+ if h.Exists {
+ log.Println(h.FullName, h.actual.Id, s)
+ } else {
+ log.Println("nonexistent", h.FullName, s)
+ }
}
func (h *Hypha) mimeTypeForActionRaw() string {
@@ -122,8 +130,7 @@ func (h *Hypha) asHtml() (string, error) {
`
// What about using ?
if h.hasBinaryData() {
- ret += fmt.Sprintf(` `, rev.FullName, rev.Id)
+ ret += fmt.Sprintf(` `, rev.FullName, rev.Id)
}
contents, err := ioutil.ReadFile(rev.TextPath)
@@ -162,9 +169,9 @@ func (h *Hypha) ActionRaw(w http.ResponseWriter) {
h.PlainLog("Serving raw text")
}
-// ActionGetBinary is used with `?action=getBinary`.
+// ActionBinary is used with `?action=binary`.
// It writes contents of binary content file.
-func (h *Hypha) ActionGetBinary(w http.ResponseWriter) {
+func (h *Hypha) ActionBinary(w http.ResponseWriter) {
fileContents, err := ioutil.ReadFile(h.actual.BinaryPath)
if err != nil {
log.Fatal(err)
@@ -190,3 +197,26 @@ func (h *Hypha) ActionZen(w http.ResponseWriter) {
w.Write([]byte(html))
h.PlainLog("Rendering zen")
}
+
+// ActionView is used with `?action=view` or no action at all.
+// It renders the page, the layout and everything else.
+func (h *Hypha) ActionView(w http.ResponseWriter, renderExists, renderNotExists func(string, string) string) {
+ var html string
+ var err error
+ if h.Exists {
+ html, err = h.asHtml()
+ if err != nil {
+ log.Println(err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ }
+ w.Header().Set("Content-Type", "text/html;charset=utf-8")
+ w.WriteHeader(http.StatusOK)
+ if h.Exists {
+ w.Write([]byte(renderExists(h.FullName, html)))
+ } else {
+ w.Write([]byte(renderNotExists(h.FullName, "")))
+ }
+ h.PlainLog("Rendering hypha view")
+}
diff --git a/handlers.go b/handlers.go
index 1b8a9a2..d8a1ada 100644
--- a/handlers.go
+++ b/handlers.go
@@ -11,6 +11,7 @@ import (
// "time"
"github.com/bouncepaw/mycorrhiza/fs"
+ "github.com/bouncepaw/mycorrhiza/render"
"github.com/gorilla/mux"
)
@@ -19,7 +20,7 @@ import (
// Boilerplate code present in many handlers. Good to have it.
func HandlerBase(w http.ResponseWriter, rq *http.Request) (*fs.Hypha, bool) {
vars := mux.Vars(rq)
- h, err := hs.Open(vars["hypha"])
+ h, err := fs.Hs.Open(vars["hypha"])
if err != nil {
log.Println(err)
return nil, false
@@ -40,10 +41,10 @@ func HandlerRaw(w http.ResponseWriter, rq *http.Request) {
}
}
-func HandlerGetBinary(w http.ResponseWriter, rq *http.Request) {
- log.Println("?action=getBinary")
+func HandlerBinary(w http.ResponseWriter, rq *http.Request) {
+ log.Println("?action=binary")
if h, ok := HandlerBase(w, rq); ok {
- h.ActionGetBinary(w)
+ h.ActionBinary(w)
}
}
@@ -53,13 +54,13 @@ func HandlerZen(w http.ResponseWriter, rq *http.Request) {
}
}
-/*
func HandlerView(w http.ResponseWriter, rq *http.Request) {
if h, ok := HandlerBase(w, rq); ok {
- h.ActionView(w, HyphaPage)
+ h.ActionView(w, render.HyphaPage, render.Hypha404)
}
}
+/*
func HandlerEdit(w http.ResponseWriter, rq *http.Request) {
vars := mux.Vars(rq)
ActionEdit(vars["hypha"], w)
diff --git a/main.go b/main.go
index 8d3cdb1..4cbcb8d 100644
--- a/main.go
+++ b/main.go
@@ -22,8 +22,6 @@ func RevInMap(m map[string]string) string {
return "0"
}
-var hs *fs.Storage
-
func main() {
if len(os.Args) == 1 {
panic("Expected a root wiki pages directory")
@@ -36,15 +34,19 @@ func main() {
log.Println("Welcome to MycorrhizaWiki α")
cfg.InitConfig(wikiDir)
log.Println("Indexing hyphae...")
- hs = fs.InitStorage()
+ fs.InitStorage()
// Start server code. See handlers.go for handlers' implementations.
r := mux.NewRouter()
- r.Queries("action", "getBinary", "rev", cfg.RevQuery).Path(cfg.HyphaUrl).
- HandlerFunc(HandlerGetBinary)
- r.Queries("action", "getBinary").Path(cfg.HyphaUrl).
- HandlerFunc(HandlerGetBinary)
+ r.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) {
+ http.ServeFile(w, rq, filepath.Join(filepath.Dir(cfg.WikiDir), "favicon.ico"))
+ })
+
+ r.Queries("action", "binary", "rev", cfg.RevQuery).Path(cfg.HyphaUrl).
+ HandlerFunc(HandlerBinary)
+ r.Queries("action", "binary").Path(cfg.HyphaUrl).
+ HandlerFunc(HandlerBinary)
r.Queries("action", "raw", "rev", cfg.RevQuery).Path(cfg.HyphaUrl).
HandlerFunc(HandlerRaw)
@@ -56,12 +58,12 @@ func main() {
r.Queries("action", "zen").Path(cfg.HyphaUrl).
HandlerFunc(HandlerZen)
- /*
- r.Queries("action", "view", "rev", revQuery).Path(hyphaUrl).
- HandlerFunc(HandlerView)
- r.Queries("action", "view").Path(hyphaUrl).
- HandlerFunc(HandlerView)
+ r.Queries("action", "view", "rev", cfg.RevQuery).Path(cfg.HyphaUrl).
+ HandlerFunc(HandlerView)
+ r.Queries("action", "view").Path(cfg.HyphaUrl).
+ HandlerFunc(HandlerView)
+ /*
r.Queries("action", "edit").Path(hyphaUrl).
HandlerFunc(HandlerEdit)
@@ -69,7 +71,7 @@ func main() {
HandlerFunc(HandlerUpdate)
*/
- // r.HandleFunc(hyphaUrl, HandlerView)
+ r.HandleFunc(cfg.HyphaUrl, HandlerView)
// Debug page that renders all hyphae.
// TODO: make it redirect to home page.
diff --git a/render/render.go b/render/render.go
new file mode 100644
index 0000000..75c3e86
--- /dev/null
+++ b/render/render.go
@@ -0,0 +1,101 @@
+package render
+
+import (
+ "bytes"
+ "fmt"
+ "path"
+ "text/template"
+
+ "github.com/bouncepaw/mycorrhiza/cfg"
+ "github.com/bouncepaw/mycorrhiza/fs"
+)
+
+// EditHyphaPage returns HTML page of hypha editor.
+func EditHyphaPage(name, textMime, content, tags string) string {
+ page := map[string]string{
+ "Text": content,
+ "TextMime": textMime,
+ "Name": name,
+ "Tags": tags,
+ }
+ keys := map[string]string{
+ "Title": fmt.Sprintf(cfg.TitleEditTemplate, name),
+ "Header": renderFromString(name, "Hypha/edit/header.html"),
+ "Sidebar": renderFromMap(page, "Hypha/edit/sidebar.html"),
+ }
+ return renderBase(renderFromMap(page, "Hypha/edit/index.html"), keys)
+}
+
+// Hypha404 returns 404 page for nonexistent page.
+func Hypha404(name, _ string) string {
+ return HyphaGeneric(name, name, "Hypha/view/404.html")
+}
+
+// HyphaPage returns HTML page of hypha viewer.
+func HyphaPage(name, content string) string {
+ return HyphaGeneric(name, content, "Hypha/view/index.html")
+}
+
+// HyphaGeneric is used when building renderers for all types of hypha pages
+func HyphaGeneric(name string, content, templatePath string) string {
+ var sidebar string
+ if aside := renderFromMap(map[string]string{
+ "Tree": fs.Hs.GetTree(name, true).AsHtml(),
+ }, "Hypha/view/sidebar.html"); aside != "" {
+ sidebar = aside
+ }
+ keys := map[string]string{
+ "Title": fmt.Sprintf(cfg.TitleTemplate, name),
+ "Sidebar": sidebar,
+ }
+ return renderBase(renderFromString(content, templatePath), keys)
+}
+
+// renderBase collects and renders page from base template
+// Args:
+// content: string or pre-rendered template
+// keys: map with replaced standart fields
+func renderBase(content string, keys map[string]string) string {
+ page := map[string]string{
+ "Title": cfg.SiteTitle,
+ "Main": "",
+ "SiteTitle": cfg.SiteTitle,
+ }
+ for key, val := range keys {
+ page[key] = val
+ }
+ page["Main"] = content
+ return renderFromMap(page, "base.html")
+}
+
+// renderFromMap applies `data` map to template in `templatePath` and returns the result.
+func renderFromMap(data map[string]string, templatePath string) string {
+ hyphPath := path.Join(cfg.TemplatesDir, cfg.Theme, templatePath)
+ h, _ := fs.Hs.Open(hyphPath)
+ h.OnRevision("0")
+ tmpl, err := template.ParseFiles(h.TextPath())
+ if err != nil {
+ return err.Error()
+ }
+ buf := new(bytes.Buffer)
+ if err := tmpl.Execute(buf, data); err != nil {
+ return err.Error()
+ }
+ return buf.String()
+}
+
+// renderFromMap applies `data` string to template in `templatePath` and returns the result.
+func renderFromString(data string, templatePath string) string {
+ hyphPath := path.Join(cfg.TemplatesDir, cfg.Theme, templatePath)
+ h, _ := fs.Hs.Open(hyphPath)
+ h.OnRevision("0")
+ tmpl, err := template.ParseFiles(h.TextPath())
+ if err != nil {
+ return err.Error()
+ }
+ buf := new(bytes.Buffer)
+ if err := tmpl.Execute(buf, data); err != nil {
+ return err.Error()
+ }
+ return buf.String()
+}
diff --git a/w/config.json b/w/config.json
index 09fcba9..409bcd2 100644
--- a/w/config.json
+++ b/w/config.json
@@ -1,5 +1,6 @@
{
"address": "127.0.0.1:1737",
+ "theme": "default-light",
"site-title": "🍄 MycorrhizaWiki",
"title-templates": {
"edit-hypha": "Edit %s at MycorrhizaWiki",
diff --git a/w/favicon.ico b/w/favicon.ico
new file mode 100644
index 0000000..857e55c
Binary files /dev/null and b/w/favicon.ico differ
diff --git a/w/templates/Hypha/edit/header.html b/w/m/Templates/default-dark/Hypha/edit/header.html/1.html
similarity index 98%
rename from w/templates/Hypha/edit/header.html
rename to w/m/Templates/default-dark/Hypha/edit/header.html/1.html
index 31dfc0f..37a4b91 100644
--- a/w/templates/Hypha/edit/header.html
+++ b/w/m/Templates/default-dark/Hypha/edit/header.html/1.html
@@ -1 +1 @@
-
+
diff --git a/w/m/Templates/default-dark/Hypha/edit/header.html/meta.json b/w/m/Templates/default-dark/Hypha/edit/header.html/meta.json
new file mode 100644
index 0000000..cf72dcd
--- /dev/null
+++ b/w/m/Templates/default-dark/Hypha/edit/header.html/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "header.html",
+ "comment": "Create Templates/default-dark/Hypha/edit/header.html",
+ "author": "",
+ "time": 1592996801,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
diff --git a/w/m/Templates/default-dark/Hypha/edit/index.html/1.html b/w/m/Templates/default-dark/Hypha/edit/index.html/1.html
new file mode 100644
index 0000000..842b43b
--- /dev/null
+++ b/w/m/Templates/default-dark/Hypha/edit/index.html/1.html
@@ -0,0 +1,16 @@
+
diff --git a/w/m/Templates/default-dark/Hypha/edit/index.html/meta.json b/w/m/Templates/default-dark/Hypha/edit/index.html/meta.json
new file mode 100644
index 0000000..7534c5d
--- /dev/null
+++ b/w/m/Templates/default-dark/Hypha/edit/index.html/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "index.html",
+ "comment": "Create Templates/default-dark/Hypha/edit/index.html",
+ "author": "",
+ "time": 1592996876,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
diff --git a/w/m/Templates/default-dark/Hypha/edit/sidebar.html/1.html b/w/m/Templates/default-dark/Hypha/edit/sidebar.html/1.html
new file mode 100644
index 0000000..3f257cf
--- /dev/null
+++ b/w/m/Templates/default-dark/Hypha/edit/sidebar.html/1.html
@@ -0,0 +1,19 @@
+Text MIME-type
+
Good types are text/markdown
and text/plain
+
+
+
Revision comment
+
Please make your comment helpful
+
+
+
Edit tags
+
Tags are separated by commas, whitespace is ignored
+
+
+
Upload file
+
If this hypha has a file like that, the text above is meant to be a description of it
+
+
+
+
+
diff --git a/w/m/Templates/default-dark/Hypha/edit/sidebar.html/meta.json b/w/m/Templates/default-dark/Hypha/edit/sidebar.html/meta.json
new file mode 100644
index 0000000..1fe3342
--- /dev/null
+++ b/w/m/Templates/default-dark/Hypha/edit/sidebar.html/meta.json
@@ -0,0 +1,19 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": [
+ ""
+ ],
+ "name": "sidebar.html",
+ "comment": "Create Templates/default-dark/Hypha/edit/sidebar.html",
+ "author": "",
+ "time": 1593003792,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
diff --git a/w/templates/Hypha/view/404.html b/w/m/Templates/default-dark/Hypha/view/404.html/1.html
similarity index 97%
rename from w/templates/Hypha/view/404.html
rename to w/m/Templates/default-dark/Hypha/view/404.html/1.html
index e7a14c5..7f558fb 100644
--- a/w/templates/Hypha/view/404.html
+++ b/w/m/Templates/default-dark/Hypha/view/404.html/1.html
@@ -1,4 +1,4 @@
-{{ . }}
-
-The hypha you are trying to access does not exist yet. Why not create it?
-
+{{ . }}
+
+The hypha you are trying to access does not exist yet. Why not create it?
+
diff --git a/w/m/Templates/default-dark/Hypha/view/404.html/meta.json b/w/m/Templates/default-dark/Hypha/view/404.html/meta.json
new file mode 100644
index 0000000..1c22d20
--- /dev/null
+++ b/w/m/Templates/default-dark/Hypha/view/404.html/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "404.html",
+ "comment": "Create Templates/default-light/Hypha/view/404.html",
+ "author": "",
+ "time": 1592996917,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/w/templates/Hypha/view/index.html b/w/m/Templates/default-dark/Hypha/view/index.html/1.html
similarity index 88%
rename from w/templates/Hypha/view/index.html
rename to w/m/Templates/default-dark/Hypha/view/index.html/1.html
index 55a25b3..f75b53a 100644
--- a/w/templates/Hypha/view/index.html
+++ b/w/m/Templates/default-dark/Hypha/view/index.html/1.html
@@ -1 +1 @@
-{{ . }}
+{{ . }}
diff --git a/w/m/Templates/default-dark/Hypha/view/index.html/meta.json b/w/m/Templates/default-dark/Hypha/view/index.html/meta.json
new file mode 100644
index 0000000..4b75feb
--- /dev/null
+++ b/w/m/Templates/default-dark/Hypha/view/index.html/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "index.html",
+ "comment": "Create Templates/default-light/Hypha/view/index.html",
+ "author": "",
+ "time": 1592996954,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/w/templates/Hypha/view/sidebar.html b/w/m/Templates/default-dark/Hypha/view/sidebar.html/1.html
similarity index 96%
rename from w/templates/Hypha/view/sidebar.html
rename to w/m/Templates/default-dark/Hypha/view/sidebar.html/1.html
index 7e361d4..3f751f3 100644
--- a/w/templates/Hypha/view/sidebar.html
+++ b/w/m/Templates/default-dark/Hypha/view/sidebar.html/1.html
@@ -1,9 +1,9 @@
-
-{{ .Tree }}
+
+{{ .Tree }}
diff --git a/w/m/Templates/default-dark/Hypha/view/sidebar.html/meta.json b/w/m/Templates/default-dark/Hypha/view/sidebar.html/meta.json
new file mode 100644
index 0000000..0c89342
--- /dev/null
+++ b/w/m/Templates/default-dark/Hypha/view/sidebar.html/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "sidebar.html",
+ "comment": "Create Templates/default-light/Hypha/view/sidebar.html",
+ "author": "",
+ "time": 1592996977,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/w/templates/base.html b/w/m/Templates/default-dark/base.html/1.html
similarity index 84%
rename from w/templates/base.html
rename to w/m/Templates/default-dark/base.html/1.html
index 3ce1fdc..b5dbe7a 100644
--- a/w/templates/base.html
+++ b/w/m/Templates/default-dark/base.html/1.html
@@ -1,30 +1,30 @@
-
-
- {{ .Title }}
-
-
-
-
- 🍄 Open
-
- {{ .Main }}
-
-
-
-
+
+
+ {{ .Title }}
+
+
+
+
+ 🍄 Open
+
+ {{ .Main }}
+
+
+
+
diff --git a/w/m/Templates/default-dark/base.html/meta.json b/w/m/Templates/default-dark/base.html/meta.json
new file mode 100644
index 0000000..228d738
--- /dev/null
+++ b/w/m/Templates/default-dark/base.html/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "base.html",
+ "comment": "Create Templates/default-dark/base.html",
+ "author": "",
+ "time": 1592996503,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
diff --git a/w/m/Templates/default-dark/main.css/1.css b/w/m/Templates/default-dark/main.css/1.css
new file mode 100644
index 0000000..fcc208e
--- /dev/null
+++ b/w/m/Templates/default-dark/main.css/1.css
@@ -0,0 +1,221 @@
+*, *::before, *::after {
+ box-sizing: border-box;
+}
+
+html {
+ height: 100%;
+}
+
+body {
+ font: 15px/1.5 system-ui, -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Helvetica', 'PT Sans', 'Roboto', 'Arial', sans-serif;
+ max-width: 500px;
+ min-height: 100%;
+ margin: 0 auto;
+ padding: 12px 24px;
+ background-color: #272b30;
+ color: #c8c8c8;
+}
+
+.msg {
+ background-color: #f4f4f4;
+ padding: 1rem;
+ border-radius: 1rem;
+}
+
+.shroom {
+ margin: 0;
+}
+
+.shroom__button {
+ border-radius: 1rem;
+ padding: 8px 16px 8px 0;
+ border: none;
+ background: #f0f2f4;
+ color: #444;
+ font: inherit;
+ font-size: 15px;
+ font-weight: 500;
+ text-align: left;
+}
+
+.shroom span {
+ margin-left: 16px;
+ margin-right: 8px;
+ font-size: 20px;
+ vertical-align: -0.04em;
+}
+
+.mushroom .shroom__button {
+ background: #44484a;
+ color: #dddfe4;
+}
+
+
+.header {
+ padding: 8px 0;
+}
+
+.header h1 {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ color: #20ce92;
+}
+
+
+a {
+ color: #019fe3;
+}
+
+/*a:visited {
+ color: #44a;
+}*/
+
+h1, h2, h3, h4, h5, h6 {
+ margin: 0.5em 0 0.25em;
+}
+
+.page {
+ font-size: 16px;
+ line-height: 1.666;
+ max-width: 40em;
+ hyphens: auto;
+}
+
+.page pre {
+ white-space: break-spaces;
+}
+
+.page__amnt {
+ max-width: 100%;
+}
+
+.page__title {
+ font-family: 'PT Serif', 'Georgia', serif;
+ font-size: 36px;
+ font-weight: normal;
+ color: #20ce92;
+}
+
+.edit-box {
+ display: grid;
+ grid-template-columns: 7fr 5fr;
+}
+.edit-box .naviwrapper__buttons {
+ grid-column: 1;
+ grid-row: 2;
+}
+.edit-box__left { grid-column: 1; grid-row: 2 }
+.edit-box__right { grid-column: 2; grid-row: 1 / span 2; padding-right: 16px }
+.edit-box__text {
+ border-radius: 1rem;
+ color: #c8c8c8;
+ padding: 16px;
+ background-color: rgba(255,255,255,.05);
+}
+
+footer {
+ padding: 1em 0;
+ font-size: 12px;
+ color: #7a8288;
+}
+
+footer a, footer a:visited {
+ color: #7a8288;
+}
+
+.left-panel {
+ display: none;
+}
+
+.left-panel.active {
+ display: block;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: #fafafa;
+}
+
+.left-panel.active .sidebar {
+ background: #fff;
+}
+
+.left-panel__in {
+ width: 100%;
+ height: 100%;
+ max-width: 500px;
+ margin: 0 auto;
+ padding: 12px 24px;
+
+}
+
+.left-panel__contents {
+ width: 100%;
+ display: grid;
+ grid-template-rows: auto 1fr auto;
+}
+
+.left-panel .shroom {
+ margin-bottom: 16px;
+}
+
+@media (min-width: 700px) {
+ body {
+ max-width: 1200px;
+ padding: 8px 16px;
+ padding-right: 274px;
+ }
+
+ .shroom {
+ display: none;
+ }
+
+ .page {
+ font-size: 18px;
+ }
+
+ .left-panel {
+ display: block;
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ min-width: 274px;
+ right: 0;
+ }
+
+ .left-panel__contents {
+ height: 100%;
+ }
+}
+
+.sidebar {
+ padding: 16px;
+ border-radius: 1rem;
+ background-color: rgba(255,255,255,.05);
+}
+
+.hypha-actions ul {
+ margin: 0;
+ padding: 0;
+}
+
+.hypha-actions li {
+ list-style: none;
+}
+
+.hypha-actions a {
+ display: block;
+ padding: 6px 16px;
+ font: inherit;
+ text-decoration: none;
+ color: #7a8288;
+ transition: 0.1s background;
+}
+
+aside .hypha-actions a:hover {
+ background-color: #272b30;
+ color: #73ca73;
+}
diff --git a/w/m/Templates/default-dark/main.css/meta.json b/w/m/Templates/default-dark/main.css/meta.json
new file mode 100644
index 0000000..c34401f
--- /dev/null
+++ b/w/m/Templates/default-dark/main.css/meta.json
@@ -0,0 +1,19 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": [
+ ""
+ ],
+ "name": "main.css",
+ "comment": "Update sys/main.css",
+ "author": "",
+ "time": 1592666188,
+ "text_mime": "text/css",
+ "binary_mime": "",
+ "text_name": "1.css",
+ "binary_name": ""
+ }
+ }
+}
diff --git a/w/m/Templates/default-dark/main.js/1.mjs b/w/m/Templates/default-dark/main.js/1.mjs
new file mode 100644
index 0000000..7a221e6
--- /dev/null
+++ b/w/m/Templates/default-dark/main.js/1.mjs
@@ -0,0 +1,7 @@
+var menu = document.getElementById('shroomburgerMenu');
+document.getElementById('shroomBtn').addEventListener('click', function() {
+ menu.classList.add('active');
+});
+document.getElementById('mushroomBtn').addEventListener('click', function() {
+ menu.classList.remove('active');
+});
diff --git a/w/m/Templates/default-dark/main.js/meta.json b/w/m/Templates/default-dark/main.js/meta.json
new file mode 100644
index 0000000..98d4ad5
--- /dev/null
+++ b/w/m/Templates/default-dark/main.js/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "main.js",
+ "comment": "Update sys/main.js",
+ "author": "",
+ "time": 1592937088,
+ "text_mime": "text/javascript",
+ "binary_mime": "",
+ "text_name": "1.mjs",
+ "binary_name": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/w/templates/updateOk.html b/w/m/Templates/default-dark/updateOk.html/1.html
similarity index 94%
rename from w/templates/updateOk.html
rename to w/m/Templates/default-dark/updateOk.html/1.html
index f64067d..f4ddcde 100644
--- a/w/templates/updateOk.html
+++ b/w/m/Templates/default-dark/updateOk.html/1.html
@@ -1,8 +1,8 @@
-
-
- Saved {{ .Name }}
-
-
- Saved successfully. Go back
-
-
+
+
+ Saved {{ .Name }}
+
+
+ Saved successfully. Go back
+
+
diff --git a/w/m/Templates/default-dark/updateOk.html/meta.json b/w/m/Templates/default-dark/updateOk.html/meta.json
new file mode 100644
index 0000000..4c7c3c3
--- /dev/null
+++ b/w/m/Templates/default-dark/updateOk.html/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "updateOk.html",
+ "comment": "Create Templates/default-dark/updateOk.html",
+ "author": "",
+ "time": 1592996644,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
diff --git a/w/m/Templates/default-light/Hypha/edit/header.html/1.html b/w/m/Templates/default-light/Hypha/edit/header.html/1.html
new file mode 100644
index 0000000..37a4b91
--- /dev/null
+++ b/w/m/Templates/default-light/Hypha/edit/header.html/1.html
@@ -0,0 +1 @@
+
diff --git a/w/m/Templates/default-light/Hypha/edit/header.html/meta.json b/w/m/Templates/default-light/Hypha/edit/header.html/meta.json
new file mode 100644
index 0000000..5ba815a
--- /dev/null
+++ b/w/m/Templates/default-light/Hypha/edit/header.html/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "header.html",
+ "comment": "Create Templates/default-light/Hypha/edit/header.html",
+ "author": "",
+ "time": 1592996801,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/w/templates/Hypha/edit/index.html b/w/m/Templates/default-light/Hypha/edit/index.html/1.html
similarity index 96%
rename from w/templates/Hypha/edit/index.html
rename to w/m/Templates/default-light/Hypha/edit/index.html/1.html
index b98e6e9..49ab078 100644
--- a/w/templates/Hypha/edit/index.html
+++ b/w/m/Templates/default-light/Hypha/edit/index.html/1.html
@@ -1,36 +1,36 @@
-
-
-
-
-
-
-
-
Edit box
-
-
-{{ .Text }}
-
-
-
Upload file
-
If this hypha has a file like that, the text above is meant to be a description of it
-
-
-
-
-
Text MIME-type
-
Good types are text/markdown
and text/plain
-
-
-
Revision comment
-
Please make your comment helpful
-
-
-
Edit tags
-
Tags are separated by commas, whitespace is ignored
-
-
-
-
+
+
+
+
+
+
+
+
Edit box
+
+
+{{ .Text }}
+
+
+
Upload file
+
If this hypha has a file like that, the text above is meant to be a description of it
+
+
+
+
+
Text MIME-type
+
Good types are text/markdown
and text/plain
+
+
+
Revision comment
+
Please make your comment helpful
+
+
+
Edit tags
+
Tags are separated by commas, whitespace is ignored
+
+
+
+
diff --git a/w/m/Templates/default-light/Hypha/edit/index.html/meta.json b/w/m/Templates/default-light/Hypha/edit/index.html/meta.json
new file mode 100644
index 0000000..36d0d70
--- /dev/null
+++ b/w/m/Templates/default-light/Hypha/edit/index.html/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "index.html",
+ "comment": "Create Templates/default-light/Hypha/edit/index.html",
+ "author": "",
+ "time": 1592996876,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/w/m/Templates/default-light/Hypha/view/404.html/1.html b/w/m/Templates/default-light/Hypha/view/404.html/1.html
new file mode 100644
index 0000000..7f558fb
--- /dev/null
+++ b/w/m/Templates/default-light/Hypha/view/404.html/1.html
@@ -0,0 +1,4 @@
+{{ . }}
+
+The hypha you are trying to access does not exist yet. Why not create it?
+
diff --git a/w/m/Templates/default-light/Hypha/view/404.html/meta.json b/w/m/Templates/default-light/Hypha/view/404.html/meta.json
new file mode 100644
index 0000000..1c22d20
--- /dev/null
+++ b/w/m/Templates/default-light/Hypha/view/404.html/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "404.html",
+ "comment": "Create Templates/default-light/Hypha/view/404.html",
+ "author": "",
+ "time": 1592996917,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/w/m/Templates/default-light/Hypha/view/index.html/1.html b/w/m/Templates/default-light/Hypha/view/index.html/1.html
new file mode 100644
index 0000000..f75b53a
--- /dev/null
+++ b/w/m/Templates/default-light/Hypha/view/index.html/1.html
@@ -0,0 +1 @@
+{{ . }}
diff --git a/w/m/Templates/default-light/Hypha/view/index.html/meta.json b/w/m/Templates/default-light/Hypha/view/index.html/meta.json
new file mode 100644
index 0000000..4b75feb
--- /dev/null
+++ b/w/m/Templates/default-light/Hypha/view/index.html/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "index.html",
+ "comment": "Create Templates/default-light/Hypha/view/index.html",
+ "author": "",
+ "time": 1592996954,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/w/m/Templates/default-light/Hypha/view/sidebar.html/1.html b/w/m/Templates/default-light/Hypha/view/sidebar.html/1.html
new file mode 100644
index 0000000..3f751f3
--- /dev/null
+++ b/w/m/Templates/default-light/Hypha/view/sidebar.html/1.html
@@ -0,0 +1,9 @@
+
+{{ .Tree }}
diff --git a/w/m/Templates/default-light/Hypha/view/sidebar.html/meta.json b/w/m/Templates/default-light/Hypha/view/sidebar.html/meta.json
new file mode 100644
index 0000000..0c89342
--- /dev/null
+++ b/w/m/Templates/default-light/Hypha/view/sidebar.html/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "sidebar.html",
+ "comment": "Create Templates/default-light/Hypha/view/sidebar.html",
+ "author": "",
+ "time": 1592996977,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/w/m/Templates/default-light/base.html/1.html b/w/m/Templates/default-light/base.html/1.html
new file mode 100644
index 0000000..04fdc5c
--- /dev/null
+++ b/w/m/Templates/default-light/base.html/1.html
@@ -0,0 +1,30 @@
+
+
+ {{ .Title }}
+
+
+
+
+ 🍄 Open
+
+ {{ .Main }}
+
+
+
+
diff --git a/w/m/Templates/default-light/base.html/meta.json b/w/m/Templates/default-light/base.html/meta.json
new file mode 100644
index 0000000..4cf6420
--- /dev/null
+++ b/w/m/Templates/default-light/base.html/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "base.html",
+ "comment": "Create Templates/default-light/base.html",
+ "author": "",
+ "time": 1592996503,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/w/m/Templates/default-light/main.css/1.css b/w/m/Templates/default-light/main.css/1.css
new file mode 100644
index 0000000..08686ba
--- /dev/null
+++ b/w/m/Templates/default-light/main.css/1.css
@@ -0,0 +1,221 @@
+*, *::before, *::after {
+ box-sizing: border-box;
+}
+
+html {
+ height: 100%;
+}
+
+body {
+ font: 15px/1.5 system-ui, -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Helvetica', 'PT Sans', 'Roboto', 'Arial', sans-serif;
+ max-width: 500px;
+ min-height: 100%;
+ margin: 0 auto;
+ padding: 12px 24px;
+}
+
+.msg {
+ background-color: #f4f4f4;
+ padding: 1rem;
+ border-radius: 1rem;
+}
+
+.shroom {
+ margin: 0;
+}
+
+.shroom__button {
+ border-radius: 1rem;
+ padding: 8px 16px 8px 0;
+ border: none;
+ background: #f0f2f4;
+ color: #444;
+ font: inherit;
+ font-size: 15px;
+ font-weight: 500;
+ text-align: left;
+}
+
+.shroom span {
+ margin-left: 16px;
+ margin-right: 8px;
+ font-size: 20px;
+ vertical-align: -0.04em;
+}
+
+.mushroom .shroom__button {
+ background: #44484a;
+ color: #dddfe4;
+}
+
+
+.header {
+ padding: 8px 0;
+}
+
+.header h1 {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ color: #222428;
+}
+
+
+a {
+ color: #44e;
+}
+
+a:visited {
+ color: #44a;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ margin: 0.5em 0 0.25em;
+}
+
+.page {
+ font-size: 16px;
+ line-height: 1.666;
+ max-width: 40em;
+ hyphens: auto;
+}
+
+.page pre {
+ white-space: break-spaces;
+}
+
+.page__amnt {
+ max-width: 100%;
+}
+
+.page__title {
+ font-family: 'PT Serif', 'Georgia', serif;
+ font-size: 36px;
+ font-weight: normal;
+}
+
+.edit-box {
+ display: grid;
+ grid-template-columns: 7fr 5fr;
+}
+.edit-box .naviwrapper__buttons {
+ grid-column: 1;
+ grid-row: 2;
+}
+.edit-box__left { grid-column: 1; grid-row: 2 }
+.edit-box__right { grid-column: 2; grid-row: 1 / span 2; padding-right: 16px }
+
+footer {
+ padding: 1em 0;
+ font-size: 12px;
+ color: #888;
+}
+
+footer a, footer a:visited {
+ color: #666;
+}
+
+.left-panel {
+ display: none;
+}
+
+.left-panel.active {
+ display: block;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: #fafafa;
+}
+
+.left-panel.active .sidebar {
+ background: #fff;
+}
+
+.left-panel__in {
+ width: 100%;
+ height: 100%;
+ max-width: 500px;
+ margin: 0 auto;
+ padding: 12px 24px;
+
+}
+
+.left-panel__contents {
+ width: 100%;
+ display: grid;
+ grid-template-rows: auto 1fr auto;
+}
+
+.left-panel .shroom {
+ margin-bottom: 16px;
+}
+
+@media (min-width: 700px) {
+ body {
+ max-width: 1200px;
+ padding: 8px 16px;
+ padding-right: 274px;
+ }
+
+ .shroom {
+ display: none;
+ }
+
+ .page {
+ font-size: 18px;
+ }
+
+ .left-panel {
+ display: block;
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ width: 274px;
+ right: 0;
+ }
+
+ .left-panel__contents {
+ height: 100%;
+ }
+}
+
+.sidebar {
+ padding: 16px 0;
+ border-radius: 1rem;
+ background: #f4f4f4;
+}
+
+.hypha-actions ul {
+ margin: 0;
+ padding: 0;
+}
+
+.hypha-actions li {
+ list-style: none;
+}
+
+.hypha-actions a {
+ display: block;
+ padding: 6px 16px;
+ font: inherit;
+ text-decoration: none;
+ color: #666;
+ transition: 0.1s background;
+}
+
+aside .hypha-actions a:hover {
+ background: #eaeaea;
+}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/w/m/Templates/default-light/main.css/meta.json b/w/m/Templates/default-light/main.css/meta.json
new file mode 100644
index 0000000..c34401f
--- /dev/null
+++ b/w/m/Templates/default-light/main.css/meta.json
@@ -0,0 +1,19 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": [
+ ""
+ ],
+ "name": "main.css",
+ "comment": "Update sys/main.css",
+ "author": "",
+ "time": 1592666188,
+ "text_mime": "text/css",
+ "binary_mime": "",
+ "text_name": "1.css",
+ "binary_name": ""
+ }
+ }
+}
diff --git a/w/m/Templates/default-light/main.js/1.mjs b/w/m/Templates/default-light/main.js/1.mjs
new file mode 100644
index 0000000..7a221e6
--- /dev/null
+++ b/w/m/Templates/default-light/main.js/1.mjs
@@ -0,0 +1,7 @@
+var menu = document.getElementById('shroomburgerMenu');
+document.getElementById('shroomBtn').addEventListener('click', function() {
+ menu.classList.add('active');
+});
+document.getElementById('mushroomBtn').addEventListener('click', function() {
+ menu.classList.remove('active');
+});
diff --git a/w/m/Templates/default-light/main.js/meta.json b/w/m/Templates/default-light/main.js/meta.json
new file mode 100644
index 0000000..98d4ad5
--- /dev/null
+++ b/w/m/Templates/default-light/main.js/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "main.js",
+ "comment": "Update sys/main.js",
+ "author": "",
+ "time": 1592937088,
+ "text_mime": "text/javascript",
+ "binary_mime": "",
+ "text_name": "1.mjs",
+ "binary_name": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/w/m/Templates/default-light/updateOk.html/1.html b/w/m/Templates/default-light/updateOk.html/1.html
new file mode 100644
index 0000000..f4ddcde
--- /dev/null
+++ b/w/m/Templates/default-light/updateOk.html/1.html
@@ -0,0 +1,8 @@
+
+
+ Saved {{ .Name }}
+
+
+ Saved successfully. Go back
+
+
diff --git a/w/m/Templates/default-light/updateOk.html/meta.json b/w/m/Templates/default-light/updateOk.html/meta.json
new file mode 100644
index 0000000..5662fd2
--- /dev/null
+++ b/w/m/Templates/default-light/updateOk.html/meta.json
@@ -0,0 +1,17 @@
+{
+ "views": 0,
+ "deleted": false,
+ "revisions": {
+ "1": {
+ "tags": null,
+ "name": "updateOk.html",
+ "comment": "Create Templates/default-light/updateOk.html",
+ "author": "",
+ "time": 1592996644,
+ "text_mime": "text/html",
+ "binary_mime": "",
+ "text_name": "1.html",
+ "binary_name": ""
+ }
+ }
+}
\ No newline at end of file