Impl complete testing needed
parent
3079cd163f
commit
a140d0eade
17
TODO
17
TODO
|
|
@ -27,11 +27,12 @@ x main test -
|
|||
- dir->dir,
|
||||
- dir->file
|
||||
|
||||
TOC levels
|
||||
delete pages
|
||||
search
|
||||
move auth as flag in router
|
||||
. and ../** as roots cause bugs in listing and loading and linking
|
||||
`create` at root is a 400, base= in URL (when `create` input is empty)
|
||||
FTS
|
||||
https://stackoverflow.com/questions/26709971/could-this-be-more-efficient-in-go
|
||||
x TOC levels
|
||||
x delete pages
|
||||
x search
|
||||
FTS
|
||||
https://stackoverflow.com/questions/26709971/could-this-be-more-efficient-in-go
|
||||
x move auth as flag in router
|
||||
x . and ../** as roots cause bugs in listing and loading and linking
|
||||
x `create` at root is a 400, base= in URL (when `create` input is empty)
|
||||
delete top-level pages
|
||||
|
|
|
|||
|
|
@ -71,6 +71,10 @@ func (p Path) MultiLink() string {
|
|||
return full
|
||||
}
|
||||
|
||||
func (p Path) FullLI() string {
|
||||
return fmt.Sprintf(`<li><a href=%q>%s</a></li>`, p.HREF, p.HREF)
|
||||
}
|
||||
|
||||
func (p Path) LI() string {
|
||||
return fmt.Sprintf(`<li><a href=%q>%s</a></li>`, p.HREF, p.Base)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,14 @@ package filetree
|
|||
|
||||
type Paths []Path
|
||||
|
||||
func (p Paths) List() string {
|
||||
func (p Paths) List(full ...bool) string {
|
||||
content := "<ul>\n"
|
||||
for _, path := range p {
|
||||
content += path.LI() + "\n"
|
||||
if len(full) > 0 && full[0] {
|
||||
content += path.FullLI() + "\n"
|
||||
} else {
|
||||
content += path.LI() + "\n"
|
||||
}
|
||||
}
|
||||
content += "</ul>\n"
|
||||
return content
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"local/notes-server/filetree"
|
||||
"os"
|
||||
)
|
||||
|
||||
func (n *Notes) Delete(urlPath string) error {
|
||||
p := filetree.NewPathFromURL(urlPath)
|
||||
if p.IsDir() {
|
||||
return errors.New("path is dir")
|
||||
}
|
||||
return os.Remove(p.Local)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
package notes
|
||||
|
|
@ -5,31 +5,32 @@ import (
|
|||
"bytes"
|
||||
"local/notes-server/filetree"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func (n *Notes) Search(phrase string) (string, error) {
|
||||
files := filetree.NewFiles()
|
||||
err := filepath.Walk(n.root,
|
||||
func(path string, info os.FileInfo, err error) error {
|
||||
func(walked string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
ok, err := grepFile(path, []byte(phrase))
|
||||
if ok {
|
||||
p := filetree.NewPathFromLocal(path)
|
||||
ok, err := grepFile(walked, []byte(phrase))
|
||||
if err == nil && ok {
|
||||
p := filetree.NewPathFromLocal(path.Dir(walked))
|
||||
files.Push(p, info)
|
||||
}
|
||||
return err
|
||||
},
|
||||
)
|
||||
return filetree.Paths(*files).List(), err
|
||||
return filetree.Paths(*files).List(true), err
|
||||
}
|
||||
|
||||
func grepFile(file string, pat []byte) (bool, error) {
|
||||
func grepFile(file string, phrase []byte) (bool, error) {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
|
@ -37,7 +38,7 @@ func grepFile(file string, pat []byte) (bool, error) {
|
|||
defer f.Close()
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
if bytes.Contains(scanner.Bytes(), pat) {
|
||||
if bytes.Contains(scanner.Bytes(), phrase) {
|
||||
return true, scanner.Err()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
asdf
|
||||
asdf
|
||||
|
||||
this contains my search string
|
||||
|
|
@ -1 +0,0 @@
|
|||
asdf
|
||||
|
|
@ -1 +1,2 @@
|
|||
asdf
|
||||
asdf
|
||||
searchString
|
||||
|
|
|
|||
12
public/A/x
12
public/A/x
|
|
@ -1,12 +0,0 @@
|
|||
# A.x
|
||||
|
||||
| hello | world |
|
||||
|-------|-------|
|
||||
| cont | ent. |
|
||||
|
||||
## A.X
|
||||
|
||||
1
|
||||
2
|
||||
|
||||
3
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *Server) delete(w http.ResponseWriter, r *http.Request) {
|
||||
if err := s.Notes.Delete(r.URL.Path); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
r.URL.Path = strings.Replace(path.Dir(r.URL.Path), "delete", "notes", 1)
|
||||
http.Redirect(w, r, r.URL.String(), http.StatusPermanentRedirect)
|
||||
}
|
||||
|
|
@ -23,6 +23,12 @@ func (s *Server) notes(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func notesHead(w http.ResponseWriter, p filetree.Path) {
|
||||
fmt.Fprintln(w, h2(p.MultiLink()))
|
||||
fmt.Fprintf(w, `
|
||||
<form action=%q method="post">
|
||||
<input type="text" name="keywords"></input>
|
||||
<button type="submit">Search</button>
|
||||
</form>
|
||||
`, "/search")
|
||||
}
|
||||
|
||||
func (s *Server) dir(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -59,4 +65,7 @@ func fileHead(w http.ResponseWriter, baseHREF string) {
|
|||
fmt.Fprintf(w, `
|
||||
<a href=%q><input type="button" value="Edit"></input></a>
|
||||
`, path.Join("/edit/", baseHREF))
|
||||
fmt.Fprintf(w, `
|
||||
<a href=%q><input type="button" value="Delete"></input></a>
|
||||
`, path.Join("/delete/", baseHREF))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ func (s *Server) Routes() error {
|
|||
path: fmt.Sprintf("edit/%s%s", wildcard, wildcard),
|
||||
handler: s.authenticate(s.edit),
|
||||
},
|
||||
{
|
||||
path: fmt.Sprintf("delete/%s%s", wildcard, wildcard),
|
||||
handler: s.authenticate(s.delete),
|
||||
},
|
||||
{
|
||||
path: fmt.Sprintf("submit/%s%s", wildcard, wildcard),
|
||||
handler: s.authenticate(s.submit),
|
||||
|
|
@ -32,6 +36,10 @@ func (s *Server) Routes() error {
|
|||
path: fmt.Sprintf("create/%s%s", wildcard, wildcard),
|
||||
handler: s.authenticate(s.create),
|
||||
},
|
||||
{
|
||||
path: fmt.Sprintf("search"),
|
||||
handler: s.authenticate(s.search),
|
||||
},
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"local/notes-server/filetree"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (s *Server) search(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
keywords := r.FormValue("keywords")
|
||||
keywords = html.UnescapeString(keywords)
|
||||
results, err := s.Notes.Search(keywords)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
head(w, r)
|
||||
fmt.Fprintln(w, h2(filetree.NewPathFromURL("/notes").MultiLink()))
|
||||
fmt.Fprintln(w, h1(keywords), results)
|
||||
foot(w, r)
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<html>
|
||||
<header>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<!-- https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/dark.min.css -->
|
||||
<style>
|
||||
@charset "UTF-8";body{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;line-height:1.4;max-width:800px;margin:20px auto;padding:0 10px;color:#dbdbdb;background:#202b38;text-rendering:optimizeLegibility}button,input,textarea{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease}h1{font-size:2.2em;margin-top:0}h1,h2,h3,h4,h5,h6{margin-bottom:12px}h1,h2,h3,h4,h5,h6,strong{color:#fff}b,h1,h2,h3,h4,h5,h6,strong,th{font-weight:600}blockquote{border-left:4px solid rgba(0,150,191,.67);margin:1.5em 0;padding:.5em 1em;font-style:italic}blockquote>footer{margin-top:10px;font-style:normal}address,blockquote cite{font-style:normal}a[href^=mailto]:before{content:"📧 "}a[href^=tel]:before{content:"📞 "}a[href^=sms]:before{content:"💬 "}button,input[type=button],input[type=checkbox],input[type=submit]{cursor:pointer}input:not([type=checkbox]):not([type=radio]),select{display:block}button,input,select,textarea{color:#fff;background-color:#161f27;font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}button,input:not([type=checkbox]):not([type=radio]),select,textarea{-webkit-appearance:none}textarea{margin-right:0;width:100%;box-sizing:border-box;resize:vertical}button,input[type=button],input[type=submit]{padding-right:30px;padding-left:30px}button:hover,input[type=button]:hover,input[type=submit]:hover{background:#324759}button:focus,input:focus,select:focus,textarea:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67)}button:active,input[type=button]:active,input[type=checkbox]:active,input[type=radio]:active,input[type=submit]:active{transform:translateY(2px)}button:disabled,input:disabled,select:disabled,textarea:disabled{cursor:not-allowed;opacity:.5}::-webkit-input-placeholder{color:#a9a9a9}:-ms-input-placeholder{color:#a9a9a9}::-ms-input-placeholder{color:#a9a9a9}::placeholder{color:#a9a9a9}a{text-decoration:none;color:#41adff}a:hover{text-decoration:underline}code,kbd{background:#161f27;color:#ffbe85;padding:5px;border-radius:6px}pre>code{padding:10px;display:block;overflow-x:auto}img{max-width:100%}hr{border:none;border-top:1px solid #dbdbdb}table{border-collapse:collapse;margin-bottom:10px;width:100%}td,th{padding:6px;text-align:left}th{border-bottom:1px solid #dbdbdb}tbody tr:nth-child(2n){background-color:#161f27}::-webkit-scrollbar{height:10px;width:10px}::-webkit-scrollbar-track{background:#161f27;border-radius:6px}::-webkit-scrollbar-thumb{background:#324759;border-radius:6px}::-webkit-scrollbar-thumb:hover{background:#415c73}
|
||||
|
|
@ -12,6 +12,9 @@
|
|||
padding: .5pt;
|
||||
border-radius: 6px;
|
||||
}
|
||||
nav li li li li {
|
||||
display: none;
|
||||
}
|
||||
img {
|
||||
max-height: 400px;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue