mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2024-12-12 13:30:26 +00:00
Merge remote-tracking branch 'origin/master' into features/templates-from-html
This commit is contained in:
commit
5ccaacb6d9
@ -3,9 +3,10 @@ package main
|
|||||||
const (
|
const (
|
||||||
TitleTemplate = `%s at MycorrhizaWiki`
|
TitleTemplate = `%s at MycorrhizaWiki`
|
||||||
DefaultTitle = "MycorrhizaWiki"
|
DefaultTitle = "MycorrhizaWiki"
|
||||||
DefaultHeaderText = `MycorrhizaWiki`
|
DefaultHeaderText = `MycorrhizaWiki 🍄`
|
||||||
DefaultFooterText = "MycorrhizaWiki"
|
DefaultFooterText = `This website runs <a href="https://github.com/bouncepaw/mycorrhiza">MycorrhizaWiki</a>.`
|
||||||
DefaultSidebar = ""
|
DefaultSidebar = ""
|
||||||
|
DefaultBodyBottom = ""
|
||||||
DefaultContent = "It is empty here"
|
DefaultContent = "It is empty here"
|
||||||
DefaultStyles = `
|
DefaultStyles = `
|
||||||
<link rel="stylesheet" href="/sys/main.css?action=raw">
|
<link rel="stylesheet" href="/sys/main.css?action=raw">
|
||||||
|
122
handlers.go
122
handlers.go
@ -1,8 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
@ -63,7 +68,118 @@ func HandlerRename(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Println("Attempt to access an unimplemented thing")
|
log.Println("Attempt to access an unimplemented thing")
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandlerUpdate(w http.ResponseWriter, r *http.Request) {
|
func makeTagsSlice(responseTagsString string) (ret []string) {
|
||||||
w.WriteHeader(http.StatusNotImplemented)
|
// `responseTagsString` is string like "foo,, bar,kek". Whitespace around commas is insignificant. Expected output: []string{"foo", "bar", "kek"}
|
||||||
log.Println("Attempt to access an unimplemented thing")
|
for _, tag := range strings.Split(responseTagsString, ",") {
|
||||||
|
if trimmed := strings.TrimSpace(tag); "" == trimmed {
|
||||||
|
ret = append(ret, trimmed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an existing hypha it exists in `hyphae` or create a new one. If it `isNew`, you'll have to insert it to `hyphae` yourself.
|
||||||
|
func getHypha(name string) (*Hypha, bool) {
|
||||||
|
log.Println("Accessing hypha", name)
|
||||||
|
if h, ok := hyphae[name]; ok {
|
||||||
|
log.Println("Got hypha", name)
|
||||||
|
return h, false
|
||||||
|
}
|
||||||
|
log.Println("Create hypha", name)
|
||||||
|
h := &Hypha{
|
||||||
|
FullName: name,
|
||||||
|
Path: filepath.Join(rootWikiDir, name),
|
||||||
|
Revisions: make(map[string]*Revision),
|
||||||
|
parentName: filepath.Dir(name),
|
||||||
|
}
|
||||||
|
return h, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new revison for hypha `h`. All data is fetched from `r`, except for BinaryMime and BinaryPath which require additional processing. You'll have te insert the revision to `h` yourself.
|
||||||
|
func revisionFromHttpData(h *Hypha, r *http.Request) *Revision {
|
||||||
|
idStr := strconv.Itoa(h.NewestRevisionInt() + 1)
|
||||||
|
log.Println(idStr)
|
||||||
|
rev := &Revision{
|
||||||
|
Id: h.NewestRevisionInt() + 1,
|
||||||
|
FullName: h.FullName,
|
||||||
|
Tags: makeTagsSlice(r.PostFormValue("tags")),
|
||||||
|
Comment: r.PostFormValue("comment"),
|
||||||
|
Author: r.PostFormValue("author"),
|
||||||
|
Time: int(time.Now().Unix()),
|
||||||
|
TextMime: r.PostFormValue("text_mime"),
|
||||||
|
TextPath: filepath.Join(h.Path, idStr+".txt"),
|
||||||
|
// Left: BinaryMime, BinaryPath
|
||||||
|
}
|
||||||
|
return rev
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTextFileFromHttpData(rev *Revision, r *http.Request) error {
|
||||||
|
data := []byte(r.PostFormValue("text"))
|
||||||
|
err := ioutil.WriteFile(rev.TextPath, data, 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to write", len(data), "bytes to", rev.TextPath)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeBinaryFileFromHttpData(h *Hypha, oldRev Revision, newRev *Revision, r *http.Request) error {
|
||||||
|
// 10 MB file size limit
|
||||||
|
r.ParseMultipartForm(10 << 20)
|
||||||
|
// Read file
|
||||||
|
file, handler, err := r.FormFile("binary")
|
||||||
|
if file != nil {
|
||||||
|
defer file.Close()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Println("No binary data passed for", newRev.FullName)
|
||||||
|
newRev.BinaryMime = oldRev.BinaryMime
|
||||||
|
newRev.BinaryPath = oldRev.BinaryPath
|
||||||
|
log.Println("Set previous revision's binary data")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
newRev.BinaryMime = handler.Header.Get("Content-Type")
|
||||||
|
newRev.BinaryPath = filepath.Join(h.Path, newRev.IdAsStr()+".bin")
|
||||||
|
data, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("Got", len(data), "of binary data for", newRev.FullName)
|
||||||
|
err = ioutil.WriteFile(newRev.BinaryPath, data, 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to write", len(data), "bytes to", newRev.TextPath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("Written", len(data), "of binary data for", newRev.FullName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandlerUpdate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
log.Println("Attempt to update hypha", mux.Vars(r)["hypha"])
|
||||||
|
h, isNew := getHypha(vars["hypha"])
|
||||||
|
oldRev := h.GetNewestRevision()
|
||||||
|
newRev := revisionFromHttpData(h, r)
|
||||||
|
|
||||||
|
if isNew {
|
||||||
|
h.CreateDir()
|
||||||
|
}
|
||||||
|
err := writeTextFileFromHttpData(newRev, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = writeBinaryFileFromHttpData(h, oldRev, newRev, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Revisions[newRev.IdAsStr()] = newRev
|
||||||
|
h.SaveJson()
|
||||||
|
|
||||||
|
log.Println("Current hyphae storage is", hyphae)
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte("Saved successfully"))
|
||||||
}
|
}
|
||||||
|
47
hypha.go
47
hypha.go
@ -1,20 +1,24 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Hypha struct {
|
type Hypha struct {
|
||||||
FullName string
|
FullName string `json:"-"`
|
||||||
Path string
|
Path string `json:"-"`
|
||||||
ViewCount int `json:"views"`
|
ViewCount int `json:"views"`
|
||||||
Deleted bool `json:"deleted"`
|
Deleted bool `json:"deleted"`
|
||||||
Revisions map[string]*Revision `json:"revisions"`
|
Revisions map[string]*Revision `json:"revisions"`
|
||||||
ChildrenNames []string
|
ChildrenNames []string `json:"-"`
|
||||||
parentName string
|
parentName string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +43,15 @@ func (h *Hypha) Name() string {
|
|||||||
return h.FullName
|
return h.FullName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Hypha) GetNewestRevision() Revision {
|
||||||
|
return *h.Revisions[h.NewestRevision()]
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Hypha) NewestRevision() string {
|
func (h *Hypha) NewestRevision() string {
|
||||||
|
return strconv.Itoa(h.NewestRevisionInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hypha) NewestRevisionInt() int {
|
||||||
var largest int
|
var largest int
|
||||||
for k, _ := range h.Revisions {
|
for k, _ := range h.Revisions {
|
||||||
rev, _ := strconv.Atoi(k)
|
rev, _ := strconv.Atoi(k)
|
||||||
@ -47,16 +59,38 @@ func (h *Hypha) NewestRevision() string {
|
|||||||
largest = rev
|
largest = rev
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return strconv.Itoa(largest)
|
return largest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hypha) MetaJsonPath() string {
|
||||||
|
return filepath.Join(h.Path, "meta.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hypha) CreateDir() error {
|
||||||
|
return os.MkdirAll(h.Path, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hypha) ParentName() string {
|
func (h *Hypha) ParentName() string {
|
||||||
return h.parentName
|
return h.parentName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Hypha) SaveJson() {
|
||||||
|
data, err := json.Marshal(h)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to create JSON of hypha.", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(h.MetaJsonPath(), data, 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to save JSON of hypha.", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("Saved JSON data of", h.FullName)
|
||||||
|
}
|
||||||
|
|
||||||
func ActionEdit(hyphaName string, w http.ResponseWriter) {
|
func ActionEdit(hyphaName string, w http.ResponseWriter) {
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
var initContents, initTextMime, initBinaryMime, initTags string
|
var initContents, initTextMime, initTags string
|
||||||
hypha, ok := hyphae[hyphaName]
|
hypha, ok := hyphae[hyphaName]
|
||||||
if !ok {
|
if !ok {
|
||||||
initContents = "Describe " + hyphaName + "here."
|
initContents = "Describe " + hyphaName + "here."
|
||||||
@ -71,10 +105,9 @@ func ActionEdit(hyphaName string, w http.ResponseWriter) {
|
|||||||
}
|
}
|
||||||
initContents = string(contents)
|
initContents = string(contents)
|
||||||
initTextMime = newestRev.TextMime
|
initTextMime = newestRev.TextMime
|
||||||
initBinaryMime = newestRev.BinaryMime
|
|
||||||
initTags = strings.Join(newestRev.Tags, ",")
|
initTags = strings.Join(newestRev.Tags, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte(EditHyphaPage(hyphaName, initTextMime, initBinaryMime, initContents, initTags)))
|
w.Write([]byte(EditHyphaPage(hyphaName, initTextMime, initContents, initTags)))
|
||||||
}
|
}
|
||||||
|
4
main.go
4
main.go
@ -101,7 +101,9 @@ func main() {
|
|||||||
r.Queries("action", "rename", "to", hyphaPattern).Path(hyphaUrl).
|
r.Queries("action", "rename", "to", hyphaPattern).Path(hyphaUrl).
|
||||||
HandlerFunc(HandlerRename)
|
HandlerFunc(HandlerRename)
|
||||||
|
|
||||||
r.Queries("action", "update").Path(hyphaUrl).
|
r.Queries(
|
||||||
|
"action", "update",
|
||||||
|
).Path(hyphaUrl).Methods("POST").
|
||||||
HandlerFunc(HandlerUpdate)
|
HandlerFunc(HandlerUpdate)
|
||||||
|
|
||||||
r.HandleFunc(hyphaUrl, HandlerView)
|
r.HandleFunc(hyphaUrl, HandlerView)
|
||||||
|
35
render.go
35
render.go
@ -3,29 +3,36 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"path"
|
"path"
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
func EditHyphaPage(name, text_mime, binary_mime, content, tags string) string {
|
func EditHyphaPage(name, textMime, content, tags string) string {
|
||||||
keys := map[string]string{
|
keys := map[string]string{
|
||||||
"Title": fmt.Sprintf(TitleTemplate, name),
|
"Title": fmt.Sprintf(TitleTemplate, "Edit "+name),
|
||||||
|
"Header": renderFromString(name, "Hypha/edit/header.html"),
|
||||||
}
|
}
|
||||||
page := map[string]string{
|
page := map[string]string{
|
||||||
"Text": content,
|
"Text": content,
|
||||||
"TextMime": text_mime,
|
"TextMime": textMime,
|
||||||
"BinMime": binary_mime,
|
|
||||||
"Name": name,
|
"Name": name,
|
||||||
"Tags": tags,
|
"Tags": tags,
|
||||||
}
|
}
|
||||||
return renderBase(renderFromMap(page, "Hypha/edit.html"), keys)
|
return renderBase(renderFromMap(page, "Hypha/edit/index.html"), keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HyphaPage(hyphae map[string]*Hypha, rev Revision, content string) string {
|
func HyphaPage(hyphae map[string]*Hypha, rev Revision, content string) string {
|
||||||
keys := map[string]string{
|
sidebar := DefaultSidebar
|
||||||
"Title": fmt.Sprintf(TitleTemplate, rev.FullName),
|
bside, err := ioutil.ReadFile("Hypha/view/sidebar.html")
|
||||||
|
if err == nil {
|
||||||
|
sidebar = string(bside)
|
||||||
}
|
}
|
||||||
return renderBase(renderFromString(content, "Hypha/index.html"), keys)
|
keys := map[string]string{
|
||||||
|
"Title": fmt.Sprintf(TitleTemplate, rev.FullName),
|
||||||
|
"Sidebar": sidebar,
|
||||||
|
}
|
||||||
|
return renderBase(renderFromString(content, "Hypha/view/index.html"), keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -36,11 +43,13 @@ Args:
|
|||||||
*/
|
*/
|
||||||
func renderBase(content string, keys map[string]string) string {
|
func renderBase(content string, keys map[string]string) string {
|
||||||
page := map[string]string{
|
page := map[string]string{
|
||||||
"Title": DefaultTitle,
|
"Title": DefaultTitle,
|
||||||
"Header": renderFromString(DefaultHeaderText, "header.html"),
|
"Head": DefaultStyles,
|
||||||
"Footer": renderFromString(DefaultFooterText, "footer.html"),
|
"Sidebar": DefaultSidebar,
|
||||||
"Sidebar": DefaultSidebar,
|
"Main": DefaultContent,
|
||||||
"Main": DefaultContent,
|
"BodyBottom": DefaultBodyBottom,
|
||||||
|
"Header": renderFromString(DefaultHeaderText, "header.html"),
|
||||||
|
"Footer": renderFromString(DefaultFooterText, "footer.html"),
|
||||||
}
|
}
|
||||||
for key, val := range keys {
|
for key, val := range keys {
|
||||||
page[key] = val
|
page[key] = val
|
||||||
|
15
revision.go
15
revision.go
@ -6,12 +6,15 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/gomarkdown/markdown"
|
"github.com/gomarkdown/markdown"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// In different places, revision variable is called `r`. But when there is an http.Request as well, the revision becomes `rev`. TODO: name them consistently.
|
||||||
type Revision struct {
|
type Revision struct {
|
||||||
Id int
|
Id int `json:"-"`
|
||||||
FullName string
|
FullName string `json:"-"`
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
ShortName string `json:"name"`
|
ShortName string `json:"name"`
|
||||||
Comment string `json:"comment"`
|
Comment string `json:"comment"`
|
||||||
@ -19,8 +22,12 @@ type Revision struct {
|
|||||||
Time int `json:"time"`
|
Time int `json:"time"`
|
||||||
TextMime string `json:"text_mime"`
|
TextMime string `json:"text_mime"`
|
||||||
BinaryMime string `json:"binary_mime"`
|
BinaryMime string `json:"binary_mime"`
|
||||||
TextPath string
|
TextPath string `json:"-"`
|
||||||
BinaryPath string
|
BinaryPath string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Revision) IdAsStr() string {
|
||||||
|
return strconv.Itoa(r.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// During initialisation, it is guaranteed that r.BinaryMime is set to "" if the revision has no binary data.
|
// During initialisation, it is guaranteed that r.BinaryMime is set to "" if the revision has no binary data.
|
||||||
|
1
templates/Hypha/edit/header.html
Normal file
1
templates/Hypha/edit/header.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<h1 class="header__edit-title">Edit {{ . }}</h1>
|
@ -1,13 +1,16 @@
|
|||||||
<div class="naviwrapper">
|
<div class="naviwrapper">
|
||||||
<form class="naviwrapper__edit edit-box">
|
<form class="naviwrapper__edit edit-box"
|
||||||
|
method="POST"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
action="?action=update">
|
||||||
<div class="naviwrapper__buttons">
|
<div class="naviwrapper__buttons">
|
||||||
<input type="submit" name="action" value="update"/>
|
<input type="submit" value="update"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="edit-box__left">
|
<div class="edit-box__left">
|
||||||
<h4>Edit box</h4>
|
<h4>Edit box</h4>
|
||||||
<textarea class="edit-box__text" name="text" cols="80" rows="25">
|
<textarea class="edit-box__text" name="text" cols="80" rows="25">
|
||||||
{{ .Text }}
|
{{ .Text }}
|
||||||
</textarea>
|
</textarea>
|
||||||
|
|
||||||
<h4>Upload file</h4>
|
<h4>Upload file</h4>
|
||||||
@ -20,17 +23,13 @@
|
|||||||
<p>Good types are <code>text/markdown</code> and <code>text/plain</code></p>
|
<p>Good types are <code>text/markdown</code> and <code>text/plain</code></p>
|
||||||
<input type="text" name="text_mime" value="{{ .TextMime }}"/>
|
<input type="text" name="text_mime" value="{{ .TextMime }}"/>
|
||||||
|
|
||||||
<h4>Media MIME-type</h4>
|
|
||||||
<p>For now, only image formats are supported. Choose any, but <code>image/jpeg</code> and <code>image/png</code> are recommended</p>
|
|
||||||
<input type="text" name="binary_mime" value="{{ .BinMime }}"/>
|
|
||||||
|
|
||||||
<h4>Revision comment</h4>
|
<h4>Revision comment</h4>
|
||||||
<p>Please make your comment helpful</p>
|
<p>Please make your comment helpful</p>
|
||||||
<input type="text" name="comment" value="Update {{ .Name }}"/>
|
<input type="text" name="comment" value="Update {{ .Name }}"/>
|
||||||
|
|
||||||
<h4>Edit tags</h4>
|
<h4>Edit tags</h4>
|
||||||
<p>Tags are separated by commas, whitespace is ignored</p>
|
<p>Tags are separated by commas, whitespace is ignored</p>
|
||||||
<input type="text" name="comment" value="{{ .Tags }}"/>
|
<input type="text" name="tags" value="{{ .Tags }}"/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
9
templates/Hypha/view/bodybottom.html
Normal file
9
templates/Hypha/view/bodybottom.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script type="text/javascript">
|
||||||
|
var menu = document.getElementById('shroomburgerMenu');
|
||||||
|
document.getElementById('shroomBtn').addEventListener('click', function() {
|
||||||
|
menu.classList.add('active');
|
||||||
|
});
|
||||||
|
document.getElementById('mushroomBtn').addEventListener('click', function() {
|
||||||
|
menu.classList.remove('active');
|
||||||
|
});
|
||||||
|
</script>
|
10
templates/Hypha/view/sidebar.html
Normal file
10
templates/Hypha/view/sidebar.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<div class="naviwrapper">
|
||||||
|
<div class="hypha-actions">
|
||||||
|
<ul>
|
||||||
|
<li><a href="?action=edit">Edit</a>
|
||||||
|
<li><a href="?action=getBinary">Download</a>
|
||||||
|
<li><a href="?action=zen">Zen mode</a>
|
||||||
|
<li><a href="?action=raw">View raw</a>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,11 +1,25 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>{{ .Title }}</title>
|
<title>{{ .Title }}</title>
|
||||||
|
{{ .Head }}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="header">{{ .Header }}</header>
|
<div class="shroom">
|
||||||
|
<button class="shroom__button" id="shroomBtn"><span>🍄</span> Open mycelium</button>
|
||||||
|
</div>
|
||||||
<main class="main">{{ .Main }}</main>
|
<main class="main">{{ .Main }}</main>
|
||||||
<aside class="sidebar">{{ .Sidebar }}</aside>
|
<div class="left-panel" id="shroomburgerMenu">
|
||||||
<footer class="footer">{{ .Footer }}</footer>
|
<div class="left-panel__in">
|
||||||
|
<div class="shroom mushroom">
|
||||||
|
<button class="shroom__button" id="mushroomBtn"><span>🍄</span> Close mycelium</button>
|
||||||
|
</div>
|
||||||
|
<div class="left-panel__contents">
|
||||||
|
<header class="header">{{ .Header }}</header>
|
||||||
|
<aside class="sidebar">{{ .Sidebar }}</aside>
|
||||||
|
<footer class="footer">{{ .Footer }}</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ .BodyBottom }}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
BIN
w/m/Fruit/Apple/2.bin
Normal file
BIN
w/m/Fruit/Apple/2.bin
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
3
w/m/Fruit/Apple/2.txt
Normal file
3
w/m/Fruit/Apple/2.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Красное яблоко. Для съёмки использовалась белая бумага позади и над яблоком, и фотовспышка SB-600 в 1/4 мощности.
|
||||||
|
|
||||||
|
Source: https://commons.wikimedia.org/wiki/File:Red_Apple.jpg
|
6
w/m/Fruit/Apple/3.txt
Normal file
6
w/m/Fruit/Apple/3.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Mycorrhiza is pure happiness
|
||||||
|
|
||||||
|
Красное яблоко. Для съёмки использовалась белая бумага позади и над яблоком, и фотовспышка SB-600 в 1/4 мощности.
|
||||||
|
|
||||||
|
Source: https://commons.wikimedia.org/wiki/File:Red_Apple.jpg
|
||||||
|
|
BIN
w/m/Fruit/Apple/4.bin
Normal file
BIN
w/m/Fruit/Apple/4.bin
Normal file
Binary file not shown.
5
w/m/Fruit/Apple/4.txt
Normal file
5
w/m/Fruit/Apple/4.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Mycorrhiza is pure happiness
|
||||||
|
|
||||||
|
Красное яблоко. Для съёмки использовалась белая бумага позади и над яблоком, и фотовспышка SB-600 в 1/4 мощности.
|
||||||
|
|
||||||
|
Source: https://commons.wikimedia.org/wiki/File:Red_Apple.jpg
|
@ -1,13 +1 @@
|
|||||||
{
|
{"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"},"2":{"tags":null,"name":"","comment":"Update Fruit/Apple","author":"","time":1592570366,"text_mime":"text/plain","binary_mime":"image/jpeg"},"3":{"tags":null,"name":"","comment":"Test fs dumping","author":"","time":1592570926,"text_mime":"text/plain","binary_mime":"image/jpeg"},"4":{"tags":[""],"name":"","comment":"Update Fruit/Apple","author":"","time":1592579243,"text_mime":"text/plain","binary_mime":"application/pdf"}}}
|
||||||
"revisions":{
|
|
||||||
"1":{
|
|
||||||
"name": "Apple",
|
|
||||||
"time": 1591639464,
|
|
||||||
"author": "bouncepaw",
|
|
||||||
"comment": "add apple pic hehehe",
|
|
||||||
"tags": ["img"],
|
|
||||||
"text_mime": "text/plain",
|
|
||||||
"binary_mime": "image/jpeg"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,236 @@
|
|||||||
b { color: red; }
|
*, *::before, *::after {
|
||||||
article { border: 1px black solid; }
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font: 15px/1.5 system-ui, -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Helvetica', 'Roboto', 'Arial', sans-serif;
|
||||||
|
max-width: 500px;
|
||||||
|
min-height: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 12px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 700px) {
|
||||||
|
body {
|
||||||
|
|
||||||
|
/*
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 250px;
|
||||||
|
grid-template-rows: auto 1fr auto;
|
||||||
|
grid-column-gap: 24px;
|
||||||
|
grid-template-areas:
|
||||||
|
"main header"
|
||||||
|
"main sidebar"
|
||||||
|
"main footer";
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shroom {
|
||||||
|
/*margin: 8px 0 24px;*/
|
||||||
|
/*margin: -12px;*/
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shroom__button {
|
||||||
|
/*
|
||||||
|
padding: 2px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
*/border-radius: 8px;/*
|
||||||
|
background: none;
|
||||||
|
*/
|
||||||
|
padding: 8px 16px 8px 0;
|
||||||
|
border: none;
|
||||||
|
background: #f0f2f4;
|
||||||
|
color: #444;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shroom span {
|
||||||
|
margin-left: 16px;
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 20px;
|
||||||
|
vertical-align: -0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mushroom .shroom__button {
|
||||||
|
background: #44484a;
|
||||||
|
color: #dddfe4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
header {
|
||||||
|
grid-area: header;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
grid-area: main;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside {
|
||||||
|
grid-area: sidebar;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
grid-area: footer;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
color: #222428;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #44e;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: #44a;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin: 1em 0 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
font-family: 'Georgia', serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.666;
|
||||||
|
max-width: 40em;
|
||||||
|
hyphens: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page__title {
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
.edit-box { display: grid; grid-template-columns: 7fr 5fr; }
|
.edit-box { display: grid; grid-template-columns: 7fr 5fr; }
|
||||||
.edit-box .naviwrapper__buttons { grid-column: 1; grid-row: 2 }
|
.edit-box .naviwrapper__buttons { grid-column: 1; grid-row: 2 }
|
||||||
.edit-box__left { grid-column: 1; grid-row: 2 }
|
.edit-box__left { grid-column: 1; grid-row: 2 }
|
||||||
.edit-box__right { grid-column: 2; grid-row: 1 / span 2 }
|
.edit-box__right { grid-column: 2; grid-row: 1 / span 2 }
|
||||||
|
|
||||||
|
footer {
|
||||||
|
/*margin-top: 40px;
|
||||||
|
border-top: 1px solid #ddd;*/
|
||||||
|
padding: 1em 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a, footer a:visited {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel.active {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel.active .sidebar {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel__in {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 12px 24px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel__contents {
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel .shroom {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 700px) {
|
||||||
|
body {
|
||||||
|
max-width: 1200px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
padding-right: 274px; /* 250px + 16px + 16px */
|
||||||
|
}
|
||||||
|
|
||||||
|
.shroom {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 274px; /* 250px + 24px * 2 */
|
||||||
|
/*right: calc(50% + 900px / 2 + 24px);*/
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel__contents {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
padding: 16px 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hypha-actions ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hypha-actions li {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hypha-actions a {
|
||||||
|
display: block;
|
||||||
|
padding: 6px 16px;
|
||||||
|
font: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #666;
|
||||||
|
transition: 0.1s background;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside .hypha-actions a:hover {
|
||||||
|
background: #eaeaea;
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user