1
0
mirror of https://github.com/alecthomas/chroma.git synced 2025-03-31 22:05:17 +02:00

Make playground completely dynamic.

This commit is contained in:
Alec Thomas 2019-07-16 18:45:54 +10:00
parent 2332264124
commit bdb587cd37
3 changed files with 142 additions and 48 deletions

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"encoding/json"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
@ -32,63 +33,91 @@ type context struct {
Languages []string Languages []string
SelectedStyle string SelectedStyle string
Styles []string Styles []string
Text string
HTML template.HTML
Error string
CSRFField template.HTML CSRFField template.HTML
} }
func handler(w http.ResponseWriter, r *http.Request) { func index(w http.ResponseWriter, r *http.Request) {
ctx := contextFromRequest(r) ctx := newContext(r)
style := styles.Get(ctx.SelectedStyle) err := htmlTemplate.Execute(w, &ctx)
if style == nil { if err != nil {
style = styles.Fallback panic(err)
} }
ctx.Background = template.CSS(html.StyleEntryToCSS(style.Get(chroma.Background))) }
language := lexers.Get(ctx.SelectedLanguage) type renderRequest struct {
Language string `json:"language"`
Style string `json:"style"`
Text string `json:"text"`
}
type renderResponse struct {
Error string `json:"error,omitempty"`
HTML string `json:"html,omitempty"`
Language string `json:"language,omitempty"`
Background string `json:"background,omitempty"`
}
func renderHandler(w http.ResponseWriter, r *http.Request) {
req := &renderRequest{}
err := json.NewDecoder(r.Body).Decode(&req)
var rep *renderResponse
if err != nil {
rep = &renderResponse{Error: err.Error()}
} else {
rep, err = render(req)
if err != nil {
rep = &renderResponse{Error: err.Error()}
}
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(rep)
}
func render(req *renderRequest) (*renderResponse, error) {
language := lexers.Get(req.Language)
if language == nil { if language == nil {
language = lexers.Analyse(ctx.Text) language = lexers.Analyse(req.Text)
if language != nil { if language != nil {
ctx.SelectedLanguage = language.Config().Name req.Language = language.Config().Name
} }
} }
if language == nil { if language == nil {
language = lexers.Fallback language = lexers.Fallback
} }
tokens, err := language.Tokenise(nil, ctx.Text) tokens, err := language.Tokenise(nil, req.Text)
if err != nil { if err != nil {
ctx.Error = err.Error() return nil, err
} else {
buf := &strings.Builder{}
formatter := html.New()
err = formatter.Format(buf, style, tokens)
if err != nil {
ctx.Error = err.Error()
} else {
ctx.HTML = template.HTML(buf.String()) // nolint: gosec
}
} }
err = htmlTemplate.Execute(w, &ctx) style := styles.Get(req.Style)
if err != nil { if style == nil {
panic(err) style = styles.Fallback
} }
buf := &strings.Builder{}
formatter := html.New()
err = formatter.Format(buf, style, tokens)
if err != nil {
return nil, err
}
return &renderResponse{
Language: language.Config().Name,
HTML: buf.String(),
Background: html.StyleEntryToCSS(style.Get(chroma.Background)),
}, nil
} }
func contextFromRequest(r *http.Request) context { func newContext(r *http.Request) context {
err := r.ParseForm()
ctx := context{ ctx := context{
SelectedLanguage: r.Form.Get("language"), SelectedStyle: "monokailight",
SelectedStyle: r.Form.Get("style"), CSRFField: csrf.TemplateField(r),
Text: r.Form.Get("text"),
CSRFField: csrf.TemplateField(r),
} }
if err != nil { style := styles.Get(ctx.SelectedStyle)
ctx.Error = err.Error() if style == nil {
return ctx style = styles.Fallback
} }
ctx.Background = template.CSS(html.StyleEntryToCSS(style.Get(chroma.Background)))
if ctx.SelectedStyle == "" { if ctx.SelectedStyle == "" {
ctx.SelectedStyle = "monokailight" ctx.SelectedStyle = "monokailight"
} }
@ -114,8 +143,9 @@ func main() {
log.Println("Starting") log.Println("Starting")
router := mux.NewRouter() router := mux.NewRouter()
router.Handle("/", http.HandlerFunc(handler)) router.Handle("/", http.HandlerFunc(index)).Methods("GET")
router.Handle("/static/{file:.*}", http.StripPrefix("/static/", http.FileServer(staticFiles.HTTPBox()))) router.Handle("/api/render", http.HandlerFunc(renderHandler))
router.Handle("/static/{file:.*}", http.StripPrefix("/static/", http.FileServer(staticFiles.HTTPBox()))).Methods("GET")
options := []csrf.Option{} options := []csrf.Option{}
if cli.CSRFKey == "" { if cli.CSRFKey == "" {

View File

@ -1,7 +1,73 @@
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
var style = document.createElement('style');
var ref = document.querySelector('script');
ref.parentNode.insertBefore(style, ref);
var form = document.getElementById('chroma'); var form = document.getElementById('chroma');
var textArea = form.elements["text"];
var csrfToken = form.elements["gorilla.csrf.Token"].value;
var elms = document.getElementsByTagName("select"); var elms = document.getElementsByTagName("select");
for (e of elms) { var output = document.getElementById("output");
e.addEventListener('change', () => form.submit());
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this, args = arguments;
var later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
function getFormJSON() {
return {
"language": document.getElementById("language").value,
"style": document.getElementById("style").value,
"text": document.getElementById("text").value,
}
} }
function update(event) {
fetch("api/render", {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, cors, *same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json',
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrer: 'no-referrer', // no-referrer, *client
body: JSON.stringify(getFormJSON()),
}).then(data => {
data.json().then(
value => {
style.innerHTML = "#output { " + value.background + "}";
output.innerHTML = value.html;
}
);
}).catch(reason => {
console.log(reason);
})
event.preventDefault();
}
var eventHandler = (event) => update(event);
for (e of elms) {
e.addEventListener('change', eventHandler);
}
form.addEventListener('submit', eventHandler);
var debouncedEventHandler = debounce(eventHandler, 250);
textArea.addEventListener('input', debouncedEventHandler);
textArea.addEventListener('change', debouncedEventHandler)
}); });

View File

@ -9,7 +9,7 @@
} }
#output { #output {
{{.Background}} {{.Background}}
} }
#output pre { #output pre {
@ -20,7 +20,7 @@
</head> </head>
<body> <body>
<div class="container"> <div class="container">
{{if .Error}}<div class="notification">{{.Error}}</div>{{end}} <div class="notification is-hidden"></div>
<h1 class="title">Chroma Playground</h1> <h1 class="title">Chroma Playground</h1>
@ -31,7 +31,7 @@
<label class="label">Language</label> <label class="label">Language</label>
<div class="control"> <div class="control">
<div class="select"> <div class="select">
<select name="language"> <select name="language" id="language">
<option value="" disabled{{if eq "" $.SelectedLanguage}} selected{{end}}>Language</option> <option value="" disabled{{if eq "" $.SelectedLanguage}} selected{{end}}>Language</option>
{{- range .Languages}} {{- range .Languages}}
<option value="{{.}}"{{if eq . $.SelectedLanguage}} selected{{end}}>{{.}}</option> <option value="{{.}}"{{if eq . $.SelectedLanguage}} selected{{end}}>{{.}}</option>
@ -45,7 +45,7 @@
<label class="label">Style</label> <label class="label">Style</label>
<div class="control"> <div class="control">
<div class="select"> <div class="select">
<select name="style"> <select name="style" id="style">
<option value="" disabled{{if eq "" $.SelectedStyle}} selected{{end}}>Style</option> <option value="" disabled{{if eq "" $.SelectedStyle}} selected{{end}}>Style</option>
{{- range .Styles}} {{- range .Styles}}
<option value="{{.}}"{{if eq . $.SelectedStyle}} selected{{end}}>{{.}}</option> <option value="{{.}}"{{if eq . $.SelectedStyle}} selected{{end}}>{{.}}</option>
@ -59,22 +59,20 @@
<div class="field"> <div class="field">
<label class="label">Code</label> <label class="label">Code</label>
<div class="control"> <div class="control">
<textarea class="textarea" name="text" rows="25" cols="80">{{.Text}}</textarea> <textarea class="textarea" id="text" name="text" rows="25" cols="80"></textarea>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<button class="button is-link">Submit</button> <button type="submit" class="button is-link">Submit</button>
</div> </div>
</div> </div>
<hr> <hr>
<label class="label">Output</label> <label class="label">Output</label>
<div class="field box" id="output"> <div class="field box" id="output"></div>
{{.HTML}}
</div>
</form> </form>
</div> </div>
</body> </body>