diff options
author | Luke Shumaker <lukeshu@sbcglobal.net> | 2016-11-15 00:16:06 -0500 |
---|---|---|
committer | Luke Shumaker <lukeshu@sbcglobal.net> | 2016-11-15 00:16:06 -0500 |
commit | 5e1e95852821e138caf20c3f8ee23e3941e42bbc (patch) | |
tree | a88f3a3d6284e76116df24c54bd6a6ddc2184770 | |
parent | 711269a7eb2e151512bcaf1afcf45b13540d8f8e (diff) |
better progressive enhancement of editor
-rw-r--r-- | enhancers.txt | 91 | ||||
-rw-r--r-- | got/page.html.got | 1 | ||||
-rw-r--r-- | got/view_blob.got | 3 | ||||
-rw-r--r-- | src/edit/enhancers.go | 79 | ||||
-rw-r--r-- | src/edit/views.go | 38 | ||||
-rw-r--r-- | static/mde.js | 20 | ||||
-rw-r--r-- | static/style.css | 35 |
7 files changed, 225 insertions, 42 deletions
diff --git a/enhancers.txt b/enhancers.txt new file mode 100644 index 0000000..22c684b --- /dev/null +++ b/enhancers.txt @@ -0,0 +1,91 @@ +--boundary +X-Thing: Pattern + +text/markdown +--boundary +X-Thing: Head + +<link rel="stylesheet" href="/static/font-awesome/css/font-awesome.min.css"> +<link rel="stylesheet" href="/static/simplemde/dist/simplemde.min.css"> +<script src="/static/simplemde/dist/simplemde.min.js"></script> +--boundary +X-Thing: Tail + +<script> +(function() { + var textarea = document.getElementsByTagName('textarea')[0]; + var form = textarea.form; + var container = document.createElement('div'); + form.insertBefore(container, textarea); + container.appendChild(textarea); + + var tip = document.createElement('aside'); + tip.innerHTML = + '<p>Tip: To set the page title (what appears in the tab '+ + 'name/window bar), put this at the top of the page:</p>\n'+ + '<pre>---\ntitle: "Your Title Here"\n---\n</pre>'+ + '<p>I apologize that it looks funny on this page and in the '+ + 'preview.</p>'; + container.appendChild(tip); + + var simplemde = new SimpleMDE({ + autoDownloadFontAwesome: false, + element: textarea, + promptURLs: true, + forceSync: true, + showIcons: ['code', 'table'], + }); +})(); +</script> +--boundary +X-Thing: Pattern + +text/*; */*+xml +--boundary +X-Thing: Head + +<link rel="stylesheet" href="/static/codemirror/lib/codemirror.css"> +<script src="/static/codemirror/lib/codemirror.js"></script> + + +<script src="/static/codemirror/mode/xml/xml.js"></script> +<script src="/static/codemirror/mode/javascript/javascript.js"></script> +<script src="/static/codemirror/mode/css/css.js"></script> +<script src="/static/codemirror/mode/sass/sass.js"></script> +<script src="/static/codemirror/mode/htmlmixed/htmlmixed.js"></script> +<script src="/static/codemirror/addon/edit/matchbrackets.js"></script> +<script src="/static/codemirror/addon/edit/trailingspace.js"></script> +<script src="/static/codemirror/addon/display/rulers.js"></script> + +<script src="/static/codemirror/addon/fold/foldcode.js"></script> +<script src="/static/codemirror/addon/fold/foldgutter.js"></script> +<link rel="stylesheet" href="/static/codemirror/addon/fold/foldgutter.css"> +<script src="/static/codemirror/addon/fold/brace-fold.js"></script> +<script src="/static/codemirror/addon/fold/xml-fold.js"></script> +<script src="/static/codemirror/addon/fold/markdown-fold.js"></script> +<script src="/static/codemirror/addon/fold/comment-fold.js"></script> +--boundary +X-Thing: Tail + +<script> +(function() { + var textarea = document.getElementsByTagName('textarea')[0]; + var form = textarea.form; + var container = document.createElement('div'); + form.insertBefore(container, textarea); + container.appendChild(textarea); + var cm = CodeMirror.fromTextArea(textarea, { + mode: ctype, + lineNumbers: true, + matchBrackets: true, + foldGutter: true, + rulers: [{column: 80, lineStyle: "dashed"}], + showTrailingSpace: true, + gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], + }); + cm.on("change", function() { + cm.save(); + }); +})(); +</script> +--boundary-- diff --git a/got/page.html.got b/got/page.html.got index c061d50..7f2c16c 100644 --- a/got/page.html.got +++ b/got/page.html.got @@ -13,5 +13,6 @@ </head> <body> {{.body}} + {{.tail}} </body> </html> diff --git a/got/view_blob.got b/got/view_blob.got index 95692b0..752d5c7 100644 --- a/got/view_blob.got +++ b/got/view_blob.got @@ -32,6 +32,3 @@ </label> <input type="submit" value="Delete"> </form> -{{if eq .ctype "text/markdown"}} -<script src="/static/mde.js"></script> -{{end}} diff --git a/src/edit/enhancers.go b/src/edit/enhancers.go new file mode 100644 index 0000000..b732ab2 --- /dev/null +++ b/src/edit/enhancers.go @@ -0,0 +1,79 @@ +package main + +import ( + "io" + "io/ioutil" + "os" + "mime/multipart" + "strings" + "path" + + "fmt" +) + +type enhancer struct { + pattern []string + head string + tail string +} + +func loadEnhancers(filename string) []enhancer { + file, err := os.Open(filename) + errcheck(err) + + reader := multipart.NewReader(file, "boundary") + + var s uint8 = 0 + var e enhancer + var ret []enhancer + for { + part, err := reader.NextPart() + if err == io.EOF { + return ret + } + errcheck(err) + + k := part.Header.Get("X-Thing") + v, err := ioutil.ReadAll(part) + errcheck(err) + + switch k { + case "Pattern": + patterns := strings.Split(string(v), ";") + for i := range patterns { + patterns[i] = strings.TrimSpace(patterns[i]) + } + s |= 1<<0 + e.pattern = patterns + case "Head": + s |= 1<<1 + e.head = string(v) + case "Tail": + s |= 1<<2 + e.tail = string(v) + default: + panic("unknown X-Thing: "+k) + } + if s == 1<<0 | 1<<1 | 1<<2 { + ret = append(ret, e) + s = 0 + } + } +} + +var enhancers = loadEnhancers("enhancers.txt") + +func getEnhancer(ctype string) (head, tail string) { + fmt.Fprintf(os.Stderr, "enhancers = %q\n", enhancers) + for _, enhancer := range enhancers { + for _, pattern := range enhancer.pattern { + matched, err := path.Match(pattern, ctype) + errcheck(err) + fmt.Fprintf(os.Stderr, "Match(%q, %q) => %q\n", pattern, ctype, matched) + if matched { + return enhancer.head, enhancer.tail + } + } + } + return "", "" +} diff --git a/src/edit/views.go b/src/edit/views.go index 1d3176d..a2890d4 100644 --- a/src/edit/views.go +++ b/src/edit/views.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "bytes" "io" "path" @@ -24,11 +25,12 @@ var ( tmplDeleted = newTemplate("got/deleted.got") ) -func renderPage(w io.Writer, title, head, body string) error { +func renderPage(w io.Writer, title, head, body, tail string) error { return tmplPage.Execute(w, map[string]string{ "title": title, "head": head, "body": body, + "tail": tail, }) } @@ -47,8 +49,8 @@ func renderViewTree(w io.Writer, upath string, tree GitTree) error { } // Component Render - var buf bytes.Buffer - err := tmplViewTree.Execute(&buf, map[string]interface{}{ + var body bytes.Buffer + err := tmplViewTree.Execute(&body, map[string]interface{}{ "path": upath, "files": files, }) @@ -56,7 +58,7 @@ func renderViewTree(w io.Writer, upath string, tree GitTree) error { return err } // Page render - return renderPage(w, upath, "", buf.String()) + return renderPage(w, upath, "", body.String(), "") } func renderViewBlob(w io.Writer, upath string, file GitFile) error { @@ -67,8 +69,8 @@ func renderViewBlob(w io.Writer, upath string, file GitFile) error { } ctype := getctype(upath, content) // Component render - var buf bytes.Buffer - err = tmplViewBlob.Execute(&buf, map[string]string{ + var body bytes.Buffer + err = tmplViewBlob.Execute(&body, map[string]string{ "path": upath, "ctype": ctype, "content": string(content), @@ -76,38 +78,36 @@ func renderViewBlob(w io.Writer, upath string, file GitFile) error { if err != nil { return err } - head := "" - if ctype == "text/markdown" { - head += "<link rel=\"stylesheet\" href=\"/static/font-awesome/css/font-awesome.min.css\">\n" - head += "<link rel=\"stylesheet\" href=\"/static/simplemde/dist/simplemde.min.css\">\n" - head += "<script src=\"/static/simplemde/dist/simplemde.min.js\"></script>\n" - } + + head, tail := getEnhancer(ctype) + head = fmt.Sprintf("<script>ctype = \"%s\"</script>\n%s", template.JSEscapeString(ctype), head); + // Page render - return renderPage(w, upath, head, buf.String()) + return renderPage(w, upath, head, body.String(), tail) } func renderModified(w io.Writer, upath string) error { // Component render - var buf bytes.Buffer - err := tmplModified.Execute(&buf, map[string]string{ + var body bytes.Buffer + err := tmplModified.Execute(&body, map[string]string{ "path": upath, }) if err != nil { return err } // Page render - return renderPage(w, upath, "", buf.String()) + return renderPage(w, upath, "", body.String(), "") } func renderDeleted(w io.Writer, upath string) error { // Component render - var buf bytes.Buffer - err := tmplDeleted.Execute(&buf, map[string]string{ + var body bytes.Buffer + err := tmplDeleted.Execute(&body, map[string]string{ "path": upath, }) if err != nil { return err } // Page render - return renderPage(w, upath, "", buf.String()) + return renderPage(w, upath, "", body.String(), "") } diff --git a/static/mde.js b/static/mde.js deleted file mode 100644 index 7471110..0000000 --- a/static/mde.js +++ /dev/null @@ -1,20 +0,0 @@ -(function() { - var textarea = document.getElementsByTagName("textarea")[0]; - var form = textarea.form; - var container = document.createElement('div'); - /*container.innerHTML = - "<p>I know it looks funny in the editor, but to set the page "+ - "title (what appears in the tab name/window bar), put this at "+ - "the top of the page:</p>\n"+ - "<pre>---\ntitle: \"Your Title Here\"\n---\n</pre>";*/ - form.insertBefore(container, textarea); - container.appendChild(textarea); - - var simplemde = new SimpleMDE({ - autoDownloadFontAwesome: false, - element: textarea, - promptURLs: true, - forceSync: true, - showIcons: ["code", "table"], - }); -})(); diff --git a/static/style.css b/static/style.css index cad83c0..2ce7fa8 100644 --- a/static/style.css +++ b/static/style.css @@ -34,3 +34,38 @@ input[type=file] { background: #EEEEEE; border-radius: 4px; } + +kbd, code, samp, tt, pre { + background: #DDDDFF; + white-space: pre; +} +pre { + margin: auto 2em; + padding: .5em; + overflow: auto; + border: solid 1px #AAAAAA; +} + +aside { + padding: 10px; + border-radius: 4px; + border: solid 1px #ddd; + background: rgb(240, 240, 240); + opacity: 0.7; +} + +.CodeMirror { + /* match Firefox's built-in style for textearea */ + border: 1px solid rgb(232, 232, 231); +} +.CodeMirror .cm-trailingspace { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==); + background-position: bottom left; + background-repeat: repeat-x; +} +.CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word) { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==) !important; + background-position: bottom left !important; + background-repeat: repeat-x !important; + background-color: transparent !important; +} |