Add comments per heading
parent
785215bd3c
commit
8bf7503c8e
|
|
@ -0,0 +1,61 @@
|
||||||
|
package notes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"local/notes-server/filetree"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (n *Notes) Comment(urlPath string, lineno int, comment string) error {
|
||||||
|
p := filetree.NewPathFromURL(urlPath)
|
||||||
|
if stat, err := os.Stat(p.Local); err != nil {
|
||||||
|
return errors.New("cannot comment as it does not exist")
|
||||||
|
} else if stat.IsDir() {
|
||||||
|
return errors.New("cannot comment on a dir")
|
||||||
|
}
|
||||||
|
f, err := os.Open(p.Local)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
f2, err := ioutil.TempFile(os.TempDir(), path.Base(p.Local)+".*")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f2.Close()
|
||||||
|
reader := bufio.NewReader(f)
|
||||||
|
writer := io.Writer(f2)
|
||||||
|
for i := 0; i < lineno+1; i++ {
|
||||||
|
line, _, err := reader.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(writer, bytes.NewReader(line))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write([]byte("\n"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formatted := "\n"
|
||||||
|
formatted += fmt.Sprintf("> *%s*\n", comment)
|
||||||
|
_, err = io.Copy(writer, strings.NewReader(formatted))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(writer, reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f2.Close()
|
||||||
|
return os.Rename(f2.Name(), p.Local)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package notes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"local/notes-server/config"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestComment(t *testing.T) {
|
||||||
|
d, err := ioutil.TempDir(os.TempDir(), "testComment.*")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(d)
|
||||||
|
f, err := ioutil.TempFile(d, "testFile.*")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.Write([]byte(`
|
||||||
|
hello
|
||||||
|
world
|
||||||
|
# i have a heading
|
||||||
|
## i have a subber heading
|
||||||
|
### i have a[heading with](http://google.com) a hyperlink
|
||||||
|
## *I think this heading is in italics*
|
||||||
|
`))
|
||||||
|
f.Close()
|
||||||
|
fpath := path.Join("/comment", strings.TrimPrefix(f.Name(), d))
|
||||||
|
config.Root = d
|
||||||
|
n := &Notes{}
|
||||||
|
t.Logf("d=%s, fpath=%s", d, fpath)
|
||||||
|
|
||||||
|
if err := n.Comment("/comment/a", 5, "a"); err == nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := n.Comment(fpath, -1, "illegal line no"); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := n.Comment(fpath, 10000, "big line no"); err == nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := n.Comment(fpath, 0, "first_line_OK"); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if b, err := ioutil.ReadFile(f.Name()); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if !bytes.Contains(b, []byte("> *first_line_OK*\n")) {
|
||||||
|
t.Errorf("%s", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
package notes
|
package notes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"local/notes-server/filetree"
|
"local/notes-server/filetree"
|
||||||
|
"log"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gomarkdown/markdown"
|
"github.com/gomarkdown/markdown"
|
||||||
|
|
@ -23,15 +27,57 @@ func (n *Notes) File(urlPath string) (string, error) {
|
||||||
b, _ := ioutil.ReadFile(p.Local)
|
b, _ := ioutil.ReadFile(p.Local)
|
||||||
renderer := html.NewRenderer(html.RendererOptions{
|
renderer := html.NewRenderer(html.RendererOptions{
|
||||||
Flags: html.CommonFlags | html.TOC,
|
Flags: html.CommonFlags | html.TOC,
|
||||||
RenderNodeHook: n.commentFormer(),
|
RenderNodeHook: n.commentFormer(urlPath, b),
|
||||||
})
|
})
|
||||||
parser := parser.NewWithExtensions(parser.CommonExtensions | parser.HeadingIDs | parser.AutoHeadingIDs | parser.Titleblock)
|
parser := parser.NewWithExtensions(parser.CommonExtensions | parser.HeadingIDs | parser.AutoHeadingIDs | parser.Titleblock)
|
||||||
content := markdown.ToHTML(b, parser, renderer)
|
content := markdown.ToHTML(b, parser, renderer)
|
||||||
return string(content) + "\n", nil
|
return string(content) + "\n", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notes) commentFormer() html.RenderNodeFunc {
|
func (n *Notes) commentFormer(urlPath string, md []byte) html.RenderNodeFunc {
|
||||||
header_n := 0
|
urlPath = strings.TrimPrefix(urlPath, "/")
|
||||||
|
urlPath = strings.TrimPrefix(urlPath, strings.Split(urlPath, "/")[0])
|
||||||
|
lines := bytes.Split(md, []byte("\n"))
|
||||||
|
cur := -1
|
||||||
|
nextHeader := func() {
|
||||||
|
cur++
|
||||||
|
for cur < len(lines) {
|
||||||
|
if bytes.Contains(lines[cur], []byte("```")) {
|
||||||
|
cur++
|
||||||
|
for cur < len(lines) && !bytes.Contains(lines[cur], []byte("```")) {
|
||||||
|
cur++
|
||||||
|
}
|
||||||
|
cur++
|
||||||
|
}
|
||||||
|
if cur >= len(lines) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
line := lines[cur]
|
||||||
|
if ok, err := regexp.Match(`^\s*#+\s*[^\s]+\s*$`, line); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else if ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cur++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return func(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
|
||||||
|
if heading, ok := node.(*ast.Heading); ok && !entering {
|
||||||
|
log.Printf("%+v", heading)
|
||||||
|
nextHeader()
|
||||||
|
fmt.Fprintf(w, `
|
||||||
|
<form method="POST" action=%q class="comment">
|
||||||
|
<input name="lineno" type="number" style="display:none" value="%d"/>
|
||||||
|
<input autocomplete="off" name="content" type="text"/>
|
||||||
|
<input type="submit"/>
|
||||||
|
</form>
|
||||||
|
`, path.Join("/comment", urlPath)+"#"+heading.HeadingID, cur)
|
||||||
|
}
|
||||||
|
return ast.GoToNext, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Notes) commentFormerOld() html.RenderNodeFunc {
|
||||||
return func(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
|
return func(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
|
||||||
if heading, ok := node.(*ast.Heading); ok {
|
if heading, ok := node.(*ast.Heading); ok {
|
||||||
if !entering {
|
if !entering {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html"
|
||||||
|
"local/notes-server/filetree"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) comment(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Println("COMMAND", r.Method, r.FormValue("lineno"), r.FormValue("content"))
|
||||||
|
if r.Method != "POST" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
linenos := r.FormValue("lineno")
|
||||||
|
lineno, err := strconv.Atoi(linenos)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
comment := r.FormValue("content")
|
||||||
|
comment = html.UnescapeString(comment)
|
||||||
|
comment = strings.ReplaceAll(comment, "\r", "")
|
||||||
|
|
||||||
|
err = s.Notes.Comment(r.URL.Path, lineno, comment)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
url := *r.URL
|
||||||
|
url.Path = path.Join("/notes/", filetree.NewPathFromURL(r.URL.Path).BaseHREF)
|
||||||
|
http.Redirect(w, r, url.String(), http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
@ -33,6 +33,9 @@ func (s *Server) Routes() error {
|
||||||
fmt.Sprintf("create/%s%s", wildcard, wildcard): {
|
fmt.Sprintf("create/%s%s", wildcard, wildcard): {
|
||||||
handler: s.gzip(s.authenticate(s.create)),
|
handler: s.gzip(s.authenticate(s.create)),
|
||||||
},
|
},
|
||||||
|
fmt.Sprintf("comment/%s%s", wildcard, wildcard): {
|
||||||
|
handler: s.gzip(s.authenticate(s.comment)),
|
||||||
|
},
|
||||||
fmt.Sprintf("search"): {
|
fmt.Sprintf("search"): {
|
||||||
handler: s.gzip(s.authenticate(s.search)),
|
handler: s.gzip(s.authenticate(s.search)),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue