From 219981913e6c0db8ad4bd63feae31e08c53e2a5a Mon Sep 17 00:00:00 2001 From: Peter Stuifzand Date: Sun, 25 Aug 2019 12:30:00 +0200 Subject: [PATCH] Implement blocks based editor --- editor.go | 32 ++++++++ file.go | 19 ++++- main.go | 121 +++++++++++++++++++++++----- render.go | 171 ++++++++++++++++++++++++++++++++++++++++ templates/edit.html | 14 +--- templates/editorjs.html | 2 + templates/layout.html | 7 +- 7 files changed, 332 insertions(+), 34 deletions(-) create mode 100644 editor.go create mode 100644 render.go create mode 100644 templates/editorjs.html diff --git a/editor.go b/editor.go new file mode 100644 index 0000000..830171b --- /dev/null +++ b/editor.go @@ -0,0 +1,32 @@ +package main + +import ( + "bytes" + "html/template" +) + +type editorJsJson struct { + Page string + ContentType string + Data template.JS +} + +func renderEditor(pageName, inputText, contentType string) (template.HTML, error) { + if contentType == "json" { + t, err := template.ParseFiles("templates/editorjs.html") + if err != nil { + return "", err + } + if inputText == "" { + inputText = "null" + } + data := editorJsJson{Page: pageName, Data: template.JS(inputText), ContentType: contentType} + var buf bytes.Buffer + err = t.Execute(&buf, data) + if err != nil { + return "", err + } + return template.HTML(buf.String()), nil + } + return "", nil +} diff --git a/file.go b/file.go index fb37752..a66bfd0 100644 --- a/file.go +++ b/file.go @@ -3,8 +3,8 @@ package main import ( "bufio" "bytes" + "encoding/json" "fmt" - "github.com/sergi/go-diff/diffmatchpatch" "html" "html/template" "io/ioutil" @@ -14,6 +14,8 @@ import ( "path/filepath" "strings" "time" + + "github.com/sergi/go-diff/diffmatchpatch" ) type FilePages struct { @@ -44,7 +46,20 @@ func (fp *FilePages) Save(p string, page Page, summary, author string) error { return err } defer f.Close() - f.WriteString(strings.Replace(page.Content, "\r\n", "\n", -1)) + if page.Content[0] == '{' { + var buf bytes.Buffer + err = json.Indent(&buf, []byte(page.Content), "", " ") + if err != nil { + return err + } + + _, err = buf.WriteTo(f) + if err != nil { + return err + } + } else { + f.WriteString(strings.Replace(page.Content, "\r\n", "\n", -1)) + } return saveWithGit(fp, p, summary, author) } diff --git a/main.go b/main.go index 8da69c1..c10ce17 100644 --- a/main.go +++ b/main.go @@ -1,18 +1,20 @@ package main import ( + "encoding/json" "flag" "fmt" - "gitlab.com/golang-commonmark/markdown" "html/template" "log" "net/http" "net/url" - "p83.nl/go/ekster/pkg/util" - "p83.nl/go/indieauth" "regexp" "strings" "time" + + "gitlab.com/golang-commonmark/markdown" + "p83.nl/go/ekster/pkg/util" + "p83.nl/go/indieauth" ) var ( @@ -71,6 +73,7 @@ type editPage struct { Title string Content string Name string + Editor template.HTML } type historyPage struct { @@ -280,7 +283,12 @@ func (h *saveHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - r.ParseForm() + err = r.ParseForm() + if err != nil { + http.Error(w, err.Error(), 500) + return + } + isJson := r.PostForm.Get("json") == "1" page := r.PostForm.Get("p") summary := r.PostForm.Get("summary") @@ -289,7 +297,12 @@ func (h *saveHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err != nil { log.Println(err) } - http.Redirect(w, r, "/"+page, http.StatusFound) + + if isJson { + fmt.Print(w, "{}") + } else { + http.Redirect(w, r, "/"+page, http.StatusFound) + } } func (h *editHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -318,10 +331,35 @@ func (h *editHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { pageText := mp.Get(page).Content + jsonEditor := pageText[0] == '{' + + var editor template.HTML + if jsonEditor { + editor, err = renderEditor(page, pageText, "json") + if err != nil { + http.Error(w, err.Error(), 500) + return + } + } else { + editor = ` + +
+ +
+ +
+ +
+
+ +` + } + data := editPage{ Session: sess, Title: strings.Replace(page, "_", " ", -1), Content: pageText, + Editor: editor, Name: page, } @@ -362,24 +400,36 @@ func (h *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - hrefRE := regexp.MustCompile(`\[\[\s*([\w- ]+)\s*\]\]`) + jsonPage := pageText[0] == '{' + if jsonPage { + pageText, err = renderJSON(pageText) - pageText = hrefRE.ReplaceAllStringFunc(pageText, func(s string) string { - s = strings.TrimPrefix(s, "[[") - s = strings.TrimSuffix(s, "]]") - s = strings.TrimSpace(s) - if !mp.Exist(s) { - // return fmt.Sprintf("%s", s, "edit", s) - return fmt.Sprintf("%s[?](/%s)", s, strings.Replace(s, " ", "_", -1)) + if err != nil { + http.Error(w, err.Error(), 500) + return } - return fmt.Sprintf("[%s](/%s)", s, strings.Replace(s, " ", "_", -1)) - }) + } - md := markdown.New( - markdown.HTML(true), - markdown.XHTMLOutput(true), - ) - pageText = md.RenderToString([]byte(pageText)) + if !jsonPage { + hrefRE := regexp.MustCompile(`\[\[\s*([\w.\- ]+)\s*\]\]`) + + pageText = hrefRE.ReplaceAllStringFunc(pageText, func(s string) string { + s = strings.TrimPrefix(s, "[[") + s = strings.TrimSuffix(s, "]]") + s = strings.TrimSpace(s) + if !mp.Exist(s) { + // return fmt.Sprintf("%s", s, "edit", s) + return fmt.Sprintf("%s[?](/%s)", s, strings.Replace(s, " ", "_", -1)) + } + return fmt.Sprintf("[%s](/%s)", s, strings.Replace(s, " ", "_", -1)) + }) + + md := markdown.New( + markdown.HTML(true), + markdown.XHTMLOutput(true), + ) + pageText = md.RenderToString([]byte(pageText)) + } data := indexPage{ Session: sess, @@ -462,6 +512,22 @@ func (h *recentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } +type LinkResponseMetaImage struct { + Url string `json:"url"` +} + +type LinkResponseMeta struct { + Title string `json:"title"` + Description string `json:"description"` + Image LinkResponseMetaImage `json:"image"` +} + +type LinkResponse struct { + Success int `json:"success"` + Link string `json:"link"` + Meta LinkResponseMeta `json:"meta"` +} + func main() { var port int flag.IntVar(&port, "port", 8080, "http port") @@ -470,7 +536,20 @@ func main() { mp = NewFilePages("data") http.Handle("/auth/", &authHandler{}) - http.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir("ui/node_modules/trix/dist/")))) + http.HandleFunc("/fetchLink", func(w http.ResponseWriter, r *http.Request) { + link := r.URL.Query().Get("url") + u, err := url.Parse(link) + if err != nil { + http.Error(w, err.Error(), 400) + return + } + var response LinkResponse + response.Success = 1 + response.Link = u.String() + response.Meta.Title = "Test" + json.NewEncoder(w).Encode(response) + }) + http.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir("/home/peter/work/editorjs/dist/")))) http.Handle("/save/", &saveHandler{}) http.Handle("/edit/", &editHandler{}) http.Handle("/history/", &historyHandler{}) diff --git a/render.go b/render.go new file mode 100644 index 0000000..2f9f185 --- /dev/null +++ b/render.go @@ -0,0 +1,171 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" +) + +type Block struct { + Type string + Data json.RawMessage +} + +type Paragraph struct { + Text string +} + +type Code struct { + Code string +} + +type List struct { + Style string + Items []string +} + +type Header struct { + Level int + Text string +} + +type ChecklistItem struct { + Text string + Checked bool +} + +type Checklist struct { + Style string + Items []ChecklistItem +} + +type Link struct { + Link string + Meta LinkResponseMeta +} + +type Table struct { + Content [][]string +} + +type Document struct { + Time int64 + Version string + Blocks []Block +} + +func renderJSON(text string) (string, error) { + var data Document + err := json.Unmarshal([]byte(text), &data) + if err != nil { + return "", err + } + + var buf bytes.Buffer + + for _, block := range data.Blocks { + switch block.Type { + case "table": + var table Table + err = json.Unmarshal(block.Data, &table) + if err != nil { + return "", fmt.Errorf("error while parsing %s: %s", block.Type, err.Error()) + } + + buf.WriteString("") + for _, row := range table.Content { + buf.WriteString("") + for _, col := range row { + buf.WriteString("") + } + buf.WriteString("") + } + buf.WriteString("
") + buf.WriteString(col) + buf.WriteString("
") + break + case "link": + // TODO(peter): improve link rendering + var link Link + err = json.Unmarshal(block.Data, &link) + if err != nil { + return "", fmt.Errorf("error while parsing %s: %s", block.Type, err.Error()) + } + fmt.Fprintf(&buf, `%s`, link.Link, link.Meta.Title) + case "list": + var list List + err = json.Unmarshal(block.Data, &list) + if err != nil { + return "", fmt.Errorf("error while parsing %s: %s", block.Type, err.Error()) + } + var tag string + if list.Style == "ordered" { + tag = "ol" + } else { + tag = "ul" + } + buf.WriteString("<") + buf.WriteString(tag) + buf.WriteString(">") + for _, item := range list.Items { + buf.WriteString("
  • ") + buf.WriteString(item) + buf.WriteString("
  • ") + } + buf.WriteString("") + case "header": + var header Header + err = json.Unmarshal(block.Data, &header) + if err != nil { + return "", fmt.Errorf("error while parsing %s: %s", block.Type, err.Error()) + } + fmt.Fprintf(&buf, "%s", header.Level, header.Text, header.Level) + case "paragraph": + var para Paragraph + err = json.Unmarshal(block.Data, ¶) + if err != nil { + return "", fmt.Errorf("error while parsing %s: %s", block.Type, err.Error()) + } + buf.WriteString("

    ") + buf.WriteString(para.Text) + buf.WriteString("

    ") + case "code": + var code Code + err = json.Unmarshal(block.Data, &code) + if err != nil { + return "", fmt.Errorf("error while parsing %s: %s", block.Type, err.Error()) + } + buf.WriteString("
    ")
    +			buf.WriteString(code.Code)
    +			buf.WriteString("
    ") + case "checklist": + var checklist Checklist + err = json.Unmarshal(block.Data, &checklist) + if err != nil { + return "", fmt.Errorf("error while parsing %s: %s", block.Type, err.Error()) + } + for _, item := range checklist.Items { + buf.WriteString("

    ") + + buf.WriteString(``) + if item.Checked { + buf.WriteString(` `) + buf.WriteString(` `) + buf.WriteString(` `) + buf.WriteString(` `) + } + buf.WriteString(``) + buf.WriteString(item.Text) + buf.WriteString("

    ") + } + default: + return "", fmt.Errorf("unknown type: %s", block.Type) + } + + fmt.Fprintln(&buf) + } + + return buf.String(), nil +} diff --git a/templates/edit.html b/templates/edit.html index 7aea027..877e7b1 100644 --- a/templates/edit.html +++ b/templates/edit.html @@ -2,18 +2,12 @@

    {{ .Title }}

    - -
    - -
    - -
    - -
    -
    - + {{ .Editor }}
    {{ end }} {{ define "content_head" }} {{ end }} + +{{ define "footer_scripts" }} +{{ end }} diff --git a/templates/editorjs.html b/templates/editorjs.html new file mode 100644 index 0000000..39f1a01 --- /dev/null +++ b/templates/editorjs.html @@ -0,0 +1,2 @@ +
    + diff --git a/templates/layout.html b/templates/layout.html index f7dd4dd..01344b6 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -6,6 +6,7 @@ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> + {{ .Title }} - Wiki {{ block "content_head" . }} {{ end }}