1
0
mirror of https://github.com/alecthomas/chroma.git synced 2025-03-23 21:29:15 +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
import (
"encoding/json"
"html/template"
"log"
"net/http"
@ -32,63 +33,91 @@ type context struct {
Languages []string
SelectedStyle string
Styles []string
Text string
HTML template.HTML
Error string
CSRFField template.HTML
}
func handler(w http.ResponseWriter, r *http.Request) {
ctx := contextFromRequest(r)
style := styles.Get(ctx.SelectedStyle)
if style == nil {
style = styles.Fallback
func index(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r)
err := htmlTemplate.Execute(w, &ctx)
if err != nil {
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 {
language = lexers.Analyse(ctx.Text)
language = lexers.Analyse(req.Text)
if language != nil {
ctx.SelectedLanguage = language.Config().Name
req.Language = language.Config().Name
}
}
if language == nil {
language = lexers.Fallback
}
tokens, err := language.Tokenise(nil, ctx.Text)
tokens, err := language.Tokenise(nil, req.Text)
if err != nil {
ctx.Error = err.Error()
} 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
}
return nil, err
}
err = htmlTemplate.Execute(w, &ctx)
if err != nil {
panic(err)
style := styles.Get(req.Style)
if style == nil {
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 {
err := r.ParseForm()
func newContext(r *http.Request) context {
ctx := context{
SelectedLanguage: r.Form.Get("language"),
SelectedStyle: r.Form.Get("style"),
Text: r.Form.Get("text"),
CSRFField: csrf.TemplateField(r),
SelectedStyle: "monokailight",
CSRFField: csrf.TemplateField(r),
}
if err != nil {
ctx.Error = err.Error()
return ctx
style := styles.Get(ctx.SelectedStyle)
if style == nil {
style = styles.Fallback
}
ctx.Background = template.CSS(html.StyleEntryToCSS(style.Get(chroma.Background)))
if ctx.SelectedStyle == "" {
ctx.SelectedStyle = "monokailight"
}
@ -114,8 +143,9 @@ func main() {
log.Println("Starting")
router := mux.NewRouter()
router.Handle("/", http.HandlerFunc(handler))
router.Handle("/static/{file:.*}", http.StripPrefix("/static/", http.FileServer(staticFiles.HTTPBox())))
router.Handle("/", http.HandlerFunc(index)).Methods("GET")
router.Handle("/api/render", http.HandlerFunc(renderHandler))
router.Handle("/static/{file:.*}", http.StripPrefix("/static/", http.FileServer(staticFiles.HTTPBox()))).Methods("GET")
options := []csrf.Option{}
if cli.CSRFKey == "" {

View File

@ -1,7 +1,73 @@
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 textArea = form.elements["text"];
var csrfToken = form.elements["gorilla.csrf.Token"].value;
var elms = document.getElementsByTagName("select");
for (e of elms) {
e.addEventListener('change', () => form.submit());
var output = document.getElementById("output");
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 {
{{.Background}}
{{.Background}}
}
#output pre {
@ -20,7 +20,7 @@
</head>
<body>
<div class="container">
{{if .Error}}<div class="notification">{{.Error}}</div>{{end}}
<div class="notification is-hidden"></div>
<h1 class="title">Chroma Playground</h1>
@ -31,7 +31,7 @@
<label class="label">Language</label>
<div class="control">
<div class="select">
<select name="language">
<select name="language" id="language">
<option value="" disabled{{if eq "" $.SelectedLanguage}} selected{{end}}>Language</option>
{{- range .Languages}}
<option value="{{.}}"{{if eq . $.SelectedLanguage}} selected{{end}}>{{.}}</option>
@ -45,7 +45,7 @@
<label class="label">Style</label>
<div class="control">
<div class="select">
<select name="style">
<select name="style" id="style">
<option value="" disabled{{if eq "" $.SelectedStyle}} selected{{end}}>Style</option>
{{- range .Styles}}
<option value="{{.}}"{{if eq . $.SelectedStyle}} selected{{end}}>{{.}}</option>
@ -59,22 +59,20 @@
<div class="field">
<label class="label">Code</label>
<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 class="field">
<div class="control">
<button class="button is-link">Submit</button>
<button type="submit" class="button is-link">Submit</button>
</div>
</div>
<hr>
<label class="label">Output</label>
<div class="field box" id="output">
{{.HTML}}
</div>
<div class="field box" id="output"></div>
</form>
</div>
</body>