diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ffd5f5a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "metarrhiza"] + path = metarrhiza + url = https://github.com/bouncepaw/metarrhiza.git diff --git a/Makefile b/Makefile index 1573ad2..49dc584 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ run: build - ./mycorrhiza wiki + ./mycorrhiza metarrhiza build: go build . test: - go test ./util + go test . help: echo "Read the Makefile to see what it can do. It is simple." diff --git a/README.md b/README.md index 93047a7..64dc545 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,13 @@ # mycorrhiza wiki A wiki engine inspired by fungi. Not production-ready. -Current version: 0.5 (or more?) +Current version: 0.7 (or more?) ## Current features * Edit pages through html forms * Responsive design -* Theme support (no themes other than the default one implemented though) * Works in text browsers -* Pages (called hyphae) can be written in markdown, creole or geminitext. -* Two types of plugins: parsers (for supporting more markup languages) and languages (for i18n) -* Change history is saved and can be viewed (no nice way to do this yet, it can be done by changing url only) -* Namespaces called mycelia +* Pages (called hyphae) can be in gemtext. * Everything is stored as simple files, no database required ## Future features @@ -23,9 +19,3 @@ Current version: 0.5 (or more?) ## Installation I guess you can just clone this repo and run `make` to play around with the default wiki. -## License -Contents of `wiki` directory are under CC BY-SA 4.0. - -File `wiki/favicon.icon` is from [here](https://thenounproject.com/search/?q=mushroom&i=990340). - -The license for the rest of files is not decided yet, so let's consider it to be CC BY-SA 4.0 too. diff --git a/cfg/config.go b/cfg/config.go deleted file mode 100644 index d47ca38..0000000 --- a/cfg/config.go +++ /dev/null @@ -1,96 +0,0 @@ -package cfg - -import ( - "encoding/json" - "io/ioutil" - "log" - "os" - "path/filepath" - - "github.com/bouncepaw/mycorrhiza/plugin/lang" -) - -type MyceliumConfig struct { - Names []string `json:"names"` - Type string `json:"type"` -} - -const ( - HyphaPattern = `[^\s\d:/?&\\][^:?&\\]*` - HyphaUrl = `/{hypha:` + HyphaPattern + `}` - RevisionPattern = `[\d]+` - RevQuery = `{rev:` + RevisionPattern + `}` - MyceliumPattern = `[^\s\d:/?&\\][^:?&\\/]*` - MyceliumUrl = `/:{mycelium:` + MyceliumPattern + `}` -) - -var ( - Locale map[string]string - WikiDir string - configJsonPath string - - // Default values that can be overriden in config.json - Address = "0.0.0.0:80" - LocaleName = "en" - SiteTitle = `MycorrhizaWiki` - Theme = `default-light` - HomePage = `/Home` - BinaryLimit int64 = 10 << 20 - Mycelia = []MyceliumConfig{ - {[]string{"main"}, "main"}, - {[]string{"sys", "system"}, "system"}, - } -) - -func InitConfig(wd string) bool { - log.Println("WikiDir is", wd) - WikiDir = wd - configJsonPath = filepath.Join(WikiDir, "config.json") - - if _, err := os.Stat(configJsonPath); os.IsNotExist(err) { - log.Println("config.json not found, using default values") - return false - } - log.Println("config.json found, overriding default values...") - return readConfig() -} - -func readConfig() bool { - configJsonContents, err := ioutil.ReadFile(configJsonPath) - if err != nil { - log.Fatal("Error when reading config.json:", err) - return false - } - - cfg := struct { - Address string `json:"address"` - Theme string `json:"theme"` - SiteTitle string `json:"site-title"` - HomePage string `json:"home-page"` - BinaryLimitMB int64 `json:"binary-limit-mb"` - LocaleName string `json:"locale"` - Mycelia []MyceliumConfig `json:"mycelia"` - }{} - - err = json.Unmarshal(configJsonContents, &cfg) - if err != nil { - log.Fatal("Error when parsing config.json:", err) - return false - } - - Address = cfg.Address - Theme = cfg.Theme - SiteTitle = cfg.SiteTitle - HomePage = "/" + cfg.HomePage - BinaryLimit = 1024 * cfg.BinaryLimitMB - Mycelia = cfg.Mycelia - - switch cfg.LocaleName { - case "en": - Locale = lang.EnglishMap - default: - Locale = lang.EnglishMap - } - - return true -} diff --git a/fs/data.go b/fs/data.go deleted file mode 100644 index 15599cf..0000000 --- a/fs/data.go +++ /dev/null @@ -1,86 +0,0 @@ -// This file contains methods for Hypha that calculate data about the hypha based on known information. -package fs - -import ( - "fmt" - "io/ioutil" - "log" - "path/filepath" - "strconv" - "strings" - - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/util" -) - -func (h *Hypha) MetaJsonPath() string { - return filepath.Join(h.Path(), "meta.json") -} - -func (h *Hypha) CanonicalName() string { - return util.UrlToCanonical(h.FullName) -} - -func (h *Hypha) Path() string { - return filepath.Join(cfg.WikiDir, h.CanonicalName()) -} - -func (h *Hypha) TextPath() string { - return h.actual.TextPath -} - -func (h *Hypha) parentName() string { - return filepath.Dir(util.UrlToCanonical(h.FullName)) -} - -// hasBinaryData returns true if the revision has any binary data associated. -// During initialisation, it is guaranteed that r.BinaryMime is set to "" if the revision has no binary data. (is it?) -func (h *Hypha) hasBinaryData() bool { - return h.actual.BinaryMime != "" -} - -func (h *Hypha) TagsJoined() string { - if h.Exists { - return strings.Join(h.actual.Tags, ", ") - } - return "" -} - -func (h *Hypha) TextMime() string { - if h.Exists { - return h.actual.TextMime - } - return "text/markdown" -} - -func (h *Hypha) mimeTypeForActionRaw() string { - // If text mime type is text/html, it is not good as it will be rendered. - if h.actual.TextMime == "text/html" { - return "text/plain" - } - return h.actual.TextMime -} - -// NewestId finds the largest id among all revisions. -func (h *Hypha) NewestId() string { - var largest int - for k, _ := range h.Revisions { - id, _ := strconv.Atoi(k) - if id > largest { - largest = id - } - } - return strconv.Itoa(largest) -} - -func (h *Hypha) TextContent() string { - if h.Exists { - contents, err := ioutil.ReadFile(h.TextPath()) - if err != nil { - log.Println("Could not read", h.FullName) - return "Error: could not hypha text content file. It is recommended to cancel editing. Please contact the wiki admin. If you are the admin, see the logs." - } - return string(contents) - } - return fmt.Sprintf(cfg.Locale["edit/box/help pattern"], h.FullName) -} diff --git a/fs/fs.go b/fs/fs.go deleted file mode 100644 index 3d1488a..0000000 --- a/fs/fs.go +++ /dev/null @@ -1,56 +0,0 @@ -package fs - -import ( - "github.com/bouncepaw/mycorrhiza/cfg" - "io/ioutil" - "log" - "path/filepath" - "regexp" -) - -type Storage struct { - // hypha name => path - paths map[string]string - root string -} - -var Hs *Storage - -// InitStorage initiates filesystem-based hypha storage. It has to be called after configuration was inited. -func InitStorage() { - Hs = &Storage{ - paths: make(map[string]string), - root: cfg.WikiDir, - } - 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` -func hyphaName(fullPath string) string { - // {cfg.WikiDir}/{the name} - return fullPath[len(cfg.WikiDir)+1:] -} - -// indexHyphae searches for all hyphae that seem valid in `path` and saves their absolute paths to `s.paths`. This function is recursive. -func (s *Storage) indexHyphae(path string) { - nodes, err := ioutil.ReadDir(path) - if err != nil { - log.Fatal("Error when checking", path, ":", err, "; skipping") - return - } - - for _, node := range nodes { - matchesHypha, err := regexp.MatchString(cfg.HyphaPattern, node.Name()) - if err != nil { - log.Fatal("Error when matching", node.Name(), err, "\n") - return - } - switch name := filepath.Join(path, node.Name()); { - case matchesHypha && node.IsDir(): - s.indexHyphae(name) - case node.Name() == "meta.json" && !node.IsDir(): - s.paths[hyphaName(path)] = path - } - } -} diff --git a/fs/genealogy.go b/fs/genealogy.go deleted file mode 100644 index 5d63c37..0000000 --- a/fs/genealogy.go +++ /dev/null @@ -1,96 +0,0 @@ -package fs - -import ( - "fmt" - "log" - "path/filepath" - "sort" - "strings" - - "github.com/bouncepaw/mycorrhiza/util" -) - -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 { - name = util.UrlToCanonical(name) - 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 += `` - 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, ":"+util.DisplayToCanonical(name), filepath.Base(name)) -} diff --git a/fs/html.go b/fs/html.go deleted file mode 100644 index cae647c..0000000 --- a/fs/html.go +++ /dev/null @@ -1,38 +0,0 @@ -package fs - -import ( - "fmt" - "io/ioutil" - "log" - - "github.com/bouncepaw/mycorrhiza/plugin" - "github.com/bouncepaw/mycorrhiza/util" -) - -func (h *Hypha) asHtml() (string, error) { - rev := h.actual - ret := `
    -

    ` + rev.FullName + `

    -` - // What about using
    ? - // TODO: support other things - if h.hasBinaryData() { - ret += fmt.Sprintf(``, util.DisplayToCanonical(rev.FullName), rev.Id) - } - - contents, err := ioutil.ReadFile(rev.TextPath) - if err != nil { - log.Println("Failed to read contents of", rev.FullName, ":", err) - return "", err - } - - // TODO: support more markups. - // TODO: support mycorrhiza extensions like transclusion. - parser := plugin.ParserForMime(rev.TextMime) - ret += parser(contents) - - ret += ` -
    ` - - return ret, nil -} diff --git a/fs/hypha.go b/fs/hypha.go deleted file mode 100644 index 589c100..0000000 --- a/fs/hypha.go +++ /dev/null @@ -1,341 +0,0 @@ -package fs - -import ( - "encoding/json" - "errors" - "io/ioutil" - "log" - "net/http" - "os" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/mycelium" - "github.com/bouncepaw/mycorrhiza/util" -) - -type Hypha struct { - Exists bool `json:"-"` - FullName string `json:"-"` - ViewCount int `json:"views"` - Deleted bool `json:"deleted"` - Revisions map[string]*Revision `json:"revisions"` - actual *Revision `json:"-"` - Invalid bool `json:"-"` - Err error `json:"-"` -} - -func (h *Hypha) Invalidate(err error) *Hypha { - h.Invalid = true - h.Err = err - return h -} - -func (s *Storage) OpenFromMap(m map[string]string) *Hypha { - name := mycelium.NameWithMyceliumInMap(m) - h := s.open(name) - if rev, ok := m["rev"]; ok { - return h.OnRevision(rev) - } - return h -} - -func (s *Storage) open(name string) *Hypha { - name = util.UrlToCanonical(name) - h := &Hypha{ - Exists: true, - FullName: util.CanonicalToDisplay(name), - } - path, ok := s.paths[name] - // This hypha does not exist yet - if !ok { - log.Println("Hypha", name, "does not exist") - h.Exists = false - h.Revisions = make(map[string]*Revision) - } else { - metaJsonText, err := ioutil.ReadFile(filepath.Join(path, "meta.json")) - if err != nil { - return h.Invalidate(err) - } - - err = json.Unmarshal(metaJsonText, &h) - if err != nil { - return h.Invalidate(err) - } - // fill in rooted paths to content files and full names - for idStr, rev := range h.Revisions { - rev.FullName = filepath.Join(h.parentName(), rev.ShortName) - rev.Id, _ = strconv.Atoi(idStr) - if rev.BinaryName != "" { - rev.BinaryPath = filepath.Join(path, rev.BinaryName) - } - rev.TextPath = filepath.Join(path, rev.TextName) - } - - return h.OnRevision("0") - } - return h -} - -// OnRevision tries to change to a revision specified by `id`. -func (h *Hypha) OnRevision(id string) *Hypha { - if h.Invalid || !h.Exists { - return h - } - if len(h.Revisions) == 0 { - return h.Invalidate(errors.New("This hypha has no revisions")) - } - if id == "0" { - id = h.NewestId() - } - // Revision must be there, so no error checking - if rev, _ := h.Revisions[id]; true { - h.actual = rev - } - return h -} - -func (h *Hypha) PlainLog(s string) { - if h.Exists { - log.Println(h.FullName, h.actual.Id, s) - } else { - log.Println("nonexistent", h.FullName, s) - } -} - -func (h *Hypha) LogSuccMaybe(succMsg string) *Hypha { - if h.Invalid { - h.PlainLog(h.Err.Error()) - } else { - h.PlainLog(succMsg) - } - return h -} - -// ActionRaw is used with `?action=raw`. -// It writes text content of the revision without any parsing or rendering. -func (h *Hypha) ActionRaw(w http.ResponseWriter) *Hypha { - if h.Invalid { - return h - } - if h.Exists { - fileContents, err := ioutil.ReadFile(h.actual.TextPath) - if err != nil { - return h.Invalidate(err) - } - w.Header().Set("Content-Type", h.mimeTypeForActionRaw()) - w.WriteHeader(http.StatusOK) - w.Write(fileContents) - } else { - log.Println("Hypha", h.FullName, "has no actual revision") - w.WriteHeader(http.StatusNotFound) - } - return h -} - -// ActionBinary is used with `?action=binary`. -// It writes contents of binary content file. -func (h *Hypha) ActionBinary(w http.ResponseWriter) *Hypha { - if h.Invalid { - return h - } - if h.Exists { - fileContents, err := ioutil.ReadFile(h.actual.BinaryPath) - if err != nil { - return h.Invalidate(err) - } - w.Header().Set("Content-Type", h.actual.BinaryMime) - w.WriteHeader(http.StatusOK) - w.Write(fileContents) - } else { - log.Println("Hypha", h.FullName, "has no actual revision") - w.WriteHeader(http.StatusNotFound) - } - return h -} - -// ActionZen is used with `?action=zen`. -// It renders the hypha but without any layout or styles. Pure. Zen. -func (h *Hypha) ActionZen(w http.ResponseWriter) *Hypha { - if h.Invalid { - return h - } - html, err := h.asHtml() - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return h.Invalidate(err) - } - w.Header().Set("Content-Type", "text/html;charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write([]byte(html)) - return h -} - -// 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) []byte) *Hypha { - var html string - var err error - if h.Exists { - html, err = h.asHtml() - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return h.Invalidate(err) - } - } - w.Header().Set("Content-Type", "text/html;charset=utf-8") - w.WriteHeader(http.StatusOK) - if h.Exists { - w.Write(renderExists(h.FullName, html)) - } else { - w.Write(renderNotExists(h.FullName, "")) - } - return h -} - -// CreateDirIfNeeded creates directory where the hypha must reside if needed. -// It is not needed if the dir already exists. -func (h *Hypha) CreateDirIfNeeded() *Hypha { - if h.Invalid { - return h - } - // os.MkdirAll created dir if it is not there. Basically, checks it for us. - err := os.MkdirAll(h.Path(), os.ModePerm) - if err != nil { - h.Invalidate(err) - } - return h -} - -// makeTagsSlice turns strings like `"foo,, bar,kek"` to slice of strings that represent tag names. Whitespace around commas is insignificant. -// Expected output for string above: []string{"foo", "bar", "kek"} -func makeTagsSlice(responseTagsString string) (ret []string) { - for _, tag := range strings.Split(responseTagsString, ",") { - if trimmed := strings.TrimSpace(tag); "" == trimmed { - ret = append(ret, trimmed) - } - } - return ret -} - -// revisionFromHttpData creates a new revison for hypha `h`. All data is fetched from `rq`, except for BinaryMime and BinaryPath which require additional processing. The revision is inserted for you. You'll have to pop it out if there is an error. -func (h *Hypha) AddRevisionFromHttpData(rq *http.Request) *Hypha { - if h.Invalid { - return h - } - id := 1 - if h.Exists { - id = h.actual.Id + 1 - } - log.Printf("Creating revision %d from http data", id) - rev := &Revision{ - Id: id, - FullName: h.FullName, - ShortName: filepath.Base(h.FullName), - Tags: makeTagsSlice(rq.PostFormValue("tags")), - Comment: rq.PostFormValue("comment"), - Author: rq.PostFormValue("author"), - Time: int(time.Now().Unix()), - TextMime: rq.PostFormValue("text_mime"), - // Fields left: BinaryMime, BinaryPath, BinaryName, TextName, TextPath - } - rev.generateTextFilename() // TextName is set now - rev.TextPath = filepath.Join(h.Path(), rev.TextName) - return h.AddRevision(rev) -} - -func (h *Hypha) AddRevision(rev *Revision) *Hypha { - if h.Invalid { - return h - } - h.Revisions[strconv.Itoa(rev.Id)] = rev - h.actual = rev - return h -} - -// WriteTextFileFromHttpData tries to fetch text content from `rq` for revision `rev` and write it to a corresponding text file. It used in `HandlerUpdate`. -func (h *Hypha) WriteTextFileFromHttpData(rq *http.Request) *Hypha { - if h.Invalid { - return h - } - data := []byte(rq.PostFormValue("text")) - err := ioutil.WriteFile(h.TextPath(), data, 0644) - if err != nil { - log.Println("Failed to write", len(data), "bytes to", h.TextPath()) - h.Invalidate(err) - } - return h -} - -// WriteBinaryFileFromHttpData tries to fetch binary content from `rq` for revision `newRev` and write it to a corresponding binary file. If there is no content, it is taken from a previous revision, if there is any. -func (h *Hypha) WriteBinaryFileFromHttpData(rq *http.Request) *Hypha { - if h.Invalid { - return h - } - // 10 MB file size limit - rq.ParseMultipartForm(cfg.BinaryLimit) - // Read file - file, handler, err := rq.FormFile("binary") - if file != nil { - defer file.Close() - } - // If file is not passed: - if err != nil { - // Let's hope there are no other errors 🙏 - // TODO: actually check if there any other errors - log.Println("No binary data passed for", h.FullName) - // It is expected there is at least one revision - if len(h.Revisions) > 1 { - prevRev := h.Revisions[strconv.Itoa(h.actual.Id-1)] - h.actual.BinaryMime = prevRev.BinaryMime - h.actual.BinaryPath = prevRev.BinaryPath - h.actual.BinaryName = prevRev.BinaryName - log.Println("Set previous revision's binary data") - } - return h - } - // If file is passed: - h.actual.BinaryMime = handler.Header.Get("Content-Type") - h.actual.generateBinaryFilename() - h.actual.BinaryPath = filepath.Join(h.Path(), h.actual.BinaryName) - - data, err := ioutil.ReadAll(file) - if err != nil { - return h.Invalidate(err) - } - log.Println("Got", len(data), "of binary data for", h.FullName) - err = ioutil.WriteFile(h.actual.BinaryPath, data, 0644) - if err != nil { - return h.Invalidate(err) - } - log.Println("Written", len(data), "of binary data for", h.FullName) - return h -} - -// SaveJson dumps the hypha's metadata to `meta.json` file. -func (h *Hypha) SaveJson() *Hypha { - if h.Invalid { - return h - } - data, err := json.MarshalIndent(h, "", "\t") - if err != nil { - return h.Invalidate(err) - } - err = ioutil.WriteFile(h.MetaJsonPath(), data, 0644) - if err != nil { - return h.Invalidate(err) - } - log.Println("Saved JSON data of", h.FullName) - return h -} - -// Store adds `h` to the `Hs` if it is not already there -func (h *Hypha) Store() *Hypha { - if !h.Invalid { - Hs.paths[h.CanonicalName()] = h.Path() - } - return h -} diff --git a/fs/revision.go b/fs/revision.go deleted file mode 100644 index 69ae0c6..0000000 --- a/fs/revision.go +++ /dev/null @@ -1,42 +0,0 @@ -package fs - -import ( - "mime" - "strconv" -) - -type Revision struct { - Id int `json:"-"` - FullName string `json:"-"` - Tags []string `json:"tags"` - ShortName string `json:"name"` - Comment string `json:"comment"` - Author string `json:"author"` - Time int `json:"time"` - TextMime string `json:"text_mime"` - BinaryMime string `json:"binary_mime"` - TextPath string `json:"-"` - BinaryPath string `json:"-"` - TextName string `json:"text_name"` - BinaryName string `json:"binary_name"` -} - -// TODO: https://github.com/bouncepaw/mycorrhiza/issues/4 -// Some filenames are wrong? -func (rev *Revision) generateTextFilename() { - ts, err := mime.ExtensionsByType(rev.TextMime) - if err != nil || ts == nil { - rev.TextName = strconv.Itoa(rev.Id) + ".txt" - } else { - rev.TextName = strconv.Itoa(rev.Id) + ts[0] - } -} - -func (rev *Revision) generateBinaryFilename() { - ts, err := mime.ExtensionsByType(rev.BinaryMime) - if err != nil || ts == nil { - rev.BinaryName = strconv.Itoa(rev.Id) + ".bin" - } else { - rev.BinaryName = strconv.Itoa(rev.Id) + ts[0] - } -} diff --git a/gemtext/lexer.go b/gemtext/lexer.go new file mode 100644 index 0000000..507808b --- /dev/null +++ b/gemtext/lexer.go @@ -0,0 +1,173 @@ +package gemtext + +import ( + "fmt" + "html" + "path" + "strings" +) + +// HyphaExists holds function that checks that a hypha is present. +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. +type GemLexerState struct { + // Name of hypha being parsed + name string + where string // "", "list", "pre" + // Line id + id int + buf string +} + +// GeminiToHtml converts gemtext `content` of hypha `name` to html string. +func GeminiToHtml(name, content string) string { + return "TODO: do" +} + +type Line struct { + id int + // interface{} may be bad. What I need is a sum of string and Transclusion + contents interface{} +} + +// Parse gemtext 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)) + if src == "" { + return + } + // Href is text after => till first whitespace + href = strings.Fields(src)[0] + // Text is everything after whitespace. + // If there's no text, make it same as href + if text = strings.TrimPrefix(src, href); text == "" { + text = href + } + + class = "wikilink_internal" + + switch { + case strings.HasPrefix(href, "./"): + hyphaName := canonicalName(path.Join( + state.name, strings.TrimPrefix(href, "./"))) + if !HyphaExists(hyphaName) { + class = "wikilink_new" + } + href = path.Join("/page", hyphaName) + case strings.HasPrefix(href, "../"): + hyphaName := canonicalName(path.Join( + path.Dir(state.name), strings.TrimPrefix(href, "../"))) + if !HyphaExists(hyphaName) { + class = "wikilink_new" + } + href = path.Join("/page", hyphaName) + case strings.HasPrefix(href, "/"): + case strings.ContainsRune(href, ':'): + class = "wikilink_external" + default: + href = path.Join("/page", href) + } + return href, strings.TrimSpace(text), class +} + +func lex(name, content string) (ast []Line) { + var state = GemLexerState{name: name} + + for _, line := range strings.Split(content, "\n") { + geminiLineToAST(line, &state, &ast) + } + return ast +} + +// Lex `line` in gemtext and save it to `ast` using `state`. +func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) { + if "" == strings.TrimSpace(line) { + return + } + + startsWith := func(token string) bool { + return strings.HasPrefix(line, token) + } + addLine := func(text interface{}) { + *ast = append(*ast, Line{id: state.id, contents: text}) + } + + // Beware! Usage of goto. Some may say it is considered evil but in this case it helped to make a better-structured code. + switch state.where { + case "pre": + goto preformattedState + case "list": + goto listState + default: + goto normalState + } + +preformattedState: + switch { + case startsWith("```"): + state.where = "" + state.buf = strings.TrimSuffix(state.buf, "\n") + addLine(state.buf + "") + state.buf = "" + default: + state.buf += html.EscapeString(line) + "\n" + } + return + +listState: + switch { + case startsWith("*"): + state.buf += fmt.Sprintf("\t
  • %s
  • \n", remover("*")(line)) + case startsWith("```"): + state.where = "pre" + addLine(state.buf + "") + state.id++ + state.buf = fmt.Sprintf("
    ", state.id, strings.TrimPrefix(line, "```"))
    +	default:
    +		state.where = ""
    +		addLine(state.buf + "")
    +		goto normalState
    +	}
    +	return
    +
    +normalState:
    +	state.id++
    +	switch {
    +
    +	case startsWith("```"):
    +		state.where = "pre"
    +		state.buf = fmt.Sprintf("
    ", state.id, strings.TrimPrefix(line, "```"))
    +	case startsWith("*"):
    +		state.where = "list"
    +		state.buf = fmt.Sprintf("
      \n", state.id) + goto listState + + case startsWith("###"): + addLine(fmt.Sprintf( + "

      %s

      ", state.id, removeHeadingOctothorps(line))) + case startsWith("##"): + addLine(fmt.Sprintf( + "

      %s

      ", state.id, removeHeadingOctothorps(line))) + case startsWith("#"): + addLine(fmt.Sprintf( + "

      %s

      ", state.id, removeHeadingOctothorps(line))) + + case startsWith(">"): + addLine(fmt.Sprintf( + "
      %s
      ", state.id, remover(">")(line))) + case startsWith("=>"): + source, content, class := wikilink(line, state) + addLine(fmt.Sprintf( + `

      %s

      `, state.id, class, source, content)) + + case startsWith("<="): + addLine(parseTransclusion(line, state.name)) + default: + addLine(fmt.Sprintf("

      %s

      ", state.id, line)) + } +} diff --git a/gemtext/lexer_test.go b/gemtext/lexer_test.go new file mode 100644 index 0000000..f4129b8 --- /dev/null +++ b/gemtext/lexer_test.go @@ -0,0 +1,57 @@ +package gemtext + +import ( + "fmt" + "io/ioutil" + "reflect" + "testing" +) + +// TODO: move test gemtext docs to files, perhaps? These strings sure are ugly +func TestLex(t *testing.T) { + check := func(name, content string, expectedAst []Line) { + if ast := lex(name, content); !reflect.DeepEqual(ast, expectedAst) { + if len(ast) != len(expectedAst) { + t.Error("Expected and generated AST length of", name, "do not match. Printed generated AST.") + for _, l := range ast { + fmt.Printf("%d: %s\n", l.id, l.contents) + } + return + } + for i, e := range ast { + if e != expectedAst[i] { + t.Error("Mismatch when lexing", name, "\nExpected:", expectedAst[i], "\nGot:", e) + } + } + } + } + contentsB, err := ioutil.ReadFile("testdata/test.gmi") + if err != nil { + t.Error("Could not read test gemtext file!") + } + contents := string(contentsB) + check("Apple", contents, []Line{ + {1, "

      1

      "}, + {2, "

      2

      "}, + {3, "

      3

      "}, + {4, "
      quote
      "}, + {5, `
        +
      • li 1
      • +
      • li 2
      • +
      `}, + {6, "

      text

      "}, + {7, "

      more text

      "}, + {8, `

      some link

      `}, + {9, `
        +
      • li\n"+
      • +
      `}, + {10, `
      => preformatted text
      +where gemtext is not lexed
      `}, + {11, `

      linking

      `}, + {12, "

      text

      "}, + {13, `
      ()
      +/\
      `}, + // More thorough testing of xclusions is done in xclusion_test.go + {14, Transclusion{"apple", 1, 3}}, + }) +} diff --git a/gemtext/parser.go b/gemtext/parser.go new file mode 100644 index 0000000..dae7682 --- /dev/null +++ b/gemtext/parser.go @@ -0,0 +1,31 @@ +package gemtext + +import () + +const maxRecursionLevel = 3 + +type GemParserState struct { + recursionLevel int +} + +func Parse(ast []Line, from, to int, state GemParserState) (html string) { + if state.recursionLevel > maxRecursionLevel { + return "Transclusion depth limit" + } + for _, line := range ast { + if line.id >= from && (line.id <= to || to == 0) { + switch v := line.contents.(type) { + case Transclusion: + html += Transclude(v, state) + case string: + html += v + } + } + } + return html +} + +func ToHtml(name, text string) string { + state := GemParserState{} + return Parse(lex(name, text), 0, 0, state) +} diff --git a/gemtext/testdata/test.gmi b/gemtext/testdata/test.gmi new file mode 100644 index 0000000..c1dc9c7 --- /dev/null +++ b/gemtext/testdata/test.gmi @@ -0,0 +1,24 @@ +# 1 +## 2 +### 3 +> quote + +* li 1 +* li 2 +text +more text +=> Pear some link + +* li\n"+ +```alt text goes here +=> preformatted text +where gemtext is not lexed +```it ends here" +=>linking + +text +``` +() +/\ +``` +<= Apple : 1..3 diff --git a/gemtext/utils.go b/gemtext/utils.go new file mode 100644 index 0000000..65b8c41 --- /dev/null +++ b/gemtext/utils.go @@ -0,0 +1,23 @@ +package gemtext + +import ( + "strings" +) + +// Function that returns a function that can strip `prefix` and trim whitespace when called. +func remover(prefix string) func(string) string { + return func(l string) string { + return strings.TrimSpace(strings.TrimPrefix(l, prefix)) + } +} + +// Remove #, ## or ### from beginning of `line`. +func removeHeadingOctothorps(line string) string { + f := remover("#") + return f(f(f(line))) +} + +// Return a canonical representation of a hypha `name`. +func canonicalName(name string) string { + return strings.ToLower(strings.ReplaceAll(strings.TrimSpace(name), " ", "_")) +} diff --git a/gemtext/xclusion.go b/gemtext/xclusion.go new file mode 100644 index 0000000..32514d7 --- /dev/null +++ b/gemtext/xclusion.go @@ -0,0 +1,106 @@ +package gemtext + +import ( + "fmt" + "path" + "strconv" + "strings" +) + +const xclError = -9 + +// Transclusion is used by gemtext parser to remember what hyphae shall be transcluded. +type Transclusion struct { + name string + from int // inclusive + to int // inclusive +} + +// Transclude transcludes `xcl` and returns html representation. +func Transclude(xcl Transclusion, state GemParserState) (html string) { + state.recursionLevel++ + tmptOk := `
      + %s +
      %s
      +
      ` + tmptFailed := `
      +

      Failed to transclude %s

      +
      ` + if xcl.from == xclError || xcl.to == xclError || xcl.from > xcl.to { + return fmt.Sprintf(tmptFailed, xcl.name, xcl.name) + } + + rawText, binaryHtml, err := HyphaAccess(xcl.name) + if err != nil { + return fmt.Sprintf(tmptFailed, xcl.name, xcl.name) + } + xclText := Parse(lex(xcl.name, rawText), xcl.from, xcl.to, state) + return fmt.Sprintf(tmptOk, xcl.name, xcl.name, binaryHtml+xclText) +} + +/* Grammar from hypha ‘transclusion’: +transclusion_line ::= transclusion_token hypha_name LWS* [":" LWS* range LWS*] +transclusion_token ::= "<=" LWS+ +hypha_name ::= canonical_name | noncanonical_name +range ::= id | (from_id two_dots to_id) | (from_id two_dots) | (two_dots to_id) +two_dots ::= ".." +*/ + +func parseTransclusion(line, hyphaName string) (xclusion Transclusion) { + line = strings.TrimSpace(remover("<=")(line)) + if line == "" { + return Transclusion{"", xclError, xclError} + } + + if strings.ContainsRune(line, ':') { + parts := strings.SplitN(line, ":", 2) + xclusion.name = xclCanonicalName(hyphaName, strings.TrimSpace(parts[0])) + selector := strings.TrimSpace(parts[1]) + xclusion.from, xclusion.to = parseSelector(selector) + } else { + xclusion.name = xclCanonicalName(hyphaName, strings.TrimSpace(line)) + } + return xclusion +} + +func xclCanonicalName(hyphaName, xclName string) string { + switch { + case strings.HasPrefix(xclName, "./"): + return canonicalName(path.Join(hyphaName, strings.TrimPrefix(xclName, "./"))) + case strings.HasPrefix(xclName, "../"): + return canonicalName(path.Join(path.Dir(hyphaName), strings.TrimPrefix(xclName, "../"))) + default: + return canonicalName(xclName) + } +} + +// At this point: +// selector ::= id +// | from ".." +// | from ".." to +// | ".." to +// If it is not, return (xclError, xclError). +func parseSelector(selector string) (from, to int) { + if selector == "" { + return 0, 0 + } + if strings.Contains(selector, "..") { + parts := strings.Split(selector, "..") + + var ( + fromStr = strings.TrimSpace(parts[0]) + from, fromErr = strconv.Atoi(fromStr) + toStr = strings.TrimSpace(parts[1]) + to, toErr = strconv.Atoi(toStr) + ) + if fromStr == "" && toStr == "" { + return 0, 0 + } + if fromErr == nil || toErr == nil { + return from, to + } + } else if id, err := strconv.Atoi(selector); err == nil { + return id, id + } + return xclError, xclError +} diff --git a/gemtext/xclusion_test.go b/gemtext/xclusion_test.go new file mode 100644 index 0000000..a1531a9 --- /dev/null +++ b/gemtext/xclusion_test.go @@ -0,0 +1,22 @@ +package gemtext + +import ( + "testing" +) + +func TestParseTransclusion(t *testing.T) { + check := func(line string, expectedXclusion Transclusion) { + if xcl := parseTransclusion(line); xcl != expectedXclusion { + t.Error(line, "; got:", xcl, "wanted:", expectedXclusion) + } + } + check("<= ", Transclusion{"", -9, -9}) + check("<=hypha", Transclusion{"hypha", 0, 0}) + check("<= hypha\t", Transclusion{"hypha", 0, 0}) + check("<= hypha :", Transclusion{"hypha", 0, 0}) + check("<= hypha : ..", Transclusion{"hypha", 0, 0}) + check("<= hypha : 3", Transclusion{"hypha", 3, 3}) + check("<= hypha : 3..", Transclusion{"hypha", 3, 0}) + check("<= hypha : ..3", Transclusion{"hypha", 0, 3}) + check("<= hypha : 3..4", Transclusion{"hypha", 3, 4}) +} diff --git a/go.mod b/go.mod index aa23e6d..d4ae430 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,6 @@ module github.com/bouncepaw/mycorrhiza go 1.14 require ( - github.com/gorilla/mux v1.7.4 - mvdan.cc/gogrep v0.0.0-20200420132841-24e8804e5b3c // indirect + golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect + golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6 // indirect ) - -require ( - github.com/m4tty/cajun v0.0.0-20150303030909-35de273cc87b - github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect - gopkg.in/russross/blackfriday.v2 v2.0.1 -) - -replace gopkg.in/russross/blackfriday.v2 => github.com/russross/blackfriday/v2 v2.0.1 diff --git a/go.sum b/go.sum index 7df91cf..12c582a 100644 --- a/go.sum +++ b/go.sum @@ -1,35 +1,25 @@ -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gomarkdown/markdown v0.0.0-20200609195525-3f9352745725 h1:X6sZdr+t2E2jwajTy/FfXbmAKPFTYxEq9hiFgzMiuPQ= -github.com/gomarkdown/markdown v0.0.0-20200609195525-3f9352745725/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU= -github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/m4tty/cajun v0.0.0-20150303030909-35de273cc87b h1:aY3LtSBlkQahoWaPTytHcIFsDbeXFYMc4noRQ/N5Q+A= -github.com/m4tty/cajun v0.0.0-20150303030909-35de273cc87b/go.mod h1:zFXkL7I5vIwKg4dxEA9025SLdIHu9qFX/cYTdUcusHc= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20191223235410-3721262b3e7c h1:PeFrxQ8YTAKg53UR8aP/nxa82lQYIdb+pd1bfg3dBDM= -golang.org/x/tools v0.0.0-20191223235410-3721262b3e7c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6 h1:qKpj8TpV+LEhel7H/fR788J+KvhWZ3o3V6N2fU/iuLU= +golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= -gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -mvdan.cc/gogrep v0.0.0-20200420132841-24e8804e5b3c h1:bz7/KkVXLQ6AWDoNX/hXfcAcNbLwQVAKNGt2I5vZKEE= -mvdan.cc/gogrep v0.0.0-20200420132841-24e8804e5b3c/go.mod h1:LBbI8cEsbrMdWjW4Lcs806EWonhTiZbaBCCbsalF+6c= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/handlers.go b/handlers.go deleted file mode 100644 index 3580eff..0000000 --- a/handlers.go +++ /dev/null @@ -1,68 +0,0 @@ -package main - -import ( - "log" - "net/http" - - "github.com/bouncepaw/mycorrhiza/fs" - "github.com/bouncepaw/mycorrhiza/render" - "github.com/gorilla/mux" -) - -// There are handlers below. See main() for their usage. - -// Boilerplate code present in many handlers. Good to have it. -func HandlerBase(w http.ResponseWriter, rq *http.Request) *fs.Hypha { - vars := mux.Vars(rq) - return fs.Hs.OpenFromMap(vars).OnRevision(RevInMap(vars)) -} - -func HandlerRaw(w http.ResponseWriter, rq *http.Request) { - log.Println("?action=raw") - HandlerBase(w, rq).ActionRaw(w).LogSuccMaybe("Serving raw text") -} - -func HandlerBinary(w http.ResponseWriter, rq *http.Request) { - log.Println("?action=binary") - HandlerBase(w, rq).ActionBinary(w).LogSuccMaybe("Serving binary data") -} - -func HandlerZen(w http.ResponseWriter, rq *http.Request) { - log.Println("?action=zen") - HandlerBase(w, rq).ActionZen(w).LogSuccMaybe("Rendering zen") -} - -func HandlerView(w http.ResponseWriter, rq *http.Request) { - log.Println("?action=view") - HandlerBase(w, rq). - ActionView(w, render.HyphaPage, render.Hypha404). - LogSuccMaybe("Rendering hypha view") -} - -func HandlerEdit(w http.ResponseWriter, rq *http.Request) { - vars := mux.Vars(rq) - h := fs.Hs.OpenFromMap(vars).OnRevision("0") - w.Header().Set("Content-Type", "text/html;charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(render.HyphaEdit(h)) -} - -func HandlerUpdate(w http.ResponseWriter, rq *http.Request) { - vars := mux.Vars(rq) - log.Println("Attempt to update hypha", vars["hypha"]) - h := fs.Hs. - OpenFromMap(vars). - CreateDirIfNeeded(). - AddRevisionFromHttpData(rq). - WriteTextFileFromHttpData(rq). - WriteBinaryFileFromHttpData(rq). - SaveJson(). - Store(). - LogSuccMaybe("Saved changes") - - if !h.Invalid { - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.Write(render.HyphaUpdateOk(h)) - } -} diff --git a/http_mutators.go b/http_mutators.go new file mode 100644 index 0000000..ca8cdd1 --- /dev/null +++ b/http_mutators.go @@ -0,0 +1,151 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "path/filepath" +) + +func init() { + http.HandleFunc("/upload-binary/", handlerUploadBinary) + http.HandleFunc("/upload-text/", handlerUploadText) + http.HandleFunc("/edit/", handlerEdit) +} + +// handlerEdit shows the edit form. It doesn't edit anything actually. +func handlerEdit(w http.ResponseWriter, rq *http.Request) { + log.Println(rq.URL) + var ( + hyphaName = HyphaNameFromRq(rq, "edit") + hyphaData, isOld = HyphaStorage[hyphaName] + warning string + textAreaFill string + err error + ) + if isOld { + textAreaFill, err = FetchTextPart(hyphaData) + if err != nil { + log.Println(err) + HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", + "Could not fetch text data") + return + } + } else { + warning = `

      You are creating a new hypha.

      ` + } + form := fmt.Sprintf(` +
      +

      Edit %[1]s

      + %[3]s +
      + +
      + + Cancel +
      +
      +`, hyphaName, textAreaFill, warning) + + w.Header().Set("Content-Type", "text/html;charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write([]byte(base( + "Edit "+hyphaName, form))) +} + +// handlerUploadText uploads a new text part for the hypha. +func handlerUploadText(w http.ResponseWriter, rq *http.Request) { + log.Println(rq.URL) + var ( + hyphaName = HyphaNameFromRq(rq, "upload-text") + hyphaData, isOld = HyphaStorage[hyphaName] + textData = rq.PostFormValue("text") + textDataBytes = []byte(textData) + fullPath = filepath.Join(WikiDir, hyphaName+"&.gmi") + ) + if textData == "" { + HttpErr(w, http.StatusBadRequest, hyphaName, "Error", + "No text data passed") + return + } + // For some reason, only 0777 works. Why? + if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil { + log.Println(err) + } + if err := ioutil.WriteFile(fullPath, textDataBytes, 0644); err != nil { + log.Println(err) + HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", + fmt.Sprintf("Failed to write %d bytes to %s", + len(textDataBytes), fullPath)) + return + } + if !isOld { + HyphaStorage[hyphaName] = &HyphaData{ + textType: TextGemini, + textPath: fullPath, + } + } else { + hyphaData.textType = TextGemini + hyphaData.textPath = fullPath + } + http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) +} + +// handlerUploadBinary uploads a new binary part for the hypha. +func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { + log.Println(rq.URL) + hyphaName := HyphaNameFromRq(rq, "upload-binary") + rq.ParseMultipartForm(10 << 20) + // Read file + file, handler, err := rq.FormFile("binary") + if file != nil { + defer file.Close() + } + // If file is not passed: + if err != nil { + HttpErr(w, http.StatusBadRequest, hyphaName, "Error", + "No binary data passed") + return + } + // If file is passed: + var ( + hyphaData, isOld = HyphaStorage[hyphaName] + mimeType = MimeToBinaryType(handler.Header.Get("Content-Type")) + ext = mimeType.Extension() + fullPath = filepath.Join(WikiDir, hyphaName+"&"+ext) + ) + + data, err := ioutil.ReadAll(file) + if err != nil { + HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", + "Could not read passed data") + return + } + if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil { + log.Println(err) + } + if err = ioutil.WriteFile(fullPath, data, 0644); err != nil { + HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", + "Could not save passed data") + return + } + if !isOld { + HyphaStorage[hyphaName] = &HyphaData{ + binaryPath: fullPath, + binaryType: mimeType, + } + } else { + if hyphaData.binaryPath != fullPath { + if err := os.Remove(hyphaData.binaryPath); err != nil { + log.Println(err) + } + } + hyphaData.binaryPath = fullPath + hyphaData.binaryType = mimeType + } + log.Println("Written", len(data), "of binary data for", hyphaName, "to path", fullPath) + http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) +} diff --git a/http_readers.go b/http_readers.go new file mode 100644 index 0000000..f00673d --- /dev/null +++ b/http_readers.go @@ -0,0 +1,86 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + + "github.com/bouncepaw/mycorrhiza/gemtext" +) + +func init() { + http.HandleFunc("/page/", handlerPage) + http.HandleFunc("/text/", handlerText) + http.HandleFunc("/binary/", handlerBinary) +} + +// handlerText serves raw source text of the hypha. +func handlerText(w http.ResponseWriter, rq *http.Request) { + log.Println(rq.URL) + hyphaName := HyphaNameFromRq(rq, "text") + if data, ok := HyphaStorage[hyphaName]; ok { + log.Println("Serving", data.textPath) + w.Header().Set("Content-Type", data.textType.Mime()) + http.ServeFile(w, rq, data.textPath) + } +} + +// handlerBinary serves binary part of the hypha. +func handlerBinary(w http.ResponseWriter, rq *http.Request) { + log.Println(rq.URL) + hyphaName := HyphaNameFromRq(rq, "binary") + if data, ok := HyphaStorage[hyphaName]; ok { + log.Println("Serving", data.binaryPath) + w.Header().Set("Content-Type", data.binaryType.Mime()) + http.ServeFile(w, rq, data.binaryPath) + } +} + +// handlerPage is the main hypha action that displays the hypha and the binary upload form along with some navigation. +func handlerPage(w http.ResponseWriter, rq *http.Request) { + log.Println(rq.URL) + var ( + hyphaName = HyphaNameFromRq(rq, "page") + contents = fmt.Sprintf(`

      This hypha has no text. Why not create it?

      `, hyphaName) + data, hyphaExists = HyphaStorage[hyphaName] + ) + if hyphaExists { + fileContentsT, errT := ioutil.ReadFile(data.textPath) + _, errB := os.Stat(data.binaryPath) + if errT == nil { + contents = gemtext.ToHtml(hyphaName, string(fileContentsT)) + } + if !os.IsNotExist(errB) { + contents = binaryHtmlBlock(hyphaName, data) + contents + } + } + form := fmt.Sprintf(` +
      + +
      + %[2]s + %[3]s +
      +
      +
      + +
      + + +
      +
      +`, hyphaName, naviTitle(hyphaName), contents) + w.Header().Set("Content-Type", "text/html;charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write([]byte(base(hyphaName, form))) +} diff --git a/hypha.go b/hypha.go new file mode 100644 index 0000000..d3be116 --- /dev/null +++ b/hypha.go @@ -0,0 +1,124 @@ +package main + +import ( + "errors" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + + "github.com/bouncepaw/mycorrhiza/gemtext" +) + +func init() { + gemtext.HyphaExists = func(hyphaName string) bool { + _, hyphaExists := HyphaStorage[hyphaName] + return hyphaExists + } + gemtext.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) { + if hyphaData, ok := HyphaStorage[hyphaName]; ok { + rawText, err = FetchTextPart(hyphaData) + if hyphaData.binaryPath != "" { + binaryBlock = binaryHtmlBlock(hyphaName, hyphaData) + } + } else { + err = errors.New("Hypha " + hyphaName + " does not exist") + } + return + } +} + +// HyphaData represents a hypha's meta information: binary and text parts rooted paths and content types. +type HyphaData struct { + textPath string + textType TextType + binaryPath string + binaryType BinaryType +} + +// binaryHtmlBlock creates an html block for binary part of the hypha. +func binaryHtmlBlock(hyphaName string, d *HyphaData) string { + switch d.binaryType { + case BinaryJpeg, BinaryGif, BinaryPng, BinaryWebp, BinarySvg, BinaryIco: + return fmt.Sprintf(` +
      + +
      `, hyphaName) + case BinaryOgg, BinaryWebm, BinaryMp4: + return fmt.Sprintf(` +
      + + `, hyphaName) + case BinaryMp3: + return fmt.Sprintf(` +
      + + `, hyphaName) + default: + return fmt.Sprintf(` +
      +

      This hypha's media cannot be rendered. Access it directly

      +
      + `, hyphaName) + } +} + +// Index finds all hypha files in the full `path` and saves them to HyphaStorage. This function is recursive. +func Index(path string) { + nodes, err := ioutil.ReadDir(path) + if err != nil { + log.Fatal(err) + } + + for _, node := range nodes { + // If this hypha looks like it can be a hypha path, go deeper + if node.IsDir() && isCanonicalName(node.Name()) { + Index(filepath.Join(path, node.Name())) + } + + hyphaPartFilename := filepath.Join(path, node.Name()) + skip, hyphaName, isText, mimeId := DataFromFilename(hyphaPartFilename) + if !skip { + var ( + hyphaData *HyphaData + ok bool + ) + if hyphaData, ok = HyphaStorage[hyphaName]; !ok { + hyphaData = &HyphaData{} + HyphaStorage[hyphaName] = hyphaData + } + if isText { + hyphaData.textPath = hyphaPartFilename + hyphaData.textType = TextType(mimeId) + } else { + hyphaData.binaryPath = hyphaPartFilename + hyphaData.binaryType = BinaryType(mimeId) + } + } + } +} + +// FetchTextPart tries to read text file in the `d`. If there is no file, empty string is returned. +func FetchTextPart(d *HyphaData) (string, error) { + if d.textPath == "" { + return "", nil + } + _, err := os.Stat(d.textPath) + if os.IsNotExist(err) { + return "", nil + } else if err != nil { + return "", err + } + text, err := ioutil.ReadFile(d.textPath) + if err != nil { + return "", err + } + return string(text), nil +} diff --git a/main.go b/main.go index d2eb2b1..7d08e0b 100644 --- a/main.go +++ b/main.go @@ -1,99 +1,132 @@ package main import ( + "fmt" "log" "net/http" "os" "path/filepath" - "time" - - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/fs" - "github.com/bouncepaw/mycorrhiza/mycelium" - "github.com/gorilla/mux" + "regexp" + "strings" ) -// RevInMap finds value of `rev` (the one from URL queries like) in the passed map that is usually got from `mux.Vars(*http.Request)`. -// If there is no `rev`, return "0". -func RevInMap(m map[string]string) string { - if id, ok := m["rev"]; ok { - return id - } - return "0" +// WikiDir is a rooted path to the wiki storage directory. +var WikiDir string + +// HyphaPattern is a pattern which all hyphae must match. Not used currently. +var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%]+`) + +// HyphaStorage is a mapping between canonical hypha names and their meta information. +var HyphaStorage = make(map[string]*HyphaData) + +// HttpErr is used by many handlers to signal errors in a compact way. +func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) { + log.Println(errMsg, "for", name) + w.Header().Set("Content-Type", "text/html;charset=utf-8") + w.WriteHeader(status) + fmt.Fprint(w, base(title, fmt.Sprintf( + `

      %s. Go back to the hypha.

      `, + errMsg, name))) } -func IdempotentRouterBoiler(router *mux.Router, action string, handler func(w http.ResponseWriter, rq *http.Request)) { - router. - Queries("action", action, "rev", cfg.RevQuery). - Path(cfg.MyceliumUrl + cfg.HyphaUrl). - HandlerFunc(handler) - router. - Queries("action", action). - Path(cfg.MyceliumUrl + cfg.HyphaUrl). - HandlerFunc(handler) - router. - Queries("action", action, "rev", cfg.RevQuery). - Path(cfg.HyphaUrl). - HandlerFunc(handler) - router. - Queries("action", action). - Path(cfg.HyphaUrl). - HandlerFunc(handler) +// shorterPath is used by handlerList to display shorter path to the files. It simply strips WikiDir. +func shorterPath(fullPath string) string { + tmp := strings.TrimPrefix(fullPath, WikiDir) + if tmp == "" { + return "" + } + return tmp[1:] +} + +// Show all hyphae +func handlerList(w http.ResponseWriter, rq *http.Request) { + log.Println(rq.URL) + w.Header().Set("Content-Type", "text/html;charset=utf-8") + w.WriteHeader(http.StatusOK) + buf := ` +

      List of pages

      + + + + + + + + + + + ` + for name, data := range HyphaStorage { + buf += fmt.Sprintf(` + + + + + + + `, + name, name, + shorterPath(data.textPath), data.textType, + shorterPath(data.binaryPath), data.binaryType, + ) + } + buf += ` + +
      NameText pathText typeBinary pathBinary type
      %s%s%d%s%d
      +` + w.Write([]byte(base("List of pages", buf))) +} + +// This part is present in all html documents. +func base(title, body string) string { + return fmt.Sprintf(` + + + + + + %s + + + %s + + +`, title, body) +} + +// Reindex all hyphae by checking the wiki storage directory anew. +func handlerReindex(w http.ResponseWriter, rq *http.Request) { + log.Println(rq.URL) + HyphaStorage = make(map[string]*HyphaData) + log.Println("Wiki storage directory is", WikiDir) + log.Println("Start indexing hyphae...") + Index(WikiDir) + log.Println("Indexed", len(HyphaStorage), "hyphae") } func main() { - if len(os.Args) == 1 { - panic("Expected a root wiki pages directory") - } - wikiDir, err := filepath.Abs(os.Args[1]) + log.Println("Running MycorrhizaWiki β") + + var err error + WikiDir, err = filepath.Abs(os.Args[1]) if err != nil { - panic(err) + log.Fatal(err) } + log.Println("Wiki storage directory is", WikiDir) + log.Println("Start indexing hyphae...") + Index(WikiDir) + log.Println("Indexed", len(HyphaStorage), "hyphae") - log.Println("Welcome to MycorrhizaWiki α") - cfg.InitConfig(wikiDir) - log.Println("Indexing hyphae...") - mycelium.Init() - fs.InitStorage() - - // Start server code. See handlers.go for handlers' implementations. - r := mux.NewRouter() - - r.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) { - http.ServeFile(w, rq, filepath.Join(cfg.WikiDir, "favicon.ico")) + http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(WikiDir+"/static")))) + // See http_readers.go for /page/, /text/, /binary/. + // See http_mutators.go for /upload-binary/, /upload-text/, /edit/. + http.HandleFunc("/list", handlerList) + http.HandleFunc("/reindex", handlerReindex) + http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) { + http.ServeFile(w, rq, WikiDir+"/static/favicon.ico") }) - - IdempotentRouterBoiler(r, "binary", HandlerBinary) - IdempotentRouterBoiler(r, "raw", HandlerRaw) - IdempotentRouterBoiler(r, "zen", HandlerZen) - IdempotentRouterBoiler(r, "view", HandlerView) - - r.Queries("action", "edit").Path(cfg.MyceliumUrl + cfg.HyphaUrl). - HandlerFunc(HandlerEdit) - r.Queries("action", "edit").Path(cfg.HyphaUrl). - HandlerFunc(HandlerEdit) - - r.Queries("action", "update").Path(cfg.MyceliumUrl + cfg.HyphaUrl). - Methods("POST").HandlerFunc(HandlerUpdate) - r.Queries("action", "update").Path(cfg.HyphaUrl). - Methods("POST").HandlerFunc(HandlerUpdate) - - r.HandleFunc(cfg.MyceliumUrl+cfg.HyphaUrl, HandlerView) - r.HandleFunc(cfg.HyphaUrl, HandlerView) - - r.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) { - http.Redirect(w, rq, cfg.HomePage, http.StatusSeeOther) + http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) { + http.Redirect(w, rq, "/page/home", http.StatusSeeOther) }) - - http.Handle("/", r) - - srv := &http.Server{ - Handler: r, - Addr: cfg.Address, - // Good practice: enforce timeouts for servers you create! - WriteTimeout: 15 * time.Second, - ReadTimeout: 15 * time.Second, - } - - log.Fatal(srv.ListenAndServe()) + log.Fatal(http.ListenAndServe("0.0.0.0:1737", nil)) } diff --git a/metarrhiza b/metarrhiza new file mode 160000 index 0000000..a22fcac --- /dev/null +++ b/metarrhiza @@ -0,0 +1 @@ +Subproject commit a22fcac89f10ad1e1db77d765788dfd8966cbb36 diff --git a/mime.go b/mime.go new file mode 100644 index 0000000..bdc69c2 --- /dev/null +++ b/mime.go @@ -0,0 +1,108 @@ +package main + +import ( + "path/filepath" + "strings" +) + +// TextType is content type of text part of a hypha. +type TextType int + +const ( + // TextPlain is default text content type. + TextPlain TextType = iota + // TextGemini is content type for MycorrhizaWiki's dialect of gemtext. + TextGemini +) + +// Mime returns mime type representation of `t`. +func (t TextType) Mime() string { + return [...]string{"text/plain", "text/gemini"}[t] +} + +// Extension returns extension (with dot) to be used for files with content type `t`. +func (t TextType) Extension() string { + return [...]string{".txt", ".gmi"}[t] +} + +// BinaryType is content type of binary part of a hypha. +type BinaryType int + +// Supported binary content types +const ( + // BinaryOctet is default binary content type. + BinaryOctet BinaryType = iota + BinaryJpeg + BinaryGif + BinaryPng + BinaryWebp + BinarySvg + BinaryIco + BinaryOgg + BinaryWebm + BinaryMp3 + BinaryMp4 +) + +var binaryMimes = [...]string{ + "application/octet-stream", + "image/jpeg", "image/gif", "image/png", "image/webp", + "image/svg+xml", "image/x-icon", + "application/ogg", "video/webm", "audio/mp3", "video/mp4", +} + +// Mime returns mime type representation of `t`. +func (t BinaryType) Mime() string { + return binaryMimes[t] +} + +var binaryExtensions = [...]string{ + ".bin", ".jpg", ".gif", ".png", ".webp", ".svg", ".ico", + ".ogg", ".webm", ".mp3", ".mp4", +} + +// Extension returns extension (with dot) to be used for files with content type `t`. +func (t BinaryType) Extension() string { + return binaryExtensions[t] +} + +// MimeToBinaryType converts mime type to BinaryType. If the mime type is not supported, BinaryOctet is returned as a fallback type. +func MimeToBinaryType(mime string) BinaryType { + for i, binaryMime := range binaryMimes { + if binaryMime == mime { + return BinaryType(i) + } + } + return BinaryOctet +} + +// DataFromFilename fetches all meta information from hypha content file with path `fullPath`. If it is not a content file, `skip` is true, and you are expected to ignore this file when indexing hyphae. `name` is name of the hypha to which this file relates. `isText` is true when the content file is text, false when is binary. `mimeId` is an integer representation of content type. Cast it to TextType if `isText == true`, cast it to BinaryType if `isText == false`. +func DataFromFilename(fullPath string) (skip bool, name string, isText bool, mimeId int) { + shortPath := strings.TrimPrefix(fullPath, WikiDir)[1:] + // Special files start with & + // &. is used in normal hypha part names + if shortPath[0] == '&' || strings.LastIndex(shortPath, "&.") < 0 { + skip = true + return + } + ext := filepath.Ext(shortPath) + name = strings.TrimSuffix(shortPath, "&"+ext) + isText, mimeId = mimeData(ext) + return +} + +// mimeData determines what content type file has judging by its `ext`ension. `itText` and `mimeId` are the same as in DataFromFilename. +func mimeData(ext string) (isText bool, mimeId int) { + switch ext { + case ".txt": + return true, int(TextPlain) + case ".gmi": + return true, int(TextGemini) + } + for i, binExt := range binaryExtensions { + if ext == binExt { + return false, i + } + } + return false, 0 +} diff --git a/mime_test.go b/mime_test.go new file mode 100644 index 0000000..05b2d08 --- /dev/null +++ b/mime_test.go @@ -0,0 +1,19 @@ +package main + +import ( + "testing" +) + +func TestMimeData(t *testing.T) { + check := func(ext string, expectedIsText bool, expectedMimeId int) { + isText, mimeId := mimeData(ext) + if isText != expectedIsText || mimeId != expectedMimeId { + t.Error(ext, isText, mimeId) + } + } + check(".txt", true, int(TextPlain)) + check(".gmi", true, int(TextGemini)) + check(".bin", false, int(BinaryOctet)) + check(".jpg", false, int(BinaryJpeg)) + check(".bin", false, int(BinaryOctet)) +} diff --git a/mycelium/mycelium.go b/mycelium/mycelium.go deleted file mode 100644 index 94d9e8b..0000000 --- a/mycelium/mycelium.go +++ /dev/null @@ -1,107 +0,0 @@ -package mycelium - -import ( - "io/ioutil" - "log" - "strings" - - "github.com/bouncepaw/mycorrhiza/cfg" -) - -var ( - MainMycelium string - SystemMycelium string -) - -func gatherDirNames(path string) map[string]struct{} { - res := make(map[string]struct{}) - nodes, err := ioutil.ReadDir(path) - if err != nil { - log.Fatal(err) - } - for _, node := range nodes { - if node.IsDir() { - res[node.Name()] = struct{}{} - } - } - return res -} - -// Add values to the set. If a value is already there, return false. -func addInUniqueSet(set map[string]struct{}, names []string) bool { - ok := true - for _, name := range names { - if _, present := set[name]; present { - ok = false - } - set[name] = struct{}{} - } - return ok -} - -func Init() { - var ( - // Used to check if there are no duplicates - foundNames = make(map[string]struct{}) - dirs = gatherDirNames(cfg.WikiDir) - mainPresent bool - systemPresent bool - ) - for _, mycelium := range cfg.Mycelia { - switch mycelium.Type { - case "main": - mainPresent = true - MainMycelium = mycelium.Names[0] - case "system": - systemPresent = true - SystemMycelium = mycelium.Names[0] - } - // Check if there is a dir corresponding to the mycelium - if _, ok := dirs[mycelium.Names[0]]; !ok { - log.Fatal("No directory found for mycelium " + mycelium.Names[0]) - } - // Confirm uniqueness of names - if ok := addInUniqueSet(foundNames, mycelium.Names); !ok { - log.Fatal("At least one name was used more than once for mycelia") - } - } - if !mainPresent { - log.Fatal("No `main` mycelium given in config.json") - } - if !systemPresent { - log.Fatal("No `system` mycelium given in config.json") - } - log.Println("Mycelial dirs are present") -} - -func NameWithMyceliumInMap(m map[string]string) (res string) { - var ( - hyphaName, okH = m["hypha"] - mycelName, okM = m["mycelium"] - ) - log.Println(m) - if !okH { - // It will result in an error when trying to open a hypha with such name - return ":::" - } - if okM { - res = canonicalMycelium(mycelName) - } else { - res = MainMycelium - } - return res + "/" + hyphaName -} - -func canonicalMycelium(name string) string { - log.Println("Determining canonical mycelial name for", name) - name = strings.ToLower(name) - for _, mycel := range cfg.Mycelia { - for _, mycelName := range mycel.Names { - if mycelName == name { - return mycel.Names[0] - } - } - } - // This is a nonexistent mycelium. Return a name that will trigger an error - return ":error:" -} diff --git a/name.go b/name.go new file mode 100644 index 0000000..741e965 --- /dev/null +++ b/name.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "net/http" + "strings" +) + +// isCanonicalName checks if the `name` is canonical. +func isCanonicalName(name string) bool { + return HyphaPattern.MatchString(name) +} + +// CanonicalName makes sure the `name` is canonical. A name is canonical if it is lowercase and all spaces are replaced with underscores. +func CanonicalName(name string) string { + return strings.ToLower(strings.ReplaceAll(name, " ", "_")) +} + +// naviTitle turns `canonicalName` into html string with each hypha path parts higlighted as links. +func naviTitle(canonicalName string) string { + var ( + html = `

      + 🍄` + prevAcc = `/page/` + parts = strings.Split(canonicalName, "/") + ) + for _, part := range parts { + html += fmt.Sprintf(` + / + %s`, + prevAcc+part, + strings.Title(part)) + prevAcc += part + "/" + } + return html + "

      " +} + +// HyphaNameFromRq extracts hypha name from http request. You have to also pass the action which is embedded in the url. For url /page/hypha, the action would be "page". +func HyphaNameFromRq(rq *http.Request, action string) string { + return CanonicalName(strings.TrimPrefix(rq.URL.Path, "/"+action+"/")) +} diff --git a/plugin/lang/en.go b/plugin/lang/en.go deleted file mode 100644 index 049c38b..0000000 --- a/plugin/lang/en.go +++ /dev/null @@ -1,31 +0,0 @@ -package lang - -var EnglishMap = map[string]string{ - "edit hypha title template": "Edit %s at MycorrhizaWiki", - "view hypha title template": "%s at MycorrhizaWiki", - "this site runs myco wiki": `

      This website runs MycorrhizaWiki

      `, - "generic error msg": `Sorry, something went wrong`, - - "edit/text mime type": "Text MIME-type", - "edit/text mime type/tip": "We support text/markdown, text/creole and text/gemini", - - "edit/revision comment": "Revision comment", - "edit/revision comment/tip": "Please make your comment helpful", - "edit/revision comment/new": "Create %s", - "edit/revision comment/old": "Update %s", - - "edit/tags": "Edit tags", - "edit/tags/tip": "Tags are separated by commas, whitespace is ignored", - - "edit/upload file": "Upload file", - "edit/upload file/tip": "Only images are fully supported for now", - - "edit/box": "Edit box", - "edit/box/title": "Edit %s", - "edit/box/help pattern": "Describe %s here", - - "edit/cancel": "Cancel", - - "update ok/title": "Saved %s", - "update ok/msg": "Saved successfully. Go back", -} diff --git a/plugin/parser/creole.go b/plugin/parser/creole.go deleted file mode 100644 index 2b1dd47..0000000 --- a/plugin/parser/creole.go +++ /dev/null @@ -1,11 +0,0 @@ -package parser - -import ( - "github.com/bouncepaw/mycorrhiza/util" - "github.com/m4tty/cajun" -) - -func CreoleToHtml(creole []byte) string { - out, _ := cajun.Transform(string(util.NormalizeEOL(creole))) - return out -} diff --git a/plugin/parser/gemini.go b/plugin/parser/gemini.go deleted file mode 100644 index c3b41a9..0000000 --- a/plugin/parser/gemini.go +++ /dev/null @@ -1,134 +0,0 @@ -package parser - -import ( - "bufio" - "bytes" - "fmt" - "strings" - - "github.com/bouncepaw/mycorrhiza/util" -) - -const ( - linkToken = "=>" - headerToken = "#" - quoteToken = ">" - preformattedToken = "```" - listItemToken = "*" -) - -var preState bool -var listState bool - -func GeminiToHtml(gemini []byte) string { - lines, _ := StringToLines(string(util.NormalizeEOL(gemini))) - var html []string - - for _, line := range lines { - html = append(html, geminiLineToHtml(line)) - } - - buffer := bytes.Buffer{} - for _, line := range html { - buffer.WriteString(line) - } - return buffer.String() -} - -func geminiLineToHtml(line string) (res string) { - arr := strings.Fields(line) - token := checkLineType(arr) - - switch token { - case headerToken: - level, content := makeOutHeader(arr) - res = fmt.Sprintf("%v", level, content, level) - case linkToken: - source, content := makeOutLink(arr[1:]) - res = fmt.Sprintf(`%v`, source, content) - case quoteToken: - res = "
      " + LinesToString(arr[1:], " ") + "
      " - case preformattedToken: - preState = true - res = fmt.Sprintf(`
      `, LinesToString(arr[1:], " "))
      -	case "pre/empty":
      -		res = "\n"
      -	case "pre/text":
      -		res = line + "\n"
      -	case "pre/end":
      -		preState = false
      -		res = "
      " - case "list/begin": - res = "
      • " + LinesToString(arr[1:], " ") + "
      • " - case listItemToken: - res = "
      • " + LinesToString(arr[1:], " ") + "
      • " - case "list/end": - listState = false - res = "
      " + geminiLineToHtml(line) - case "linebreak": - res = "
      " - default: - res = "

      " + line + "

      " - } - return -} - -func makeOutLink(arr []string) (source, content string) { - switch len(arr) { - case 0: - return "", "" - case 1: - return arr[0], arr[0] - default: - return arr[0], LinesToString(arr[1:], " ") - } -} - -func makeOutHeader(arr []string) (level int, content string) { - level = len(arr[0]) - content = LinesToString(arr[1:], " ") - return -} - -func checkLineType(arr []string) (res string) { - isEmpty := len(arr) == 0 - if preState { - if isEmpty { - res = "pre/empty" - } else if arr[0] == preformattedToken { - res = "pre/end" - } else { - res = "pre/text" - } - } else if listState { - if arr[0] == listItemToken { - res = listItemToken - } else { - res = "list/end" - } - } else if isEmpty { - res = "linebreak" - } else if arr[0][0] == headerToken[0] { - res = headerToken - } else { - return arr[0] - } - return -} - -func StringToLines(s string) (lines []string, err error) { - scanner := bufio.NewScanner(strings.NewReader(s)) - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } - err = scanner.Err() - return -} - -func LinesToString(lines []string, separator string) string { - buffer := bytes.Buffer{} - for _, line := range lines { - buffer.WriteString(line + separator) - } - return buffer.String() -} diff --git a/plugin/parser/markdown.go b/plugin/parser/markdown.go deleted file mode 100644 index faeb5d0..0000000 --- a/plugin/parser/markdown.go +++ /dev/null @@ -1,10 +0,0 @@ -package parser - -import ( - "github.com/bouncepaw/mycorrhiza/util" - "gopkg.in/russross/blackfriday.v2" -) - -func MarkdownToHtml(md []byte) string { - return string(blackfriday.Run(util.NormalizeEOL(md))) -} diff --git a/plugin/plugin.go b/plugin/plugin.go deleted file mode 100644 index 04cd9e2..0000000 --- a/plugin/plugin.go +++ /dev/null @@ -1,20 +0,0 @@ -package plugin - -import ( - "fmt" - "github.com/bouncepaw/mycorrhiza/plugin/parser" -) - -func ParserForMime(mime string) func([]byte) string { - parsers := map[string]func([]byte) string{ - "text/markdown": parser.MarkdownToHtml, - "text/creole": parser.CreoleToHtml, - "text/gemini": parser.GeminiToHtml, - } - if parserFunc, ok := parsers[mime]; ok { - return parserFunc - } - return func(contents []byte) string { - return fmt.Sprintf(`
      %s
      `, contents) - } -} diff --git a/render/render.go b/render/render.go deleted file mode 100644 index 6ac8a58..0000000 --- a/render/render.go +++ /dev/null @@ -1,149 +0,0 @@ -package render - -import ( - "bytes" - "fmt" - "path" - "text/template" - - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/fs" - "github.com/bouncepaw/mycorrhiza/mycelium" -) - -func titleTemplateView(name string) string { - return fmt.Sprintf(cfg.Locale["view hypha title template"], name) -} - -// HyphaEdit renders hypha editor. -func HyphaEdit(h *fs.Hypha) []byte { // - hyphaData := map[string]interface{}{ - "Name": h.FullName, - "Tags": h.TagsJoined(), - "TextMime": h.TextMime(), - "Text": h.TextContent(), - "Locale": cfg.Locale, - } - return layout("edit/index"). - withMap(hyphaData). - wrapInBase(map[string]string{ - "Title": fmt.Sprintf(cfg.Locale["edit hypha title template"], h.FullName), - }) -} - -// HyphaUpdateOk is used to inform that update was successful. -func HyphaUpdateOk(h *fs.Hypha) []byte { // - return layout("update_ok"). - withMap(map[string]interface{}{ - "Name": h.FullName, - "Locale": cfg.Locale, - }). - Bytes() -} - -// Hypha404 renders 404 page for nonexistent page. -func Hypha404(name, _ string) []byte { - return layout("view/404"). - withMap(map[string]interface{}{ - "PageTitle": name, - "Tree": hyphaTree(name), - "Locale": cfg.Locale, - }). - wrapInBase(map[string]string{ - "Title": titleTemplateView(name), - }) -} - -// HyphaPage renders hypha viewer. -func HyphaPage(name, content string) []byte { - return layout("view/index"). - withMap(map[string]interface{}{ - "Content": content, - "Tree": hyphaTree(name), - "Locale": cfg.Locale, - }). - wrapInBase(map[string]string{ - "Title": titleTemplateView(name), - }) -} - -// wrapInBase is used to wrap layouts in things that are present on all pages. -func (lyt *Layout) wrapInBase(keys map[string]string) []byte { - if lyt.invalid { - return lyt.Bytes() - } - page := map[string]interface{}{ - "Content": lyt.String(), - "Locale": cfg.Locale, - "Title": cfg.SiteTitle, - "SiteTitle": cfg.SiteTitle, - } - for key, val := range keys { - page[key] = val - } - return layout("base").withMap(page).Bytes() -} - -func hyphaTree(name string) string { - return fs.Hs.GetTree(name, true).AsHtml() -} - -type Layout struct { - tmpl *template.Template - buf *bytes.Buffer - invalid bool - err error -} - -func layout(name string) *Layout { - lytName := path.Join("theme", cfg.Theme, name+".html") - h := fs.Hs.OpenFromMap(map[string]string{ - "mycelium": mycelium.SystemMycelium, - "hypha": lytName, - "rev": "0", - }) - if h.Invalid { - return &Layout{nil, nil, true, h.Err} - } - tmpl, err := template.ParseFiles(h.TextPath()) - if err != nil { - return &Layout{nil, nil, true, err} - } - return &Layout{tmpl, new(bytes.Buffer), false, nil} -} - -func (lyt *Layout) withString(data string) *Layout { - if lyt.invalid { - return lyt - } - if err := lyt.tmpl.Execute(lyt.buf, data); err != nil { - lyt.invalid = true - lyt.err = err - } - return lyt -} - -func (lyt *Layout) withMap(data map[string]interface{}) *Layout { - if lyt.invalid { - return lyt - } - if err := lyt.tmpl.Execute(lyt.buf, data); err != nil { - lyt.invalid = true - lyt.err = err - } - return lyt -} - -func (lyt *Layout) Bytes() []byte { - if lyt.invalid { - return []byte(lyt.err.Error()) - } - return lyt.buf.Bytes() -} - -func (lyt *Layout) String() string { - if lyt.invalid { - return lyt.err.Error() - } - return lyt.buf.String() -} diff --git a/util/url.go b/util/url.go deleted file mode 100644 index b7103ce..0000000 --- a/util/url.go +++ /dev/null @@ -1,44 +0,0 @@ -/* This file implements things defined by Wikilink RFC. See :main/help/wikilink - */ -package util - -import ( - "path" - "regexp" - "strings" -) - -// `name` must be non-empty. -func sections(name string) (mycel, hyphaName string) { - mycelRe := regexp.MustCompile(`^:.*/`) - loc := mycelRe.FindIndex([]byte(name)) - if loc != nil { // if has mycel - mycel = name[:loc[1]] - name = name[loc[1]:] - } - return mycel, name -} - -// Wikilink processes `link` as defined by :main/help/wikilink assuming that `atHypha` is current hypha name. -func Wikilink(link, atHypha string) string { - mycel, hyphaName := sections(atHypha) - urlProtocolRe := regexp.MustCompile(`^[a-zA-Z]+:`) - switch { - case strings.HasPrefix(link, "::"): - return "/" + mycel + link[2:] - case strings.HasPrefix(link, ":"): - return "/" + link - case strings.HasPrefix(link, "../") && strings.Count(hyphaName, "/") > 0: - return "/" + path.Dir(atHypha) + "/" + link[3:] - case strings.HasPrefix(link, "../"): - return "/" + mycel + link[3:] - case strings.HasPrefix(link, "/"): - return "/" + atHypha + link - case strings.HasPrefix(link, "./"): - return "/" + atHypha + link[1:] - case urlProtocolRe.MatchString(link): - return link - default: - return "/" + link - } -} diff --git a/util/util.go b/util/util.go deleted file mode 100644 index 545bd37..0000000 --- a/util/util.go +++ /dev/null @@ -1,90 +0,0 @@ -package util - -import ( - "bytes" - "strings" - "unicode" -) - -func addColonPerhaps(name string) string { - if strings.HasPrefix(name, ":") { - return name - } - return ":" + name -} - -func removeColonPerhaps(name string) string { - if strings.HasPrefix(name, ":") { - return name[1:] - } - return name -} - -func UrlToCanonical(name string) string { - return removeColonPerhaps( - strings.ToLower(strings.ReplaceAll(name, " ", "_"))) -} - -func DisplayToCanonical(name string) string { - return removeColonPerhaps( - strings.ToLower(strings.ReplaceAll(name, " ", "_"))) -} - -func CanonicalToDisplay(name string) (res string) { - tmp := strings.Title(name) - var afterPoint bool - for _, ch := range tmp { - if afterPoint { - afterPoint = false - ch = unicode.ToLower(ch) - } - switch ch { - case '.': - afterPoint = true - case '_': - ch = ' ' - } - res += string(ch) - } - return addColonPerhaps(res) -} - -// NormalizeEOL will convert Windows (CRLF) and Mac (CR) EOLs to UNIX (LF) -// Code taken from here: https://github.com/go-gitea/gitea/blob/dc8036dcc680abab52b342d18181a5ee42f40318/modules/util/util.go#L68-L102 -// Gitea has MIT License -// -// We use it because md parser does not handle CRLF correctly. I don't know why, but CRLF appears sometimes. -func NormalizeEOL(input []byte) []byte { - var right, left, pos int - if right = bytes.IndexByte(input, '\r'); right == -1 { - return input - } - length := len(input) - tmp := make([]byte, length) - - // We know that left < length because otherwise right would be -1 from IndexByte. - copy(tmp[pos:pos+right], input[left:left+right]) - pos += right - tmp[pos] = '\n' - left += right + 1 - pos++ - - for left < length { - if input[left] == '\n' { - left++ - } - - right = bytes.IndexByte(input[left:], '\r') - if right == -1 { - copy(tmp[pos:], input[left:]) - pos += length - left - break - } - copy(tmp[pos:pos+right], input[left:left+right]) - pos += right - tmp[pos] = '\n' - left += right + 1 - pos++ - } - return tmp[:pos] -} diff --git a/util/util_test.go b/util/util_test.go deleted file mode 100644 index 6d83e36..0000000 --- a/util/util_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package util - -import ( - "testing" -) - -func TestWikilink(t *testing.T) { - atHypha := ":example/test" - results := map[string]string{ - "foo": "/foo", - "::foo": "/:example/foo", - ":bar/foo": "/:bar/foo", - "/baz": "/:example/test/baz", - "./baz": "/:example/test/baz", - "../qux": "/:example/qux", - "http://example.org": "http://example.org", - "gemini://example.org": "gemini://example.org", - "mailto:me@example.org": "mailto:me@example.org", - } - for link, expect := range results { - if res := Wikilink(link, atHypha); expect != res { - t.Errorf("%s → %s; expected %s", link, res, expect) - } - } -} diff --git a/wiki/README.md b/wiki/README.md deleted file mode 100644 index 46c1a4f..0000000 --- a/wiki/README.md +++ /dev/null @@ -1 +0,0 @@ -This is root wiki directory. diff --git a/wiki/config.json b/wiki/config.json deleted file mode 100644 index 67c2da8..0000000 --- a/wiki/config.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "address": "0.0.0.0:1737", - "theme": "default-light", - "site-title": "🍄 MycorrhizaWiki", - "home-page": "Home", - "binary-limit-mb": 10, - "locale": "en", - "mycelia": [ - { - "names": ["main"], - "type": "main" - }, - { - "names": ["sys", "system"], - "type": "system" - }, - { - "names": ["spec", "special"], - "type": "special" - }, - { - "names": ["user","u"], - "type": "user" - }, - { - "names": ["tag", "t"], - "type": "tag" - } - ] -} diff --git a/wiki/favicon.ico b/wiki/favicon.ico deleted file mode 100644 index 857e55c..0000000 Binary files a/wiki/favicon.ico and /dev/null differ diff --git a/wiki/main/README.md b/wiki/main/README.md deleted file mode 100644 index 2b3b540..0000000 --- a/wiki/main/README.md +++ /dev/null @@ -1 +0,0 @@ -This is directory with hyphae diff --git a/wiki/main/doc/1.markdown b/wiki/main/doc/1.markdown deleted file mode 100644 index 7581886..0000000 --- a/wiki/main/doc/1.markdown +++ /dev/null @@ -1 +0,0 @@ -# Help \ No newline at end of file diff --git a/wiki/main/doc/meta.json b/wiki/main/doc/meta.json deleted file mode 100644 index 354427e..0000000 --- a/wiki/main/doc/meta.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "" - ], - "name": "Help", - "comment": "Update Help", - "author": "", - "time": 1593540573, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "1.markdown", - "binary_name": "" - } - }, - "Invalid": false, - "Err": null -} \ No newline at end of file diff --git a/wiki/main/doc/mycelia/1.markdown b/wiki/main/doc/mycelia/1.markdown deleted file mode 100644 index b86210e..0000000 --- a/wiki/main/doc/mycelia/1.markdown +++ /dev/null @@ -1,85 +0,0 @@ -In MycorrhizaWiki **mycelia** are used to organize [hyphae](hyphae) in related namespaces. - -## Mycelium traits -- Every mycelium has any number (0..∞) of hyphae. -- Every hypha is part of one mycelium. -- Every mycelium has one canonical name and any number of synonyms. -- Every wiki has one main mycelium. -- They are named by the same scheme as hyphae but they have a colon prefix. - -## Mycelium URL -- Address a hypha in a particular mycelium: `/:sys/about`. -- Address subpage of the hypha above: `/:sys/about/more` -- Address a hypha in the main mycelium: `/How/does it work`. -- Address a hypha in the main mycelium explicitly: `/:main/How/does it work`. - -## Mycelium configuration -In your `config.json`, in `"mycelia"` field there is an array of objects. - -``` -{ - ... - "mycelia": [ - { - "names": ["main"], - "type": "main" - }, - { - "names": ["sys", "system"], - "type": "system" - }, - { - "names": ["spec", "special"], - "type": "special" - }, - { - "names": ["user","u"], - "type": "user" - }, - { - "names": ["tag", "t"], - "type": "tag" - } - ] - ... -} -``` - -Each object reprents a mycelium. You can set all their names there. First name in each `"names"` array is a canonical name for the mycelium. - -Field `"type"` sets the mycelium's type. There are such types: - -| **Type** | **Description** | -| `main` | The main mycelium. There must be exactly one such mycelium in a wiki. | -| `system` | Things like scripts, styles and templates go here. There must be exactly one such mycelium in a wiki. | -| `special` | Things like utility hyphae and plugin pages go here. It is optional because there are no hyphae or plugins now. | -| `user` | Userpages. It is optional because there are no users now. | -| `tag` | Pages describing tags. It is optional because there are no tags now. | -| `other` | Mycelia without any additional meaning added by the engine. There can be any number of them. | - -## How are they stored in the filesystem. -For example, `wiki` is your wiki directory and you have configured the mycelia like in the example above. You should have structure like that: - -``` -wiki/ - config.json ← your configuration - favicon.ico ← your site icon - main/ ← :main, - ...most of content goes here - sys/ ← :sys - ...themes go here - spec/ ← :spec - ...something goes here - user/ ← :user, :u - ...user pages go here - tag/ ← :tag, :t - ...pages describing tags go here -``` - -There are usual hypha directories inside those mycelial directories. - -## Code -- Things related to reading the `config.json` go to the `cfg` module. -- Most of code related to mycelia is in the `fs` module. -- And also check out `handlers.go` and `main.go` for routing of mycelia. - diff --git a/wiki/main/doc/mycelia/meta.json b/wiki/main/doc/mycelia/meta.json deleted file mode 100644 index 1343e07..0000000 --- a/wiki/main/doc/mycelia/meta.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "" - ], - "name": "Mycelia", - "comment": "Update Help/Mycelia", - "author": "", - "time": 1593541268, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "1.markdown", - "binary_name": "" - } - }, - "Invalid": false, - "Err": null -} \ No newline at end of file diff --git a/wiki/main/doc/plugin/1.markdown b/wiki/main/doc/plugin/1.markdown deleted file mode 100644 index c44577c..0000000 --- a/wiki/main/doc/plugin/1.markdown +++ /dev/null @@ -1,29 +0,0 @@ -# Plugin system RFC -MycorrhizaWiki engine does not provide all the functionality a wiki may need and not need. Instead, it relies on the system of plugins. - -## Types of plugins -- **Parser.** They add support for displaying different MIME-types. -- **Utilities.** They add hyphae to the `:spec` mycelium. These hyphae provide administrative functionality. -- **Macros.** Something like [moinmoin ones](http://moinmo.in/HelpOnMacros), I guess. - -## Default plugins -Default MycorrhizaWiki distributive is shipped with several plugins installed. - -- **parser/markdown.** Support for `text/markdown`. -- **parser/gemini.** Support for `text/gemini`. -- *what other markups to ship? I don't want to have markdown as the main one. Textile? ReST? Asciidoc is too complex, so let's not choose it.* -- **utility/rename.** Renaming of non-user hyphae. -- *what else?* -- **macro/toc.** Table of contents. -- *what else?* - -## Plugin implementation -All plugins are written in Go and are compiled together with MycorrhizaWiki. If a wiki's admin decides to add a plugin, they shall recompile the engine with the plugin. - -> Reminds of [something](http://suckless.org/), right? - -*But compiling the engine just to add a plugin is stupid!!* Not really. Also, it makes the architecture more simple and secure. - -*What if an admin doesn't know how to program?* Plugin installation is basically limited to putting some files into a folder, editing the config and running a shell command. No programming required to install a plugin. - - diff --git a/wiki/main/doc/plugin/2.markdown b/wiki/main/doc/plugin/2.markdown deleted file mode 100644 index 5568204..0000000 --- a/wiki/main/doc/plugin/2.markdown +++ /dev/null @@ -1,30 +0,0 @@ -# Plugin system RFC -MycorrhizaWiki engine does not provide all the functionality a wiki may need and not need. Instead, it relies on the system of plugins. - -## Types of plugins -- **Parser.** They add support for displaying different MIME-types. -- **Utilities.** They add hyphae to the `:spec` mycelium. These hyphae provide administrative functionality. -- **Macros.** Something like [moinmoin ones](http://moinmo.in/HelpOnMacros), I guess. - -## Default plugins -Default MycorrhizaWiki distributive is shipped with several plugins installed. - -- **parser/markdown.** Support for `text/markdown`. This parser is powered by [russross/blackfriday](https://github.com/russross/blackfriday); this parser is ok, I guess. -- **parser/creole.** Support for `text/creole`. *Note:* there is no standard Creole MIME type. This parser is powered by [m4tty/cajun](https://github.com/m4tty/cajun); this library is somewhat outdated. Perhaps we'll reimplement it. -- **parser/gemini.** Support for `text/gemini`. *Not implemented yet.* -- *what about shipping BBcode? lol* -- **utility/rename.** Renaming of non-user hyphae. *Not implemented yet.* -- *what else?* -- **macro/toc.** Table of contents. *Not implemented yet.* -- *what else?* - -## Plugin implementation -All plugins are written in Go and are compiled together with MycorrhizaWiki. If a wiki's admin decides to add a plugin, they shall recompile the engine with the plugin. - -> Reminds of [something](http://suckless.org/), right? - -*But compiling the engine just to add a plugin is stupid!!* Not really. Also, it makes the architecture more simple and secure. - -*What if an admin doesn't know how to program?* Plugin installation is basically limited to putting some files into a folder, editing the config and running a shell command. No programming required to install a plugin. - -See `plugin` directory at the root of the repo to get inspired by the present parsers. diff --git a/wiki/main/doc/plugin/3.markdown b/wiki/main/doc/plugin/3.markdown deleted file mode 100644 index 702f628..0000000 --- a/wiki/main/doc/plugin/3.markdown +++ /dev/null @@ -1,27 +0,0 @@ -# Plugin system RFC -MycorrhizaWiki engine does not provide all the functionality a wiki may need and not need. Instead, it relies on the system of plugins. - -This document is up-to-date. - -## Types of plugins -- **Parser.** They add support for displaying different MIME-types. -- **Language.** They provide i18n support. - -## Default plugins -Default MycorrhizaWiki distributive is shipped with several plugins installed. - -- **parser/markdown.** Support for `text/markdown`. This parser is powered by [russross/blackfriday](https://github.com/russross/blackfriday); this parser is ok, I guess. -- **parser/creole.** Support for `text/creole`. *Note:* there is no standard Creole MIME type. This parser is powered by [m4tty/cajun](https://github.com/m4tty/cajun); this library is somewhat outdated. Perhaps we'll reimplement it. -- **parser/gemini.** Support for `text/gemini`. -- **language/en.** Support for English language. - -## Plugin implementation -All plugins are written in Go and are compiled together with MycorrhizaWiki. If a wiki's admin decides to add a plugin, they shall recompile the engine with the plugin. - -> Reminds of [something](http://suckless.org/), right? - -*But compiling the engine just to add a plugin is stupid!!* Not really. Also, it makes the architecture more simple and secure. - -*What if an admin doesn't know how to program?* Plugin installation is basically limited to putting some files into a folder, editing the config and running a shell command. No programming required to install a plugin. - -See `plugin` directory at the root of the repo to get inspired by the present parsers. diff --git a/wiki/main/doc/plugin/4.markdown b/wiki/main/doc/plugin/4.markdown deleted file mode 100644 index 961a49b..0000000 --- a/wiki/main/doc/plugin/4.markdown +++ /dev/null @@ -1,27 +0,0 @@ -# Plugin system RFC -MycorrhizaWiki engine does not provide all the functionality a wiki may need and not need. Instead, it relies on the system of plugins. - -This document is up-to-date. - -## Types of plugins -- **Parser.** They add support for displaying different MIME-types. -- **Language.** They provide i18n support. - -## Default plugins -Default MycorrhizaWiki distributive is shipped with several plugins installed. - -- **parser/markdown.** Support for `text/markdown`. This parser is powered by [russross/blackfriday](https://github.com/russross/blackfriday); this parser is ok, I guess. -- **parser/creole.** Support for `text/creole`. *Note:* there is no standard Creole MIME type. This parser is powered by [m4tty/cajun](https://github.com/m4tty/cajun); this library is somewhat outdated. Perhaps we'll reimplement it. -- **parser/gemini.** Support for `text/gemini`. -- **lang/en.** Support for English language. - -## Plugin implementation -All plugins are written in Go and are compiled together with MycorrhizaWiki. If a wiki's admin decides to add a plugin, they shall recompile the engine with the plugin. - -> Reminds of [something](http://suckless.org/), right? - -*But compiling the engine just to add a plugin is stupid!!* Not really. Also, it makes the architecture more simple and secure. - -*What if an admin doesn't know how to program?* Plugin installation is basically limited to putting some files into a folder, editing the config and running a shell command. No programming required to install a plugin. - -See `plugin` directory at the root of the repo to get inspired by the present parsers. diff --git a/wiki/main/doc/plugin/5.markdown b/wiki/main/doc/plugin/5.markdown deleted file mode 100644 index 44b04e4..0000000 --- a/wiki/main/doc/plugin/5.markdown +++ /dev/null @@ -1,32 +0,0 @@ -# Plugin system RFC -MycorrhizaWiki engine does not provide all the functionality a wiki may need and not need. Instead, it relies on the system of plugins. - -This document is up-to-date. - -## Types of plugins -- **Parser.** They add support for displaying different MIME-types. -- **Language.** They provide i18n support. - -## Default plugins -Default MycorrhizaWiki distributive is shipped with several plugins installed. - -- **parser/markdown.** Support for `text/markdown`. This parser is powered by [russross/blackfriday](https://github.com/russross/blackfriday); this parser is ok, I guess. -- **parser/creole.** Support for `text/creole`. *Note:* there is no standard Creole MIME type. This parser is powered by [m4tty/cajun](https://github.com/m4tty/cajun); this library is somewhat outdated. Perhaps we'll reimplement it. -- **parser/gemini.** Support for `text/gemini`. -- **lang/en.** Support for English language. - -## Plugin implementation -All plugins are written in Go and are compiled together with MycorrhizaWiki. If a wiki's admin decides to add a plugin, they shall recompile the engine with the plugin. - -> Reminds of [something](http://suckless.org/), right? - -*But compiling the engine just to add a plugin is stupid!!* Not really. Also, it makes the architecture more simple and secure. - -*What if an admin doesn't know how to program?* Plugin installation is basically limited to putting some files into a folder, editing the config and running a shell command. No programming required to install a plugin. - -See `plugin` directory at the root of the repo to get inspired by the present parsers. - -## Possible future plugins -- **Utilites.** Plugins that can do different things and provide their own hypha interface in `:spec` mycelium. -- **Macros.** Dynamic content parts that are generated when page is accessed: TOC, meta information accessors, etc. - diff --git a/wiki/main/doc/plugin/6.markdown b/wiki/main/doc/plugin/6.markdown deleted file mode 100644 index 44b04e4..0000000 --- a/wiki/main/doc/plugin/6.markdown +++ /dev/null @@ -1,32 +0,0 @@ -# Plugin system RFC -MycorrhizaWiki engine does not provide all the functionality a wiki may need and not need. Instead, it relies on the system of plugins. - -This document is up-to-date. - -## Types of plugins -- **Parser.** They add support for displaying different MIME-types. -- **Language.** They provide i18n support. - -## Default plugins -Default MycorrhizaWiki distributive is shipped with several plugins installed. - -- **parser/markdown.** Support for `text/markdown`. This parser is powered by [russross/blackfriday](https://github.com/russross/blackfriday); this parser is ok, I guess. -- **parser/creole.** Support for `text/creole`. *Note:* there is no standard Creole MIME type. This parser is powered by [m4tty/cajun](https://github.com/m4tty/cajun); this library is somewhat outdated. Perhaps we'll reimplement it. -- **parser/gemini.** Support for `text/gemini`. -- **lang/en.** Support for English language. - -## Plugin implementation -All plugins are written in Go and are compiled together with MycorrhizaWiki. If a wiki's admin decides to add a plugin, they shall recompile the engine with the plugin. - -> Reminds of [something](http://suckless.org/), right? - -*But compiling the engine just to add a plugin is stupid!!* Not really. Also, it makes the architecture more simple and secure. - -*What if an admin doesn't know how to program?* Plugin installation is basically limited to putting some files into a folder, editing the config and running a shell command. No programming required to install a plugin. - -See `plugin` directory at the root of the repo to get inspired by the present parsers. - -## Possible future plugins -- **Utilites.** Plugins that can do different things and provide their own hypha interface in `:spec` mycelium. -- **Macros.** Dynamic content parts that are generated when page is accessed: TOC, meta information accessors, etc. - diff --git a/wiki/main/doc/plugin/meta.json b/wiki/main/doc/plugin/meta.json deleted file mode 100644 index ccfb082..0000000 --- a/wiki/main/doc/plugin/meta.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "" - ], - "name": "Plugin", - "comment": "Update :Main/Help/Plugin", - "author": "", - "time": 1593806875, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "1.markdown", - "binary_name": "" - }, - "2": { - "tags": [ - "" - ], - "name": "Plugin", - "comment": "Update :Main/Doc/Plugin", - "author": "", - "time": 1593882606, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "2.markdown", - "binary_name": "" - }, - "3": { - "tags": [ - "" - ], - "name": "Plugin", - "comment": "Update :Main/Doc/Plugin", - "author": "", - "time": 1594999571, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "3.markdown", - "binary_name": "" - }, - "4": { - "tags": [ - "" - ], - "name": "Plugin", - "comment": "Update :Main/Doc/Plugin", - "author": "", - "time": 1595001530, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "4.markdown", - "binary_name": "" - }, - "5": { - "tags": [ - "" - ], - "name": "Plugin", - "comment": "Update :Main/Doc/Plugin", - "author": "", - "time": 1595092102, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "5.markdown", - "binary_name": "" - }, - "6": { - "tags": [ - "" - ], - "name": "Plugin", - "comment": "Update :Main/Doc/Plugin", - "author": "", - "time": 1595092219, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "6.markdown", - "binary_name": "" - } - } -} \ No newline at end of file diff --git a/wiki/main/doc/user/1.txt b/wiki/main/doc/user/1.txt deleted file mode 100644 index 472bd33..0000000 --- a/wiki/main/doc/user/1.txt +++ /dev/null @@ -1,7 +0,0 @@ -Unlike earliest wiki engines, MycorrhizaWiki supports authorization system and non-authorized edits are disabled by default. Anyone can get an account on a MycorrhizaWiki wiki by signing up. There must be a form for that. - -* Each user is part of one or more user groups. -* Each user has a username. Usernames follow the same naming conventions as hyphae but additionally the forward slash / character is prohibited in usernames. Please note that usernames are case-insensitive, thus //shroom// and //Shroom// mean the same; spaces and underscores are also the same, //amanita muscaria// = //amanita_muscaria//. -* Password chosen by the user is not directly stored on the server. Only its hash is salted so even if a server is hacked, a hacker won't get the passwords. There are no restrictions on passwords. Server administrator doesn't have access to the password as well. -* Each user gets their own hypha in the //:user// mycelium. The hypha has the same name as the user. Subhyphae can also be added. -* If an authenticated user makes an edit, the fact that they have made the edit is stored in the revision history. diff --git a/wiki/main/doc/user/2.txt b/wiki/main/doc/user/2.txt deleted file mode 100644 index 98c4781..0000000 --- a/wiki/main/doc/user/2.txt +++ /dev/null @@ -1,9 +0,0 @@ -This document is not up-to-date. Information here will be true once we finish its development :) - -Unlike earliest wiki engines, MycorrhizaWiki supports authorization system and non-authorized edits are disabled by default. Anyone can get an account on a MycorrhizaWiki wiki by signing up. There must be a form for that. - -* Each user is part of one or more user groups. -* Each user has a username. Usernames follow the same naming conventions as hyphae but additionally the forward slash / character is prohibited in usernames. Please note that usernames are case-insensitive, thus //shroom// and //Shroom// mean the same; spaces and underscores are also the same, //amanita muscaria// = //amanita_muscaria//. -* Password chosen by the user is not directly stored on the server. Only its hash is salted so even if a server is hacked, a hacker won't get the passwords. There are no restrictions on passwords. Server administrator doesn't have access to the password as well. -* Each user gets their own hypha in the //:user// mycelium. The hypha has the same name as the user. Subhyphae can also be added. -* If an authenticated user makes an edit, the fact that they have made the edit is stored in the revision history. diff --git a/wiki/main/doc/user/meta.json b/wiki/main/doc/user/meta.json deleted file mode 100644 index 0ee761d..0000000 --- a/wiki/main/doc/user/meta.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "" - ], - "name": "User", - "comment": "Update :Main/Doc/User", - "author": "", - "time": 1594749111, - "text_mime": "text/creole", - "binary_mime": "", - "text_name": "1.txt", - "binary_name": "" - }, - "2": { - "tags": [ - "" - ], - "name": "User", - "comment": "Update :Main/Doc/User", - "author": "", - "time": 1595092543, - "text_mime": "text/creole", - "binary_mime": "", - "text_name": "2.txt", - "binary_name": "" - } - } -} \ No newline at end of file diff --git a/wiki/main/doc/wikilink/1.markdown b/wiki/main/doc/wikilink/1.markdown deleted file mode 100644 index a03e5ee..0000000 --- a/wiki/main/doc/wikilink/1.markdown +++ /dev/null @@ -1,46 +0,0 @@ -# Wikilink RFC - -All parsers for MycorrhizaWiki provide hyperlink support. Usually, they follow HTML convention. - -- `http://example.org/absolute-path` -- `/rooted-path` -- `same-folder-path` -- `../parent-folder-path` - -This is not really convenient for wikis where most of links are either rooted or links to children! - -All parsers of MycorrhizaWiki are expected to support these types of links and convert them to rooted paths. - -- `http://example.org/absolute-path` -- `hypha in main mycelium` -- `::hypha in the same mycelium` -- `:mycelium/hypha in an explicit mycelium` -- `/subhypha` -- `./subhypha` -- `../sibling-hypha` - -**TODO:** create a package that implements this thing. NB: to generate a correct link, it is required to know full name of hypha where the link is used. - -## Markdown extension - -> This is an extension to markdown's syntax that is used in MycorrhizaWiki and nowhere else. - -Text wrapped in `[[` and `]]` is a link that has same text and url. *For some reason it's not possible in Markdown without duplicating url* - -``` -[[x]] == [x](x) -``` - -## Examples - -All examples assume that `:example/test` is the current hypha. - -``` -wikilink actual path -foo == /foo -::foo == /:example/foo -:bar/foo == /:bar/foo -/baz == /:example/test/baz -./baz == /:example/test/baz -../qux == /:example/qux -``` diff --git a/wiki/main/doc/wikilink/2.markdown b/wiki/main/doc/wikilink/2.markdown deleted file mode 100644 index 87ababd..0000000 --- a/wiki/main/doc/wikilink/2.markdown +++ /dev/null @@ -1,49 +0,0 @@ -# Wikilink RFC - -All parsers for MycorrhizaWiki provide hyperlink support. Usually, they follow HTML convention. - -- `http://example.org/absolute-path` -- `/rooted-path` -- `same-folder-path` -- `../parent-folder-path` - -This is not really convenient for wikis where most of links are either rooted or links to children! - -All parsers of MycorrhizaWiki are expected to support these types of links and convert them to rooted paths. - -- `http://example.org/absolute-path` -- `hypha in main mycelium` -- `::hypha in the same mycelium` -- `:mycelium/hypha in an explicit mycelium` -- `/subhypha` -- `./subhypha` -- `../sibling-hypha` - -**TODO:** create a package that implements this thing. NB: to generate a correct link, it is required to know full name of hypha where the link is used. - -## Markdown extension - -> This is an extension to markdown's syntax that is used in MycorrhizaWiki and nowhere else. - -Text wrapped in `[[` and `]]` is a link that has same text and url. *For some reason it's not possible in Markdown without duplicating url* - -``` -[[x]] == [x](x) -``` - -## Examples - -All examples assume that `:example/test` is the current hypha. - -``` -wikilink actual path -foo == /foo -::foo == /:example/foo -:bar/foo == /:bar/foo -/baz == /:example/test/baz -./baz == /:example/test/baz -../qux == /:example/qux -http://example.org == http://example.org -gemini://example.org == gemini://example.org -mailto:me@example.org == mailto:me@example.org -``` diff --git a/wiki/main/doc/wikilink/3.markdown b/wiki/main/doc/wikilink/3.markdown deleted file mode 100644 index 28598d7..0000000 --- a/wiki/main/doc/wikilink/3.markdown +++ /dev/null @@ -1,51 +0,0 @@ -# Wikilink RFC - -*This page is not up-to-date. One day, features defined here shall be implemented.* - -All parsers for MycorrhizaWiki provide hyperlink support. Usually, they follow HTML convention. - -- `http://example.org/absolute-path` -- `/rooted-path` -- `same-folder-path` -- `../parent-folder-path` - -This is not really convenient for wikis where most of links are either rooted or links to children! - -All parsers of MycorrhizaWiki are expected to support these types of links and convert them to rooted paths. - -- `http://example.org/absolute-path` -- `hypha in main mycelium` -- `::hypha in the same mycelium` -- `:mycelium/hypha in an explicit mycelium` -- `/subhypha` -- `./subhypha` -- `../sibling-hypha` - -**TODO:** create a package that implements this thing. NB: to generate a correct link, it is required to know full name of hypha where the link is used. - -## Markdown extension - -> This is an extension to markdown's syntax that is used in MycorrhizaWiki and nowhere else. - -Text wrapped in `[[` and `]]` is a link that has same text and url. *For some reason it's not possible in Markdown without duplicating url* - -``` -[[x]] == [x](x) -``` - -## Examples - -All examples assume that `:example/test` is the current hypha. - -``` -wikilink actual path -foo == /foo -::foo == /:example/foo -:bar/foo == /:bar/foo -/baz == /:example/test/baz -./baz == /:example/test/baz -../qux == /:example/qux -http://example.org == http://example.org -gemini://example.org == gemini://example.org -mailto:me@example.org == mailto:me@example.org -``` diff --git a/wiki/main/doc/wikilink/meta.json b/wiki/main/doc/wikilink/meta.json deleted file mode 100644 index 09c4ea1..0000000 --- a/wiki/main/doc/wikilink/meta.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "" - ], - "name": "Wikilink", - "comment": "Update :Main/Help/Wikilink", - "author": "", - "time": 1593807908, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "1.markdown", - "binary_name": "" - }, - "2": { - "tags": [ - "" - ], - "name": "Wikilink", - "comment": "Update :Main/Doc/Wikilink", - "author": "", - "time": 1593875778, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "2.markdown", - "binary_name": "" - }, - "3": { - "tags": [ - "" - ], - "name": "Wikilink", - "comment": "Update :Main/Doc/Wikilink", - "author": "", - "time": 1595092265, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "3.markdown", - "binary_name": "" - } - } -} \ No newline at end of file diff --git a/wiki/main/fruit/1.md b/wiki/main/fruit/1.md deleted file mode 100644 index a70eec8..0000000 --- a/wiki/main/fruit/1.md +++ /dev/null @@ -1 +0,0 @@ -Fruit is a type of fetus. Is tasty so cool coo l ha i love fwriotwsn diff --git a/wiki/main/fruit/2.md b/wiki/main/fruit/2.md deleted file mode 100644 index 80e180f..0000000 --- a/wiki/main/fruit/2.md +++ /dev/null @@ -1,2 +0,0 @@ -Many people ask the question: what is fruit exactly? -Fruit is a type of fetus. Is tasty so cool coo l ha i love fwriotwsn diff --git a/wiki/main/fruit/3.markdown b/wiki/main/fruit/3.markdown deleted file mode 100644 index f0d9e17..0000000 --- a/wiki/main/fruit/3.markdown +++ /dev/null @@ -1,4 +0,0 @@ -According to real *scientists*, fruit is a type of fetus. Most of them are tasty and cool, though some of them are really sour and depressing. Be careful when choosing fruit. Best ones are: - -* [Apple](Apple) -* [Pear](Pear) \ No newline at end of file diff --git a/wiki/main/fruit/4.markdown b/wiki/main/fruit/4.markdown deleted file mode 100644 index 3d7f71e..0000000 --- a/wiki/main/fruit/4.markdown +++ /dev/null @@ -1,5 +0,0 @@ -According to real *scientists*, fruit is a type of fetus. Most of them are tasty and cool, though some of them are really sour and depressing. Be careful when choosing fruit. Best ones are: - -* [Apple](./Apple) -* [Pear](/Pear) - \ No newline at end of file diff --git a/wiki/main/fruit/5.markdown b/wiki/main/fruit/5.markdown deleted file mode 100644 index 3ba13b4..0000000 --- a/wiki/main/fruit/5.markdown +++ /dev/null @@ -1,10 +0,0 @@ -According to real *scientists*, fruit is a type of fetus. Most of them are tasty and cool, though some of them are really sour and depressing. Be careful when choosing fruit. Best ones are: - -- [Apple](Fruit/Apple) -- [Pear](Fruit/Pear) - -``` -фрукты полезны для здоровья!! -``` - - diff --git a/wiki/main/fruit/apple/1.jpg b/wiki/main/fruit/apple/1.jpg deleted file mode 100644 index eb03b60..0000000 Binary files a/wiki/main/fruit/apple/1.jpg and /dev/null differ diff --git a/wiki/main/fruit/apple/1.txt b/wiki/main/fruit/apple/1.txt deleted file mode 100644 index cc4fce0..0000000 --- a/wiki/main/fruit/apple/1.txt +++ /dev/null @@ -1,3 +0,0 @@ - A honeycrisp apple from an organic food farm co-op. - -Source: https://ru.wikipedia.org/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:Honeycrisp-Apple.jpg diff --git a/wiki/main/fruit/apple/2.jpg b/wiki/main/fruit/apple/2.jpg deleted file mode 100644 index 7127c67..0000000 Binary files a/wiki/main/fruit/apple/2.jpg and /dev/null differ diff --git a/wiki/main/fruit/apple/2.txt b/wiki/main/fruit/apple/2.txt deleted file mode 100644 index 64ad8c8..0000000 --- a/wiki/main/fruit/apple/2.txt +++ /dev/null @@ -1,3 +0,0 @@ -Красное яблоко. Для съёмки использовалась белая бумага позади и над яблоком, и фотовспышка SB-600 в 1/4 мощности. - -Source: https://commons.wikimedia.org/wiki/File:Red_Apple.jpg \ No newline at end of file diff --git a/wiki/main/fruit/apple/3.txt b/wiki/main/fruit/apple/3.txt deleted file mode 100644 index c00151c..0000000 --- a/wiki/main/fruit/apple/3.txt +++ /dev/null @@ -1,6 +0,0 @@ -Mycorrhiza is pure happiness - -Красное яблоко. Для съёмки использовалась белая бумага позади и над яблоком, и фотовспышка SB-600 в 1/4 мощности. - -Source: https://commons.wikimedia.org/wiki/File:Red_Apple.jpg - \ No newline at end of file diff --git a/wiki/main/fruit/apple/4.jpg b/wiki/main/fruit/apple/4.jpg deleted file mode 100644 index 951adc4..0000000 Binary files a/wiki/main/fruit/apple/4.jpg and /dev/null differ diff --git a/wiki/main/fruit/apple/4.txt b/wiki/main/fruit/apple/4.txt deleted file mode 100644 index 4281d69..0000000 --- a/wiki/main/fruit/apple/4.txt +++ /dev/null @@ -1,2 +0,0 @@ -Я́блоко — сочный плод яблони, который употребляется в пищу в свежем виде, служит сырьём в кулинарии и для приготовления напитков. Наибольшее распространение получила яблоня домашняя, реже выращивают яблоню сливолистную. Размер красных, зелёных или жёлтых шаровидных плодов 5—13 см в диаметре. Происходит из Центральной Азии, где до сих пор произрастает дикорастущий предок яблони домашней — яблоня Сиверса. На сегодняшний день существует множество сортов этого вида яблони, произрастающих в различных климатических условиях. По времени созревания отличают летние, осенние и зимние сорта, более поздние сорта отличаются хорошей стойкостью. - \ No newline at end of file diff --git a/wiki/main/fruit/apple/meta.json b/wiki/main/fruit/apple/meta.json deleted file mode 100644 index 39ff1ec..0000000 --- a/wiki/main/fruit/apple/meta.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "img" - ], - "name": "Apple", - "comment": "add apple pic hehehe", - "author": "bouncepaw", - "time": 1591639464, - "text_mime": "text/plain", - "binary_mime": "image/jpeg", - "text_name": "1.txt", - "binary_name": "1.jpg" - }, - "2": { - "tags": null, - "name": "Apple", - "comment": "Update Fruit/Apple", - "author": "", - "time": 1592570366, - "text_mime": "text/plain", - "binary_mime": "image/jpeg", - "text_name": "2.txt", - "binary_name": "2.jpg" - }, - "3": { - "tags": null, - "name": "Apple", - "comment": "Test fs dumping", - "author": "", - "time": 1592570926, - "text_mime": "text/plain", - "binary_mime": "image/jpeg", - "text_name": "3.txt", - "binary_name": "2.jpg" - }, - "4": { - "tags": null, - "name": "Apple", - "comment": "copypaste from wikipedia", - "author": "", - "time": 1592663126, - "text_mime": "text/markdown", - "binary_mime": "image/jpeg", - "text_name": "4.txt", - "binary_name": "4.jpg" - } - } -} diff --git a/wiki/main/fruit/meta.json b/wiki/main/fruit/meta.json deleted file mode 100644 index 46c5090..0000000 --- a/wiki/main/fruit/meta.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "fetus", - "tasty" - ], - "name": "Fruit", - "comment": "create Fruit", - "author": "bouncepaw", - "time": 1591635559, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "1.md", - "binary_name": "" - }, - "2": { - "tags": [ - "fetus", - "tasty" - ], - "name": "Fruit", - "comment": "update Fruit", - "author": "fungimaster", - "time": 1591636222, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "2.md", - "binary_name": "" - }, - "3": { - "tags": null, - "name": "Fruit", - "comment": "Update Fruit", - "author": "", - "time": 1593279957, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "3.markdown", - "binary_name": "" - }, - "4": { - "tags": [ - "" - ], - "name": "Fruit", - "comment": "Update Fruit", - "author": "", - "time": 1593338963, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "4.markdown", - "binary_name": "" - }, - "5": { - "tags": [ - "" - ], - "name": "Fruit", - "comment": "Update Fruit", - "author": "", - "time": 1593339050, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "5.markdown", - "binary_name": "" - } - }, - "Invalid": false, - "Err": null -} \ No newline at end of file diff --git a/wiki/main/fruit/pear/1.markdown b/wiki/main/fruit/pear/1.markdown deleted file mode 100644 index 0ec9c6b..0000000 --- a/wiki/main/fruit/pear/1.markdown +++ /dev/null @@ -1,3 +0,0 @@ -A **pear** is a sweet fruit, usually with a green skin and a lot of juice, that has a round base and is slightly pointed towards the stem. - -Source of info: [cambridge dict](https://dictionary.cambridge.org/ru/словарь/английский/pear) \ No newline at end of file diff --git a/wiki/main/fruit/pear/2.markdown b/wiki/main/fruit/pear/2.markdown deleted file mode 100644 index 30c8f65..0000000 --- a/wiki/main/fruit/pear/2.markdown +++ /dev/null @@ -1,5 +0,0 @@ -A **pear** is a sweet fruit, usually with a green skin and a lot of juice, that has a round base and is slightly pointed towards the stem. - -Source of info: [cambridge dict](https://dictionary.cambridge.org/ru/словарь/английский/pear) - -По-русски: груша \ No newline at end of file diff --git a/wiki/main/fruit/pear/meta.json b/wiki/main/fruit/pear/meta.json deleted file mode 100644 index ddb6a14..0000000 --- a/wiki/main/fruit/pear/meta.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "" - ], - "name": "Pear", - "comment": "Create Fruit/Pear", - "author": "", - "time": 1592834186, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "1.markdown", - "binary_name": "" - }, - "2": { - "tags": null, - "name": "Pear", - "comment": "Update :Main/Fruit/Pear", - "author": "", - "time": 1593949655, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "2.markdown", - "binary_name": "" - } - } -} \ No newline at end of file diff --git a/wiki/main/fruit/pineapple/1.html b/wiki/main/fruit/pineapple/1.html deleted file mode 100644 index 61b3b19..0000000 --- a/wiki/main/fruit/pineapple/1.html +++ /dev/null @@ -1 +0,0 @@ -Pineapple is apple from a pine \ No newline at end of file diff --git a/wiki/main/fruit/pineapple/meta.json b/wiki/main/fruit/pineapple/meta.json deleted file mode 100644 index 877bb4a..0000000 --- a/wiki/main/fruit/pineapple/meta.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "" - ], - "name": "Pineapple", - "comment": "Update Fruit/Pineapple", - "author": "", - "time": 1592997100, - "text_mime": "text/html", - "binary_mime": "", - "text_name": "1.html", - "binary_name": "" - } - } -} \ No newline at end of file diff --git a/wiki/main/fungus/1.markdown b/wiki/main/fungus/1.markdown deleted file mode 100644 index bb74c76..0000000 --- a/wiki/main/fungus/1.markdown +++ /dev/null @@ -1 +0,0 @@ -A **fungus** is any member of the group of eukaryotic organisms that includes microorganisms such as yeasts and molds, as well as the more familiar mushrooms. These organisms are classified as a kingdom, which is separate from the other eukaryotic life kingdoms of plants and animals. \ No newline at end of file diff --git a/wiki/main/fungus/2.markdown b/wiki/main/fungus/2.markdown deleted file mode 100644 index 4233359..0000000 --- a/wiki/main/fungus/2.markdown +++ /dev/null @@ -1,3 +0,0 @@ -A **fungus** is any member of the group of eukaryotic organisms that includes microorganisms such as yeasts and molds, as well as the more familiar mushrooms. These organisms are classified as a kingdom, which is separate from the other eukaryotic life kingdoms of plants and animals. - -По-русски: грибы. diff --git a/wiki/main/fungus/amanita_muscaria/1.markdown b/wiki/main/fungus/amanita_muscaria/1.markdown deleted file mode 100644 index 60438ef..0000000 --- a/wiki/main/fungus/amanita_muscaria/1.markdown +++ /dev/null @@ -1,3 +0,0 @@ -Amanita muscaria, commonly known as the fly agaric or fly amanita, is a basidiomycete of the genus Amanita. It is also a muscimol mushroom. Native throughout the temperate and boreal regions of the Northern Hemisphere, Amanita muscaria has been unintentionally introduced to many countries in the Southern Hemisphere, generally as a symbiont with pine and birch plantations, and is now a true cosmopolitan species. It associates with various deciduous and coniferous trees. - -It is not an [](Apple)! \ No newline at end of file diff --git a/wiki/main/fungus/amanita_muscaria/2.markdown b/wiki/main/fungus/amanita_muscaria/2.markdown deleted file mode 100644 index a1ed53d..0000000 --- a/wiki/main/fungus/amanita_muscaria/2.markdown +++ /dev/null @@ -1,4 +0,0 @@ -Amanita muscaria, commonly known as the fly agaric or fly amanita, is a basidiomycete of the genus Amanita. It is also a muscimol mushroom. Native throughout the temperate and boreal regions of the Northern Hemisphere, Amanita muscaria has been unintentionally introduced to many countries in the Southern Hemisphere, generally as a symbiont with pine and birch plantations, and is now a true cosmopolitan species. It associates with various deciduous and coniferous trees. - -It is not an [Apple](Apple)! - \ No newline at end of file diff --git a/wiki/main/fungus/amanita_muscaria/3.markdown b/wiki/main/fungus/amanita_muscaria/3.markdown deleted file mode 100644 index a20fcfa..0000000 --- a/wiki/main/fungus/amanita_muscaria/3.markdown +++ /dev/null @@ -1,5 +0,0 @@ -Amanita muscaria, commonly known as the fly agaric or fly amanita, is a basidiomycete of the genus Amanita. It is also a muscimol mushroom. Native throughout the temperate and boreal regions of the Northern Hemisphere, Amanita muscaria has been unintentionally introduced to many countries in the Southern Hemisphere, generally as a symbiont with pine and birch plantations, and is now a true cosmopolitan species. It associates with various deciduous and coniferous trees. - -It is not an [Apple](/Apple)! - - \ No newline at end of file diff --git a/wiki/main/fungus/amanita_muscaria/4.markdown b/wiki/main/fungus/amanita_muscaria/4.markdown deleted file mode 100644 index 2d1edd4..0000000 --- a/wiki/main/fungus/amanita_muscaria/4.markdown +++ /dev/null @@ -1,6 +0,0 @@ -Amanita muscaria, commonly known as the fly agaric or fly amanita, is a basidiomycete of the genus Amanita. It is also a muscimol mushroom. Native throughout the temperate and boreal regions of the Northern Hemisphere, Amanita muscaria has been unintentionally introduced to many countries in the Southern Hemisphere, generally as a symbiont with pine and birch plantations, and is now a true cosmopolitan species. It associates with various deciduous and coniferous trees. - -It is not an [Apple](/Fruit/Apple)! - - - \ No newline at end of file diff --git a/wiki/main/fungus/amanita_muscaria/meta.json b/wiki/main/fungus/amanita_muscaria/meta.json deleted file mode 100644 index 741d52e..0000000 --- a/wiki/main/fungus/amanita_muscaria/meta.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "" - ], - "name": "Amanita Muscaria", - "comment": "Update Fungus/Amanita Muscaria", - "author": "", - "time": 1592836062, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "1.markdown", - "binary_name": "" - }, - "2": { - "tags": [ - "" - ], - "name": "Amanita Muscaria", - "comment": "Update Fungus/Amanita Muscaria", - "author": "", - "time": 1592836073, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "2.markdown", - "binary_name": "" - }, - "3": { - "tags": [ - "" - ], - "name": "Amanita Muscaria", - "comment": "Update Fungus/Amanita Muscaria", - "author": "", - "time": 1592836084, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "3.markdown", - "binary_name": "" - }, - "4": { - "tags": [ - "" - ], - "name": "Amanita Muscaria", - "comment": "Update Fungus/Amanita Muscaria", - "author": "", - "time": 1592836099, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "4.markdown", - "binary_name": "" - } - } -} \ No newline at end of file diff --git a/wiki/main/fungus/amanita_regalis/1.jpeg b/wiki/main/fungus/amanita_regalis/1.jpeg deleted file mode 100644 index 2059fbd..0000000 Binary files a/wiki/main/fungus/amanita_regalis/1.jpeg and /dev/null differ diff --git a/wiki/main/fungus/amanita_regalis/1.markdown b/wiki/main/fungus/amanita_regalis/1.markdown deleted file mode 100644 index e9bd635..0000000 --- a/wiki/main/fungus/amanita_regalis/1.markdown +++ /dev/null @@ -1,7 +0,0 @@ -Шляпка 7—16(20) см в диаметре, полушаровидная, раскрывающаяся до выпуклой и почти плоской со слабо вдавленным центром, с радиально разлинованным краем. Окраска тёмно-умброво-коричневая, оливково-охристая, охристо-коричневая, иногда серо-жёлтая, в центре более интенсивная. Общее покрывало на молодых грибах опушённое, ярко-жёлтое, затем остаётся в виде легко смываемых обрывков, на солнце белеющих, а к старости иногда становящихся серо-жёлтыми. - - -Пластинки частые, сначала узко-приросшие к ножке, затем свободные от неё, кремовые, с многочисленными пластиночками разной длины. - - -Ножка достигает 9—20 см в высоту и 1—2,5 см в поперечнике, утончающаяся кверху, в основании с яйцевидным или шаровидным утолщением. Поверхность ножки волокнисто-бархатистая, белая или беловатая, при прикосновении иногда слабо буреющая. Кольцо в верхней части ножки, беловатое, перепончатое, не разлинованное. Остатки общего покрывала в виде нескольких поясков желтоватых бородавчатых хлопьев на утолщении ножки. \ No newline at end of file diff --git a/wiki/main/fungus/amanita_regalis/meta.json b/wiki/main/fungus/amanita_regalis/meta.json deleted file mode 100644 index 1f06a09..0000000 --- a/wiki/main/fungus/amanita_regalis/meta.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "" - ], - "name": "Amanita Regalis", - "comment": "Update Fungus/Amanita Regalis", - "author": "", - "time": 1592838045, - "text_mime": "text/markdown", - "binary_mime": "image/jpeg", - "text_name": "1.markdown", - "binary_name": "1.jpeg" - } - } -} \ No newline at end of file diff --git a/wiki/main/fungus/meta.json b/wiki/main/fungus/meta.json deleted file mode 100644 index 065253b..0000000 --- a/wiki/main/fungus/meta.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "" - ], - "name": "Fungus", - "comment": "Update Fungus", - "author": "", - "time": 1592835944, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "1.markdown", - "binary_name": "" - }, - "2": { - "tags": [ - "" - ], - "name": "Fungus", - "comment": "Update :Main/Fungus", - "author": "", - "time": 1593951453, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "2.markdown", - "binary_name": "" - } - } -} \ No newline at end of file diff --git a/wiki/main/test/1.markdown b/wiki/main/test/1.markdown deleted file mode 100644 index 565097b..0000000 --- a/wiki/main/test/1.markdown +++ /dev/null @@ -1 +0,0 @@ -This is a testing page to test that editing works, etc diff --git a/wiki/main/test/meta.json b/wiki/main/test/meta.json deleted file mode 100644 index 7478662..0000000 --- a/wiki/main/test/meta.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "" - ], - "name": "Test", - "comment": "Update :Main/Test", - "author": "", - "time": 1593943985, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "1.markdown", - "binary_name": "" - } - } -} \ No newline at end of file diff --git a/wiki/main/testcreole/1.txt b/wiki/main/testcreole/1.txt deleted file mode 100644 index bc193fa..0000000 --- a/wiki/main/testcreole/1.txt +++ /dev/null @@ -1,135 +0,0 @@ -//This test was taken from http://www.wikicreole.org/wiki/JSPWikiTestCases?skin=raw// - -The following is a test of WikiCreole: - -= Top-level heading (1) -== This a test for creole 0.1 (2) -=== This is a Subheading (3) -==== Subsub (4) -===== Subsubsub (5) - -The ending equal signs should not be displayed: - -= Top-level heading (1) = -== This a test for creole 0.1 (2) == -=== This is a Subheading (3) === -==== Subsub (4) ==== -===== Subsubsub (5) ===== - - -You can make things **bold** or //italic// or **//both//** or //**both**//. - -Character formatting extends across line breaks: **bold, -this is still bold. This line deliberately does not end in star-star. - -Not bold. Character formatting does not cross paragraph boundaries. - -You can use [[internal links]] or [[http://www.wikicreole.org|external links]], -give the link a [[internal links|different]] name. - -Here's another sentence: This wisdom is taken from [[Ward Cunningham's]] -[[http://www.c2.com/doc/wikisym/WikiSym2006.pdf|Presentation at the Wikisym 06]]. - -Here's a external link without a description: [[http://www.wikicreole.org]] - -Free links without braces should be rendered as well, like http://www.wikicreole.org/ and http://www.wikicreole.org/users/~example. - -Creole1.0 specifies that http://bar and ftp://bar should not render italic, -something like foo://bar should render as italic. - -You can use this to draw a line to separate the page: ----- - -You can use lists, start it at the first column for now, please... - -unnumbered lists are like -* item a -* item b -* **bold item c** - -blank space is also permitted before lists like: - * item a - * item b -* item c - ** item c.a - -or you can number them -# [[item 1]] -# item 2 -# // italic item 3 // - ## item 3.1 - ## item 3.2 - -up to five levels -* 1 -** 2 -*** 3 -**** 4 -***** 5 - -* You can have -multiline list items -* this is a second multiline -list item - -You can use nowiki syntax if you would like do stuff like this: - -{{{ -Guitar Chord C: - -||---|---|---| -||-1-|---|---| -||---|---|---| -||---|-2-|---| -||---|---|-3-| -||---|---|---| -~}}} - -Note: if you look at the source code of the above, you see the escape char (tilde, ~ ) -being used to escape the closing triple curly braces. This is to do nesting because -all this text is enclosed in nowiki markup. -}}} - -You can also use it inline nowiki {{{ in a sentence }}} like this. - -= Escapes = -Normal Link: http://wikicreole.org/ - now same link, but escaped: ~http://wikicreole.org/ - -Normal asterisks: ~**not bold~** - -a tilde alone: ~ - -a tilde escapes itself: ~~xxx - -=== Creole 0.2 === - -This should be a flower with the ALT text "this is a flower" if your wiki supports ALT text on images: - -{{Red_Flower.jpg|here is a red flower}} - -=== Creole 0.4 === - -Tables are done like this: - -|=header col1|=header col2| -|col1|col2| -|you |can | -|also |align\\ it. | -|links |[[http://www.wikicreole.org/|Should Work]]| - -You can format an address by simply forcing linebreaks: - -My contact dates:\\ -Pone: xyz\\ -Fax: +45\\ -Mobile: abc - -=== Creole 0.5 === - -|= Header title |= Another header title | -| {{{ //not italic text// }}} | {{{ **not bold text** }}} | -| //italic text// | ** bold text ** | - -=== Creole 1.0 === - -If interwiki links are setup in your wiki, this links to the WikiCreole page about Creole 1.0 test cases: [[WikiCreole:Creole1.0TestCases]]. \ No newline at end of file diff --git a/wiki/main/testcreole/2.txt b/wiki/main/testcreole/2.txt deleted file mode 100644 index 2b2d944..0000000 --- a/wiki/main/testcreole/2.txt +++ /dev/null @@ -1,135 +0,0 @@ -//This test was taken from http://www.wikicreole.org/wiki/JSPWikiTestCases?skin=raw// - -The following is a test of WikiCreole: - -= Top-level heading (1) -== This a test for creole 0.1 (2) -=== This is a Subheading (3) -==== Subsub (4) -===== Subsubsub (5) - -The ending equal signs should not be displayed: - -= Top-level heading (1) = -== This a test for creole 0.1 (2) == -=== This is a Subheading (3) === -==== Subsub (4) ==== -===== Subsubsub (5) ===== - - -You can make things **bold** or //italic// or **//both//** or //**both**//. - -Character formatting extends across line breaks: **bold, -this is still bold. This line deliberately does not end in star-star. - -Not bold. Character formatting does not cross paragraph boundaries. - -You can use [[internal links]] or [[http://www.wikicreole.org|external links]], -give the link a [[internal links|different]] name. - -Here's another sentence: This wisdom is taken from [[Ward Cunningham's]] -[[http://www.c2.com/doc/wikisym/WikiSym2006.pdf|Presentation at the Wikisym 06]]. - -Here's a external link without a description: [[http://www.wikicreole.org]] - -Free links without braces should be rendered as well, like http://www.wikicreole.org/ and http://www.wikicreole.org/users/~example. - -Creole1.0 specifies that http://bar and ftp://bar should not render italic, -something like foo://bar should render as italic. - -You can use this to draw a line to separate the page: ----- - -You can use lists, start it at the first column for now, please... - -unnumbered lists are like -* item a -* item b -* **bold item c** - -blank space is also permitted before lists like: - * item a - * item b -* item c - ** item c.a - -or you can number them -# [[item 1]] -# item 2 -# // italic item 3 // - ## item 3.1 - ## item 3.2 - -up to five levels -* 1 -** 2 -*** 3 -**** 4 -***** 5 - -* You can have -multiline list items -* this is a second multiline -list item - -You can use nowiki syntax if you would like do stuff like this: - -{{{ -Guitar Chord C: - -||---|---|---| -||-1-|---|---| -||---|---|---| -||---|-2-|---| -||---|---|-3-| -||---|---|---| -~}}} - -Note: if you look at the source code of the above, you see the escape char (tilde, ~ ) -being used to escape the closing triple curly braces. This is to do nesting because -all this text is enclosed in nowiki markup. -}}} - -You can also use it inline nowiki {{{ in a sentence }}} like this. - -= Escapes = -Normal Link: http://wikicreole.org/ - now same link, but escaped: ~http://wikicreole.org/ - -Normal asterisks: ~**not bold~** - -a tilde alone: ~ - -a tilde escapes itself: ~~xxx - -=== Creole 0.2 === - -This should be a flower with the ALT text "this is a flower" if your wiki supports ALT text on images: - -{{/Red_Flower.jpg|here is a red flower}} - -=== Creole 0.4 === - -Tables are done like this: - -|=header col1|=header col2| -|col1|col2| -|you |can | -|also |align\\ it. | -|links |[[http://www.wikicreole.org/|Should Work]]| - -You can format an address by simply forcing linebreaks: - -My contact dates:\\ -Pone: xyz\\ -Fax: +45\\ -Mobile: abc - -=== Creole 0.5 === - -|= Header title |= Another header title | -| {{{ //not italic text// }}} | {{{ **not bold text** }}} | -| //italic text// | ** bold text ** | - -=== Creole 1.0 === - -If interwiki links are setup in your wiki, this links to the WikiCreole page about Creole 1.0 test cases: [[WikiCreole:Creole1.0TestCases]]. \ No newline at end of file diff --git a/wiki/main/testcreole/meta.json b/wiki/main/testcreole/meta.json deleted file mode 100644 index db9d560..0000000 --- a/wiki/main/testcreole/meta.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "" - ], - "name": "Testcreole", - "comment": "Update :Main/Testcreole", - "author": "", - "time": 1593881943, - "text_mime": "text/creole", - "binary_mime": "", - "text_name": "1.txt", - "binary_name": "" - }, - "2": { - "tags": [ - "" - ], - "name": "Testcreole", - "comment": "Update :Main/Testcreole", - "author": "", - "time": 1593882073, - "text_mime": "text/creole", - "binary_mime": "", - "text_name": "2.txt", - "binary_name": "" - } - } -} \ No newline at end of file diff --git a/wiki/main/testgemini/1.txt b/wiki/main/testgemini/1.txt deleted file mode 100644 index afdf451..0000000 --- a/wiki/main/testgemini/1.txt +++ /dev/null @@ -1,50 +0,0 @@ -# Gemtext cheatsheet -Here's the basics of how text works in Gemtext: - -Long lines get wrapped by the client to fit the screen -Short lines *don't* get joined together -Write paragraphs as single long lines -Blank lines are rendered verbatim -You get three levels of heading: - -# Heading -## Sub-heading -### Sub-subheading -You get one kind of list and you can't nest them: - -* Mercury -* Gemini -* Apollo -Here's a quote from Maciej Cegłowski: - -> I contend that text-based websites should not exceed in size the major works of Russian literature. -Lines which start with ``` will cause clients to toggle in and out of ordinary rendering mode and preformatted mode. In preformatted mode, Gemtext syntax is ignored so links etc. will not be rendered, and text will appear in a monospace font. -``` ALT TEXT - . - (' - '| - |' - [::] - [::] _......_ - [::].-' _.-`. - [:.' .-. '-._.-`. - [/ /\ | \ `-.. - / / | `-.' .-. `-. - / `-' ( `. `. - | /\ `-._/ \ - ' .'\ / `. _.-'| - / / / \_.-' _.':;:/ - .' \_/ _.-':;_.-' - / .-. _.-' \;.-' -/ ( \ _..-' | -\ `._/ _..-' .--. | - `-.....-'/ _ _ .' '.| - | |_|_| | | \ (o) - (o) | |_|_| | | | (\'/) - (\'/)/ ''''' | o| \;:; - :; | | | |/) - ;: `-.._ /__..--'\.' ;: - :; `--' :; :; -``` -=> https://proxy.vulpes.one/gemini/gemini.circumlunar.space/docs/cheatsheet.gmi Original cheatsheet -=> https://proxy.vulpes.one/gemini/gemini.circumlunar.space/docs/cheatsheet.gmi diff --git a/wiki/main/testgemini/meta.json b/wiki/main/testgemini/meta.json deleted file mode 100644 index 82e4bf3..0000000 --- a/wiki/main/testgemini/meta.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": null, - "name": "Testgemini", - "comment": "Create :Main/Testgemini", - "author": "", - "time": 1593960824, - "text_mime": "text/gemini", - "binary_mime": "", - "text_name": "1.txt", - "binary_name": "" - } - } -} \ No newline at end of file diff --git a/wiki/main/testmd/1.markdown b/wiki/main/testmd/1.markdown deleted file mode 100644 index c7ff99a..0000000 --- a/wiki/main/testmd/1.markdown +++ /dev/null @@ -1,232 +0,0 @@ - - -# h1 Heading 8-) -## h2 Heading -### h3 Heading -#### h4 Heading -##### h5 Heading -###### h6 Heading - - -## Horizontal Rules - -___ - ---- - -*** - - -## Typographic replacements - -Enable typographer option to see result. - -(c) (C) (r) (R) (tm) (TM) (p) (P) +- - -test.. test... test..... test?..... test!.... - -!!!!!! ???? ,, -- --- - -"Smartypants, double quotes" and 'single quotes' - - -## Emphasis - -**This is bold text** - -__This is bold text__ - -*This is italic text* - -_This is italic text_ - -~~Strikethrough~~ - - -## Blockquotes - - -> Blockquotes can also be nested... ->> ...by using additional greater-than signs right next to each other... -> > > ...or with spaces between arrows. - - -## Lists - -Unordered - -+ Create a list by starting a line with `+`, `-`, or `*` -+ Sub-lists are made by indenting 2 spaces: - - Marker character change forces new list start: - * Ac tristique libero volutpat at - + Facilisis in pretium nisl aliquet - - Nulla volutpat aliquam velit -+ Very easy! - -Ordered - -1. Lorem ipsum dolor sit amet -2. Consectetur adipiscing elit -3. Integer molestie lorem at massa - - -1. You can use sequential numbers... -1. ...or keep all the numbers as `1.` - -Start numbering with offset: - -57. foo -1. bar - - -## Code - -Inline `code` - -Indented code - - // Some comments - line 1 of code - line 2 of code - line 3 of code - - -Block code "fences" - -``` -Sample text here... -``` - -Syntax highlighting - -``` js -var foo = function (bar) { - return bar++; -}; - -console.log(foo(5)); -``` - -## Tables - -| Option | Description | -| ------ | ----------- | -| data | path to data files to supply the data that will be passed into templates. | -| engine | engine to be used for processing templates. Handlebars is the default. | -| ext | extension to be used for dest files. | - -Right aligned columns - -| Option | Description | -| ------:| -----------:| -| data | path to data files to supply the data that will be passed into templates. | -| engine | engine to be used for processing templates. Handlebars is the default. | -| ext | extension to be used for dest files. | - - -## Links - -[link text](http://dev.nodeca.com) - -[link with title](http://nodeca.github.io/pica/demo/ "title text!") - -Autoconverted link https://github.com/nodeca/pica (enable linkify to see) - - -## Images - -![Minion](https://octodex.github.com/images/minion.png) -![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat") - -Like links, Images also have a footnote style syntax - -![Alt text][id] - -With a reference later in the document defining the URL location: - -[id]: https://octodex.github.com/images/dojocat.jpg "The Dojocat" - - -## Plugins - -The killer feature of `markdown-it` is very effective support of -[syntax plugins](https://www.npmjs.org/browse/keyword/markdown-it-plugin). - - -### [Emojies](https://github.com/markdown-it/markdown-it-emoji) - -> Classic markup: :wink: :crush: :cry: :tear: :laughing: :yum: -> -> Shortcuts (emoticons): :-) :-( 8-) ;) - -see [how to change output](https://github.com/markdown-it/markdown-it-emoji#change-output) with twemoji. - - -### [Subscript](https://github.com/markdown-it/markdown-it-sub) / [Superscript](https://github.com/markdown-it/markdown-it-sup) - -- 19^th^ -- H~2~O - - -### [\](https://github.com/markdown-it/markdown-it-ins) - -++Inserted text++ - - -### [\](https://github.com/markdown-it/markdown-it-mark) - -==Marked text== - - -### [Footnotes](https://github.com/markdown-it/markdown-it-footnote) - -Footnote 1 link[^first]. - -Footnote 2 link[^second]. - -Inline footnote^[Text of inline footnote] definition. - -Duplicated footnote reference[^second]. - -[^first]: Footnote **can have markup** - - and multiple paragraphs. - -[^second]: Footnote text. - - -### [Definition lists](https://github.com/markdown-it/markdown-it-deflist) - -Term 1 - -: Definition 1 -with lazy continuation. - -Term 2 with *inline markup* - -: Definition 2 - - { some code, part of Definition 2 } - - Third paragraph of definition 2. - -_Compact style:_ - -Term 1 - ~ Definition 1 - -Term 2 - ~ Definition 2a - ~ Definition 2b - - -### [Abbreviations](https://github.com/markdown-it/markdown-it-abbr) - -This is HTML abbreviation example. - -It converts "HTML", but keep intact partial entries like "xxxHTMLyyy" and so on. - -*[HTML]: Hyper Text Markup Language - -### [Custom containers](https://github.com/markdown-it/markdown-it-container) - diff --git a/wiki/main/testmd/meta.json b/wiki/main/testmd/meta.json deleted file mode 100644 index 16f8db7..0000000 --- a/wiki/main/testmd/meta.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "" - ], - "name": "TestMd", - "comment": "Update TestMd", - "author": "", - "time": 1593340713, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "1.markdown", - "binary_name": "" - } - }, - "Invalid": false, - "Err": null -} \ No newline at end of file diff --git a/wiki/spec/README.md b/wiki/spec/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/wiki/sys/theme/1.markdown b/wiki/sys/theme/1.markdown deleted file mode 100644 index bf3de04..0000000 --- a/wiki/sys/theme/1.markdown +++ /dev/null @@ -1 +0,0 @@ -**TODO:** Reorganize templates. \ No newline at end of file diff --git a/wiki/sys/theme/2.markdown b/wiki/sys/theme/2.markdown deleted file mode 100644 index 7ddb176..0000000 --- a/wiki/sys/theme/2.markdown +++ /dev/null @@ -1,11 +0,0 @@ -# Themes - -In MycorrhizaWiki, themes are used to specify what is shown to a user, the front-end. So, a theme consists of: - -- **HTML templates.** Data is inserted into them. -- **CSS.** -- **JS.** - -Your HTML template structure must be identical to the one. You can include any CSS and JS in your themes. - -See [default-light](:sys/theme/default-light) theme for more information. This is the only theme that is guaranteed to work. diff --git a/wiki/sys/theme/3.markdown b/wiki/sys/theme/3.markdown deleted file mode 100644 index 275da3d..0000000 --- a/wiki/sys/theme/3.markdown +++ /dev/null @@ -1,11 +0,0 @@ -# Themes - -In MycorrhizaWiki, themes are used to specify what is shown to a user, the front-end. So, a theme consists of: - -- **HTML templates.** Data is inserted into them. -- **CSS.** -- **JS.** - -Your HTML template structure must be identical to the one. You can include any CSS and JS in your themes. - -See [default-light](/:sys/theme/default-light) theme for more information. This is the only theme that is guaranteed to work. diff --git a/wiki/sys/theme/default-light/base.html/1.html b/wiki/sys/theme/default-light/base.html/1.html deleted file mode 100644 index 511eed0..0000000 --- a/wiki/sys/theme/default-light/base.html/1.html +++ /dev/null @@ -1,24 +0,0 @@ - - - {{ .Title }} - - - - -
      - -

      - {{ .SiteTitle }} -

      - -
      - - {{ .Content }} -
      - {{index .Locale "this site runs mycowiki"}} -
      - - - diff --git a/wiki/sys/theme/default-light/base.html/meta.json b/wiki/sys/theme/default-light/base.html/meta.json deleted file mode 100644 index 4cf6420..0000000 --- a/wiki/sys/theme/default-light/base.html/meta.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "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/wiki/sys/theme/default-light/edit/index.html/1.html b/wiki/sys/theme/default-light/edit/index.html/1.html deleted file mode 100644 index db331e8..0000000 --- a/wiki/sys/theme/default-light/edit/index.html/1.html +++ /dev/null @@ -1,45 +0,0 @@ -
      - -
      -

      {{printf (index .Locale "edit/box/title") .Name}}

      -
      - {{index .Locale "edit/box"}} - -
      -
      -
      - diff --git a/wiki/sys/theme/default-light/edit/index.html/meta.json b/wiki/sys/theme/default-light/edit/index.html/meta.json deleted file mode 100644 index 36d0d70..0000000 --- a/wiki/sys/theme/default-light/edit/index.html/meta.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "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/wiki/sys/theme/default-light/main.css/1.css b/wiki/sys/theme/default-light/main.css/1.css deleted file mode 100644 index f0c19b6..0000000 --- a/wiki/sys/theme/default-light/main.css/1.css +++ /dev/null @@ -1,59 +0,0 @@ -*, *::before, *::after { box-sizing: border-box; } -html { height: 100%; } -body { font: 15px/1.5 'PT Sans', system-ui, sans-serif; - min-height: 100%; padding: 0; margin:0; } -.msg { background-color: #f4f4f4; padding: 1rem; border-radius: 1rem; } -a { color: #44e; } -a:visited { color: #44a; } -header { margin: 0 2rem; } -header * { display: inline; } -header h1 { margin: 0; font-size: 1rem; } -header a, header a:visited { color: black; text-decoration:none; } -header a:active, header a:hover { color: #005f87; } - -h1, h2, h3, h4, h5, h6 { margin: 0.5em 0 0.25em; } -code { background-color: #f4f4f4; } -.page { line-height: 1.666; max-width: 40rem; hyphens: auto; } -.page img { max-width:100%; } -.page pre { white-space: break-spaces; background-color: #f4f4f4; } -.page__title { font-size: 2rem; margin: 0; } - -footer { padding: 1rem 0; font-size: .8rem; } -footer a, footer a:visited { color: black; } -/* Sidebar section */ -.sidebar { padding: 1rem 0; background: #f4f4f4; } -.sidebar-controller { font: inherit; padding: .25rem 1rem; -font-size: 2rem; float: right; } - -.hypha-actions ul { margin: 0; padding: 0; } -.hypha-actions li { list-style: none; } -.hypha-actions a { display: block; padding: .25rem 1rem; font: inherit; -text-decoration: none; color: black; } -.hypha-actions a:hover { background: #eaeaea; } - -.navitree__node { padding-left: 2rem; } -.navitree__entry { margin-bottom: .5rem; } -.navitree__link, .navitree__link:visited { color:black; text-decoration:none; } -.navitree__link:hover, .navitree__link:active { text-decoration:underline; } -.navitree__ancestor { list-style: none; margin-left: -1rem; } -.navitree__pagename a { font-weight: bold; } - -.edit-box aside { padding-left: 1rem; } - -@media (max-width: 950px) { - .hidden_mobile { display: none; } - aside { height: 100%; } - main, footer, header { margin: 0 1rem; } - header, header * { display:inline; } - .edit-box__text { width: 100%; height: 70%; } -} -@media (min-width: 950px) { - .sidebar-controller { display: none; } - aside { float:right; width: 300px; padding: 0; } - main, footer { margin: 0 0 auto 2rem; } - .edit-box__text { min-width: 600px; height: 70%; } -} - -legend { font-weight: bold; padding: 0; } -fieldset { border: 0; padding: 0; margin: 0; margin-bottom: .5rem; } -fieldset * { margin: 0; } diff --git a/wiki/sys/theme/default-light/main.css/meta.json b/wiki/sys/theme/default-light/main.css/meta.json deleted file mode 100644 index c34401f..0000000 --- a/wiki/sys/theme/default-light/main.css/meta.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "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/wiki/sys/theme/default-light/main.js/1.mjs b/wiki/sys/theme/default-light/main.js/1.mjs deleted file mode 100644 index bf9b3a5..0000000 --- a/wiki/sys/theme/default-light/main.js/1.mjs +++ /dev/null @@ -1,11 +0,0 @@ -var isOpen = false -var sidebar = document.getElementById('sidebar') -var btn = document.getElementById('shroomburger') -btn.addEventListener('click', function() { - if (isOpen) { - sidebar.classList.add('hidden_mobile') - } else { - sidebar.classList.remove('hidden_mobile') - } - isOpen = !isOpen -}) diff --git a/wiki/sys/theme/default-light/main.js/meta.json b/wiki/sys/theme/default-light/main.js/meta.json deleted file mode 100644 index 98d4ad5..0000000 --- a/wiki/sys/theme/default-light/main.js/meta.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "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/wiki/sys/theme/default-light/update_ok.html/1.html b/wiki/sys/theme/default-light/update_ok.html/1.html deleted file mode 100644 index 4805dac..0000000 --- a/wiki/sys/theme/default-light/update_ok.html/1.html +++ /dev/null @@ -1,9 +0,0 @@ - - - {{printf (index .Locale "update ok/title") .Name}} - - - -

      {{printf (index .Locale "update ok/msg") .Name}}

      - - diff --git a/wiki/sys/theme/default-light/update_ok.html/meta.json b/wiki/sys/theme/default-light/update_ok.html/meta.json deleted file mode 100644 index b1ac640..0000000 --- a/wiki/sys/theme/default-light/update_ok.html/meta.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": null, - "name": "update_ok.html", - "comment": "Create Templates/default-light/updateOk.html", - "author": "", - "time": 1592996644, - "text_mime": "text/html", - "binary_mime": "", - "text_name": "1.html", - "binary_name": "" - } - } -} diff --git a/wiki/sys/theme/default-light/view/404.html/1.html b/wiki/sys/theme/default-light/view/404.html/1.html deleted file mode 100644 index d8cf4a5..0000000 --- a/wiki/sys/theme/default-light/view/404.html/1.html +++ /dev/null @@ -1,18 +0,0 @@ - -
      -
      -

      {{ .PageTitle }}

      -

      - The hypha you are trying to access does not exist yet. Why not create it? -

      -
      -
      - - diff --git a/wiki/sys/theme/default-light/view/404.html/meta.json b/wiki/sys/theme/default-light/view/404.html/meta.json deleted file mode 100644 index 1c22d20..0000000 --- a/wiki/sys/theme/default-light/view/404.html/meta.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "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/wiki/sys/theme/default-light/view/index.html/1.html b/wiki/sys/theme/default-light/view/index.html/1.html deleted file mode 100644 index f053efb..0000000 --- a/wiki/sys/theme/default-light/view/index.html/1.html +++ /dev/null @@ -1,14 +0,0 @@ - -
      - {{ .Content }} -
      diff --git a/wiki/sys/theme/default-light/view/index.html/meta.json b/wiki/sys/theme/default-light/view/index.html/meta.json deleted file mode 100644 index 4b75feb..0000000 --- a/wiki/sys/theme/default-light/view/index.html/meta.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "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/wiki/sys/theme/meta.json b/wiki/sys/theme/meta.json deleted file mode 100644 index 53e4e9c..0000000 --- a/wiki/sys/theme/meta.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "views": 0, - "deleted": false, - "revisions": { - "1": { - "tags": [ - "" - ], - "name": "Templates", - "comment": "Update Templates", - "author": "", - "time": 1593194769, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "1.markdown", - "binary_name": "" - }, - "2": { - "tags": [ - "" - ], - "name": "Theme", - "comment": "Update Sys/Theme", - "author": "", - "time": 1593802668, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "2.markdown", - "binary_name": "" - }, - "3": { - "tags": [ - "" - ], - "name": "Theme", - "comment": "Update Sys/Theme", - "author": "", - "time": 1593802769, - "text_mime": "text/markdown", - "binary_mime": "", - "text_name": "3.markdown", - "binary_name": "" - } - } -} \ No newline at end of file diff --git a/wiki/tag/README.md b/wiki/tag/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/wiki/user/README.md b/wiki/user/README.md deleted file mode 100644 index e69de29..0000000