@ -1,2 +1,6 @@
|
|||||||
# mycorrhiza wiki
|
# mycorrhiza wiki
|
||||||
Simple wiki engine inspired by fungi.
|
A wiki engine inspired by fungi.
|
||||||
|
|
||||||
|
Current version: 0.5.
|
||||||
|
|
||||||
|
Not production-ready.
|
||||||
|
|||||||
@ -8,17 +8,23 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type MyceliumConfig struct {
|
||||||
|
Names []string `json:"names"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HyphaPattern = `[^\s\d:/?&\\][^:?&\\]*`
|
HyphaPattern = `[^\s\d:/?&\\][^:?&\\]*`
|
||||||
HyphaUrl = `/{hypha:` + HyphaPattern + `}`
|
HyphaUrl = `/{hypha:` + HyphaPattern + `}`
|
||||||
RevisionPattern = `[\d]+`
|
RevisionPattern = `[\d]+`
|
||||||
RevQuery = `{rev:` + RevisionPattern + `}`
|
RevQuery = `{rev:` + RevisionPattern + `}`
|
||||||
|
MyceliumPattern = `[^\s\d:/?&\\][^:?&\\/]*`
|
||||||
|
MyceliumUrl = `/:{mycelium:` + MyceliumPattern + `}`
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DescribeHyphaHerePattern = "Describe %s here"
|
DescribeHyphaHerePattern = "Describe %s here"
|
||||||
WikiDir string
|
WikiDir string
|
||||||
TemplatesDir string
|
|
||||||
configJsonPath string
|
configJsonPath string
|
||||||
|
|
||||||
// Default values that can be overriden in config.json
|
// Default values that can be overriden in config.json
|
||||||
@ -28,24 +34,25 @@ var (
|
|||||||
GenericErrorMsg = `<b>Sorry, something went wrong</b>`
|
GenericErrorMsg = `<b>Sorry, something went wrong</b>`
|
||||||
SiteTitle = `MycorrhizaWiki`
|
SiteTitle = `MycorrhizaWiki`
|
||||||
Theme = `default-light`
|
Theme = `default-light`
|
||||||
|
Mycelia = []MyceliumConfig{
|
||||||
|
{[]string{"main"}, "main"},
|
||||||
|
{[]string{"sys", "system"}, "system"},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitConfig(wd string) bool {
|
func InitConfig(wd string) bool {
|
||||||
log.Println("WikiDir is", wd)
|
log.Println("WikiDir is", wd)
|
||||||
WikiDir = wd
|
WikiDir = wd
|
||||||
TemplatesDir = "Templates"
|
configJsonPath = filepath.Join(WikiDir, "config.json")
|
||||||
configJsonPath = filepath.Join(filepath.Dir(WikiDir), "config.json")
|
|
||||||
|
|
||||||
if _, err := os.Stat(configJsonPath); os.IsNotExist(err) {
|
if _, err := os.Stat(configJsonPath); os.IsNotExist(err) {
|
||||||
log.Println("config.json not found, using default values")
|
log.Println("config.json not found, using default values")
|
||||||
} else {
|
return false
|
||||||
|
}
|
||||||
log.Println("config.json found, overriding default values...")
|
log.Println("config.json found, overriding default values...")
|
||||||
return readConfig()
|
return readConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func readConfig() bool {
|
func readConfig() bool {
|
||||||
configJsonContents, err := ioutil.ReadFile(configJsonPath)
|
configJsonContents, err := ioutil.ReadFile(configJsonPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -57,6 +64,7 @@ func readConfig() bool {
|
|||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Theme string `json:"theme"`
|
Theme string `json:"theme"`
|
||||||
SiteTitle string `json:"site-title"`
|
SiteTitle string `json:"site-title"`
|
||||||
|
Mycelia []MyceliumConfig `json:"mycelia"`
|
||||||
TitleTemplates struct {
|
TitleTemplates struct {
|
||||||
EditHypha string `json:"edit-hypha"`
|
EditHypha string `json:"edit-hypha"`
|
||||||
ViewHypha string `json:"view-hypha"`
|
ViewHypha string `json:"view-hypha"`
|
||||||
@ -74,6 +82,7 @@ func readConfig() bool {
|
|||||||
SiteTitle = cfg.SiteTitle
|
SiteTitle = cfg.SiteTitle
|
||||||
TitleEditTemplate = cfg.TitleTemplates.EditHypha
|
TitleEditTemplate = cfg.TitleTemplates.EditHypha
|
||||||
TitleTemplate = cfg.TitleTemplates.ViewHypha
|
TitleTemplate = cfg.TitleTemplates.ViewHypha
|
||||||
|
Mycelia = cfg.Mycelia
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,14 +10,19 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/cfg"
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *Hypha) MetaJsonPath() string {
|
func (h *Hypha) MetaJsonPath() string {
|
||||||
return filepath.Join(h.Path(), "meta.json")
|
return filepath.Join(h.Path(), "meta.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Hypha) CanonicalName() string {
|
||||||
|
return util.UrlToCanonical(h.FullName)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Hypha) Path() string {
|
func (h *Hypha) Path() string {
|
||||||
return filepath.Join(cfg.WikiDir, h.FullName)
|
return filepath.Join(cfg.WikiDir, h.CanonicalName())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hypha) TextPath() string {
|
func (h *Hypha) TextPath() string {
|
||||||
@ -25,7 +30,7 @@ func (h *Hypha) TextPath() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hypha) parentName() string {
|
func (h *Hypha) parentName() string {
|
||||||
return filepath.Dir(h.FullName)
|
return filepath.Dir(util.UrlToCanonical(h.FullName))
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasBinaryData returns true if the revision has any binary data associated.
|
// hasBinaryData returns true if the revision has any binary data associated.
|
||||||
|
|||||||
3
fs/fs.go
@ -54,6 +54,3 @@ func (s *Storage) indexHyphae(path string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hypha) Close() {
|
|
||||||
}
|
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Storage) RenderHypha(h *Hypha) {
|
func (s *Storage) RenderHypha(h *Hypha) {
|
||||||
@ -24,6 +26,7 @@ type Tree struct {
|
|||||||
// It can also generate trees for non-existent hyphae, that's why we use `name string` instead of making it a method on `Hypha`.
|
// 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.
|
// In `root` is `false`, siblings will not be fetched.
|
||||||
func (s *Storage) GetTree(name string, root bool) *Tree {
|
func (s *Storage) GetTree(name string, root bool) *Tree {
|
||||||
|
name = util.UrlToCanonical(name)
|
||||||
t := &Tree{Name: name, Root: root}
|
t := &Tree{Name: name, Root: root}
|
||||||
for hyphaName, _ := range s.paths {
|
for hyphaName, _ := range s.paths {
|
||||||
s.compareNamesAndAppend(t, hyphaName)
|
s.compareNamesAndAppend(t, hyphaName)
|
||||||
@ -65,14 +68,14 @@ func (t *Tree) AsHtml() (html string) {
|
|||||||
html += `<ul class="navitree__node">`
|
html += `<ul class="navitree__node">`
|
||||||
if t.Root {
|
if t.Root {
|
||||||
for _, ancestor := range t.Ancestors {
|
for _, ancestor := range t.Ancestors {
|
||||||
html += navitreeEntry(ancestor, "navitree__ancestor")
|
html += navitreeEntry(util.CanonicalToDisplay(ancestor), "navitree__ancestor")
|
||||||
}
|
}
|
||||||
for _, siblingName := range t.Siblings {
|
for _, siblingName := range t.Siblings {
|
||||||
html += navitreeEntry(siblingName, "navitree__sibling")
|
html += navitreeEntry(util.CanonicalToDisplay(siblingName), "navitree__sibling")
|
||||||
}
|
}
|
||||||
html += navitreeEntry(t.Name, "navitree__pagename")
|
html += navitreeEntry(util.CanonicalToDisplay(t.Name), "navitree__pagename")
|
||||||
} else {
|
} else {
|
||||||
html += navitreeEntry(t.Name, "navitree__name")
|
html += navitreeEntry(util.CanonicalToDisplay(t.Name), "navitree__name")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, subtree := range t.Descendants {
|
for _, subtree := range t.Descendants {
|
||||||
@ -89,5 +92,5 @@ func navitreeEntry(name, class string) string {
|
|||||||
return fmt.Sprintf(`<li class="navitree__entry %s">
|
return fmt.Sprintf(`<li class="navitree__entry %s">
|
||||||
<a class="navitree__link" href="/%s">%s</a>
|
<a class="navitree__link" href="/%s">%s</a>
|
||||||
</li>
|
</li>
|
||||||
`, class, name, filepath.Base(name))
|
`, class, ":"+util.DisplayToCanonical(name), filepath.Base(name))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
"gopkg.in/russross/blackfriday.v2"
|
"gopkg.in/russross/blackfriday.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ func (h *Hypha) asHtml() (string, error) {
|
|||||||
`
|
`
|
||||||
// What about using <figure>?
|
// What about using <figure>?
|
||||||
if h.hasBinaryData() {
|
if h.hasBinaryData() {
|
||||||
ret += fmt.Sprintf(`<img src="/%s?action=binary&rev=%d" class="page__amnt"/>`, rev.FullName, rev.Id)
|
ret += fmt.Sprintf(`<img src="/:%s?action=binary&rev=%d" class="page__amnt"/>`, util.DisplayToCanonical(rev.FullName), rev.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
contents, err := ioutil.ReadFile(rev.TextPath)
|
contents, err := ioutil.ReadFile(rev.TextPath)
|
||||||
|
|||||||
35
fs/hypha.go
@ -12,7 +12,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/cfg"
|
"github.com/bouncepaw/mycorrhiza/mycelium"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Hypha struct {
|
type Hypha struct {
|
||||||
@ -22,8 +23,8 @@ type Hypha struct {
|
|||||||
Deleted bool `json:"deleted"`
|
Deleted bool `json:"deleted"`
|
||||||
Revisions map[string]*Revision `json:"revisions"`
|
Revisions map[string]*Revision `json:"revisions"`
|
||||||
actual *Revision `json:"-"`
|
actual *Revision `json:"-"`
|
||||||
Invalid bool
|
Invalid bool `json:"-"`
|
||||||
Err error
|
Err error `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hypha) Invalidate(err error) *Hypha {
|
func (h *Hypha) Invalidate(err error) *Hypha {
|
||||||
@ -32,10 +33,20 @@ func (h *Hypha) Invalidate(err error) *Hypha {
|
|||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Storage) Open(name string) *Hypha {
|
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{
|
h := &Hypha{
|
||||||
Exists: true,
|
Exists: true,
|
||||||
FullName: name,
|
FullName: util.CanonicalToDisplay(name),
|
||||||
}
|
}
|
||||||
path, ok := s.paths[name]
|
path, ok := s.paths[name]
|
||||||
// This hypha does not exist yet
|
// This hypha does not exist yet
|
||||||
@ -109,6 +120,7 @@ func (h *Hypha) ActionRaw(w http.ResponseWriter) *Hypha {
|
|||||||
if h.Invalid {
|
if h.Invalid {
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
if h.Exists {
|
||||||
fileContents, err := ioutil.ReadFile(h.actual.TextPath)
|
fileContents, err := ioutil.ReadFile(h.actual.TextPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return h.Invalidate(err)
|
return h.Invalidate(err)
|
||||||
@ -116,6 +128,10 @@ func (h *Hypha) ActionRaw(w http.ResponseWriter) *Hypha {
|
|||||||
w.Header().Set("Content-Type", h.mimeTypeForActionRaw())
|
w.Header().Set("Content-Type", h.mimeTypeForActionRaw())
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write(fileContents)
|
w.Write(fileContents)
|
||||||
|
} else {
|
||||||
|
log.Println("Hypha", h.FullName, "has no actual revision")
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,6 +141,7 @@ func (h *Hypha) ActionBinary(w http.ResponseWriter) *Hypha {
|
|||||||
if h.Invalid {
|
if h.Invalid {
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
if h.Exists {
|
||||||
fileContents, err := ioutil.ReadFile(h.actual.BinaryPath)
|
fileContents, err := ioutil.ReadFile(h.actual.BinaryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return h.Invalidate(err)
|
return h.Invalidate(err)
|
||||||
@ -132,6 +149,10 @@ func (h *Hypha) ActionBinary(w http.ResponseWriter) *Hypha {
|
|||||||
w.Header().Set("Content-Type", h.actual.BinaryMime)
|
w.Header().Set("Content-Type", h.actual.BinaryMime)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write(fileContents)
|
w.Write(fileContents)
|
||||||
|
} else {
|
||||||
|
log.Println("Hypha", h.FullName, "has no actual revision")
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +202,7 @@ func (h *Hypha) CreateDirIfNeeded() *Hypha {
|
|||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
// os.MkdirAll created dir if it is not there. Basically, checks it for us.
|
// os.MkdirAll created dir if it is not there. Basically, checks it for us.
|
||||||
err := os.MkdirAll(filepath.Join(cfg.WikiDir, h.FullName), os.ModePerm)
|
err := os.MkdirAll(h.Path(), os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.Invalidate(err)
|
h.Invalidate(err)
|
||||||
}
|
}
|
||||||
@ -313,7 +334,7 @@ func (h *Hypha) SaveJson() *Hypha {
|
|||||||
// Store adds `h` to the `Hs` if it is not already there
|
// Store adds `h` to the `Hs` if it is not already there
|
||||||
func (h *Hypha) Store() *Hypha {
|
func (h *Hypha) Store() *Hypha {
|
||||||
if !h.Invalid {
|
if !h.Invalid {
|
||||||
Hs.paths[h.FullName] = h.Path()
|
Hs.paths[h.CanonicalName()] = h.Path()
|
||||||
}
|
}
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import (
|
|||||||
// Boilerplate code present in many handlers. Good to have it.
|
// Boilerplate code present in many handlers. Good to have it.
|
||||||
func HandlerBase(w http.ResponseWriter, rq *http.Request) *fs.Hypha {
|
func HandlerBase(w http.ResponseWriter, rq *http.Request) *fs.Hypha {
|
||||||
vars := mux.Vars(rq)
|
vars := mux.Vars(rq)
|
||||||
return fs.Hs.Open(vars["hypha"]).OnRevision(RevInMap(vars))
|
return fs.Hs.OpenFromMap(vars).OnRevision(RevInMap(vars))
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandlerRaw(w http.ResponseWriter, rq *http.Request) {
|
func HandlerRaw(w http.ResponseWriter, rq *http.Request) {
|
||||||
@ -41,7 +41,7 @@ func HandlerView(w http.ResponseWriter, rq *http.Request) {
|
|||||||
|
|
||||||
func HandlerEdit(w http.ResponseWriter, rq *http.Request) {
|
func HandlerEdit(w http.ResponseWriter, rq *http.Request) {
|
||||||
vars := mux.Vars(rq)
|
vars := mux.Vars(rq)
|
||||||
h := fs.Hs.Open(vars["hypha"]).OnRevision("0")
|
h := fs.Hs.OpenFromMap(vars).OnRevision("0")
|
||||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write(render.HyphaEdit(h))
|
w.Write(render.HyphaEdit(h))
|
||||||
@ -51,7 +51,7 @@ func HandlerUpdate(w http.ResponseWriter, rq *http.Request) {
|
|||||||
vars := mux.Vars(rq)
|
vars := mux.Vars(rq)
|
||||||
log.Println("Attempt to update hypha", vars["hypha"])
|
log.Println("Attempt to update hypha", vars["hypha"])
|
||||||
h := fs.Hs.
|
h := fs.Hs.
|
||||||
Open(vars["hypha"]).
|
OpenFromMap(vars).
|
||||||
CreateDirIfNeeded().
|
CreateDirIfNeeded().
|
||||||
AddRevisionFromHttpData(rq).
|
AddRevisionFromHttpData(rq).
|
||||||
WriteTextFileFromHttpData(rq).
|
WriteTextFileFromHttpData(rq).
|
||||||
|
|||||||
53
main.go
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/cfg"
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
"github.com/bouncepaw/mycorrhiza/fs"
|
"github.com/bouncepaw/mycorrhiza/fs"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/mycelium"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,6 +23,25 @@ func RevInMap(m map[string]string) string {
|
|||||||
return "0"
|
return "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) == 1 {
|
if len(os.Args) == 1 {
|
||||||
panic("Expected a root wiki pages directory")
|
panic("Expected a root wiki pages directory")
|
||||||
@ -34,6 +54,7 @@ func main() {
|
|||||||
log.Println("Welcome to MycorrhizaWiki α")
|
log.Println("Welcome to MycorrhizaWiki α")
|
||||||
cfg.InitConfig(wikiDir)
|
cfg.InitConfig(wikiDir)
|
||||||
log.Println("Indexing hyphae...")
|
log.Println("Indexing hyphae...")
|
||||||
|
mycelium.Init()
|
||||||
fs.InitStorage()
|
fs.InitStorage()
|
||||||
|
|
||||||
// Start server code. See handlers.go for handlers' implementations.
|
// Start server code. See handlers.go for handlers' implementations.
|
||||||
@ -43,32 +64,22 @@ func main() {
|
|||||||
http.ServeFile(w, rq, filepath.Join(filepath.Dir(cfg.WikiDir), "favicon.ico"))
|
http.ServeFile(w, rq, filepath.Join(filepath.Dir(cfg.WikiDir), "favicon.ico"))
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Queries("action", "binary", "rev", cfg.RevQuery).Path(cfg.HyphaUrl).
|
IdempotentRouterBoiler(r, "binary", HandlerBinary)
|
||||||
HandlerFunc(HandlerBinary)
|
IdempotentRouterBoiler(r, "raw", HandlerRaw)
|
||||||
r.Queries("action", "binary").Path(cfg.HyphaUrl).
|
IdempotentRouterBoiler(r, "zen", HandlerZen)
|
||||||
HandlerFunc(HandlerBinary)
|
IdempotentRouterBoiler(r, "view", HandlerView)
|
||||||
|
|
||||||
r.Queries("action", "raw", "rev", cfg.RevQuery).Path(cfg.HyphaUrl).
|
|
||||||
HandlerFunc(HandlerRaw)
|
|
||||||
r.Queries("action", "raw").Path(cfg.HyphaUrl).
|
|
||||||
HandlerFunc(HandlerRaw)
|
|
||||||
|
|
||||||
r.Queries("action", "zen", "rev", cfg.RevQuery).Path(cfg.HyphaUrl).
|
|
||||||
HandlerFunc(HandlerZen)
|
|
||||||
r.Queries("action", "zen").Path(cfg.HyphaUrl).
|
|
||||||
HandlerFunc(HandlerZen)
|
|
||||||
|
|
||||||
r.Queries("action", "view", "rev", cfg.RevQuery).Path(cfg.HyphaUrl).
|
|
||||||
HandlerFunc(HandlerView)
|
|
||||||
r.Queries("action", "view").Path(cfg.HyphaUrl).
|
|
||||||
HandlerFunc(HandlerView)
|
|
||||||
|
|
||||||
|
r.Queries("action", "edit").Path(cfg.MyceliumUrl + cfg.HyphaUrl).
|
||||||
|
HandlerFunc(HandlerEdit)
|
||||||
r.Queries("action", "edit").Path(cfg.HyphaUrl).
|
r.Queries("action", "edit").Path(cfg.HyphaUrl).
|
||||||
HandlerFunc(HandlerEdit)
|
HandlerFunc(HandlerEdit)
|
||||||
|
|
||||||
r.Queries("action", "update").Path(cfg.HyphaUrl).Methods("POST").
|
r.Queries("action", "update").Path(cfg.MyceliumUrl + cfg.HyphaUrl).
|
||||||
HandlerFunc(HandlerUpdate)
|
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(cfg.HyphaUrl, HandlerView)
|
||||||
|
|
||||||
// Debug page that renders all hyphae.
|
// Debug page that renders all hyphae.
|
||||||
|
|||||||
107
mycelium/mycelium.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
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:"
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/cfg"
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||||
"github.com/bouncepaw/mycorrhiza/fs"
|
"github.com/bouncepaw/mycorrhiza/fs"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/mycelium"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HyphaEdit renders hypha editor.
|
// HyphaEdit renders hypha editor.
|
||||||
@ -29,7 +30,7 @@ func HyphaEdit(h *fs.Hypha) []byte { //
|
|||||||
|
|
||||||
// HyphaUpdateOk is used to inform that update was successful.
|
// HyphaUpdateOk is used to inform that update was successful.
|
||||||
func HyphaUpdateOk(h *fs.Hypha) []byte { //
|
func HyphaUpdateOk(h *fs.Hypha) []byte { //
|
||||||
return layout("updateOk").
|
return layout("update_ok").
|
||||||
withMap(map[string]string{"Name": h.FullName}).
|
withMap(map[string]string{"Name": h.FullName}).
|
||||||
Bytes()
|
Bytes()
|
||||||
}
|
}
|
||||||
@ -56,6 +57,9 @@ func hyphaGeneric(name, content, templateName string) []byte {
|
|||||||
|
|
||||||
// wrapInBase is used to wrap layouts in things that are present on all pages.
|
// wrapInBase is used to wrap layouts in things that are present on all pages.
|
||||||
func (lyt *Layout) wrapInBase(keys map[string]string) []byte {
|
func (lyt *Layout) wrapInBase(keys map[string]string) []byte {
|
||||||
|
if lyt.invalid {
|
||||||
|
return lyt.Bytes()
|
||||||
|
}
|
||||||
page := map[string]string{
|
page := map[string]string{
|
||||||
"Title": cfg.SiteTitle,
|
"Title": cfg.SiteTitle,
|
||||||
"Main": "",
|
"Main": "",
|
||||||
@ -82,7 +86,12 @@ type Layout struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func layout(name string) *Layout {
|
func layout(name string) *Layout {
|
||||||
h := fs.Hs.Open(path.Join(cfg.TemplatesDir, cfg.Theme, name+".html")).OnRevision("0")
|
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 {
|
if h.Invalid {
|
||||||
return &Layout{nil, nil, true, h.Err}
|
return &Layout{nil, nil, true, h.Err}
|
||||||
}
|
}
|
||||||
|
|||||||
49
util/util.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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)
|
||||||
|
}
|
||||||
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"address": "127.0.0.1:1737",
|
|
||||||
"theme": "default-light",
|
|
||||||
"site-title": "🍄 MycorrhizaWiki",
|
|
||||||
"title-templates": {
|
|
||||||
"edit-hypha": "Edit %s at MycorrhizaWiki",
|
|
||||||
"view-hypha": "%s at MycorrhizaWiki"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
<div class="naviwrapper">
|
|
||||||
<form class="naviwrapper__edit edit-box"
|
|
||||||
method="POST"
|
|
||||||
enctype="multipart/form-data"
|
|
||||||
action="?action=update">
|
|
||||||
<h4>Edit box</h4>
|
|
||||||
<!-- It is important that there is no indent ↓ -->
|
|
||||||
<textarea class="edit-box__text" name="text">
|
|
||||||
{{ .Text }}</textarea>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
<div style=""><h4>Text MIME-type</h4>
|
|
||||||
<p>Good types are <code>text/markdown</code> and <code>text/plain</code></p>
|
|
||||||
<input type="text" name="text_mime" value="{{ .TextMime }}" form="edit-form"/>
|
|
||||||
|
|
||||||
<h4>Revision comment</h4>
|
|
||||||
<p>Please make your comment helpful</p>
|
|
||||||
<input type="text" name="comment" value="Update {{ .Name }}" form="edit-form"/>
|
|
||||||
|
|
||||||
<h4>Edit tags</h4>
|
|
||||||
<p>Tags are separated by commas, whitespace is ignored</p>
|
|
||||||
<input type="text" name="tags" value="{{ .Tags }}" form="edit-form"/>
|
|
||||||
|
|
||||||
<h4>Upload file</h4>
|
|
||||||
<p>If this hypha has a file like that, the text above is meant to be a description of it</p>
|
|
||||||
<input type="file" name="binary" form="edit-form"/>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<input type="submit" value="update" form="edit-form"/>
|
|
||||||
<a href="?">Cancel</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
<h1 class="page__title">{{ . }}</h1>
|
|
||||||
<p class="msg_hypha-does-not-exist msg">
|
|
||||||
The hypha you are trying to access does not exist yet. Why not <a href="?action=edit">create</a> it?
|
|
||||||
</p>
|
|
||||||
31
wiki/config.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"address": "127.0.0.1:1737",
|
||||||
|
"theme": "default-light",
|
||||||
|
"site-title": "🍄 MycorrhizaWiki",
|
||||||
|
"title-templates": {
|
||||||
|
"edit-hypha": "Edit %s at MycorrhizaWiki",
|
||||||
|
"view-hypha": "%s at MycorrhizaWiki"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 427 KiB After Width: | Height: | Size: 427 KiB |
|
Before Width: | Height: | Size: 608 KiB After Width: | Height: | Size: 608 KiB |
1
wiki/main/help/1.markdown
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Help
|
||||||
@ -6,10 +6,10 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
"name": "Templates",
|
"name": "Help",
|
||||||
"comment": "Update Templates",
|
"comment": "Update Help",
|
||||||
"author": "",
|
"author": "",
|
||||||
"time": 1593194769,
|
"time": 1593540573,
|
||||||
"text_mime": "text/markdown",
|
"text_mime": "text/markdown",
|
||||||
"binary_mime": "",
|
"binary_mime": "",
|
||||||
"text_name": "1.markdown",
|
"text_name": "1.markdown",
|
||||||
85
wiki/main/help/mycelia/1.markdown
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
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, <empty prefix>
|
||||||
|
...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.
|
||||||
|
|
||||||
21
wiki/main/help/mycelia/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
11
wiki/sys/theme/2.markdown
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# 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.
|
||||||
11
wiki/sys/theme/3.markdown
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# 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.
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>{{ .Title }}</title>
|
<title>{{ .Title }}</title>
|
||||||
<link rel="stylesheet" href="/Templates/default-light/main.css?action=raw">
|
<link rel="stylesheet" href="/:sys/theme/default-light/main.css?action=raw">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="header">
|
<header class="header">
|
||||||
@ -20,6 +20,6 @@
|
|||||||
<footer>
|
<footer>
|
||||||
<p>This website runs <a href='https://github.com/bouncepaw/mycorrhiza'>MycorrhizaWiki</a></p>
|
<p>This website runs <a href='https://github.com/bouncepaw/mycorrhiza'>MycorrhizaWiki</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
<script src="/Templates/default-light/main.js?action=raw"></script>
|
<script src="/:sys/theme/default-light/main.js?action=raw"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
11
wiki/sys/theme/default-light/edit/index.html/1.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<form class="edit-box"
|
||||||
|
method="POST"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
action="?action=update"
|
||||||
|
id="edit-form">
|
||||||
|
<h4>Edit box</h4>
|
||||||
|
<!-- It is important that there is no indent ↓ -->
|
||||||
|
<textarea class="edit-box__text" name="text">
|
||||||
|
{{ .Text }}</textarea>
|
||||||
|
</form>
|
||||||
|
|
||||||
22
wiki/sys/theme/default-light/edit/sidebar.html/1.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<div>
|
||||||
|
<h4>Text MIME-type</h4>
|
||||||
|
<p>Good types are <code>text/markdown</code> and <code>text/plain</code></p>
|
||||||
|
<input type="text" name="text_mime" value="{{ .TextMime }}" form="edit-form"/>
|
||||||
|
|
||||||
|
<h4>Revision comment</h4>
|
||||||
|
<p>Please make your comment helpful</p>
|
||||||
|
<input type="text" name="comment" value="Update {{ .Name }}" form="edit-form"/>
|
||||||
|
|
||||||
|
<h4>Edit tags</h4>
|
||||||
|
<p>Tags are separated by commas, whitespace is ignored</p>
|
||||||
|
<input type="text" name="tags" value="{{ .Tags }}" form="edit-form"/>
|
||||||
|
|
||||||
|
<h4>Upload file</h4>
|
||||||
|
<p>If this hypha has a file like that, the text above is meant to be a description of it</p>
|
||||||
|
<input type="file" name="binary" form="edit-form"/>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<input type="submit" value="update" form="edit-form"/>
|
||||||
|
<a href="?">Cancel</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
@ -12,12 +12,13 @@ header a, header a:visited { color: black; text-decoration:none; }
|
|||||||
header a:active, header a:hover { color: #005f87; }
|
header a:active, header a:hover { color: #005f87; }
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 { margin: 0.5em 0 0.25em; }
|
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 { line-height: 1.666; max-width: 40rem; hyphens: auto; }
|
||||||
.page img { max-width:100%; }
|
.page img { max-width:100%; }
|
||||||
.page pre { white-space: break-spaces; }
|
.page pre { white-space: break-spaces; background-color: #f4f4f4; }
|
||||||
.page__title { font-size: 2rem; margin: 0; }
|
.page__title { font-size: 2rem; margin: 0; }
|
||||||
|
|
||||||
footer { padding: 1rem 0; font-size: .8rem; bottom: 0; position: absolute; }
|
footer { padding: 1rem 0; font-size: .8rem; }
|
||||||
footer a, footer a:visited { color: black; }
|
footer a, footer a:visited { color: black; }
|
||||||
/* Sidebar section */
|
/* Sidebar section */
|
||||||
.sidebar { padding: 1rem 0; background: #f4f4f4; }
|
.sidebar { padding: 1rem 0; background: #f4f4f4; }
|
||||||
@ -4,7 +4,7 @@
|
|||||||
"revisions": {
|
"revisions": {
|
||||||
"1": {
|
"1": {
|
||||||
"tags": null,
|
"tags": null,
|
||||||
"name": "updateOk.html",
|
"name": "update_ok.html",
|
||||||
"comment": "Create Templates/default-light/updateOk.html",
|
"comment": "Create Templates/default-light/updateOk.html",
|
||||||
"author": "",
|
"author": "",
|
||||||
"time": 1592996644,
|
"time": 1592996644,
|
||||||
6
wiki/sys/theme/default-light/view/404.html/1.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<article class="page page404">
|
||||||
|
<h1 class="page__title">{{ . }}</h1>
|
||||||
|
<p class="msg_hypha-does-not-exist msg">
|
||||||
|
The hypha you are trying to access does not exist yet. Why not <a href="?action=edit">create</a> it?
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<div class="hypha-actions">
|
<div class="hypha-actions">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="?action=edit">Edit</a></li>
|
<li><a href="?action=edit">Edit</a></li>
|
||||||
<li><a href="?action=getBinary">Download</a></li>
|
<li><a href="?action=binary">Download</a></li>
|
||||||
<li><a href="?action=zen">Zen mode</a></li>
|
<li><a href="?action=zen">Zen mode</a></li>
|
||||||
<li><a href="?action=raw">View raw</a></li>
|
<li><a href="?action=raw">View raw</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
45
wiki/sys/theme/meta.json
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"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": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||