as minimal as it gets notes application
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

192 lines
5.1 KiB

package main
import (
var (
tmpl string // tmpl defines the template used for rendering the notes
path string // path defines the path where the notes should be stored
func main() {
r := mux.NewRouter()
// getting rid of this "favicon.ico could not be found error message"
r.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "%s", "")})
// static files
fs := http.FileServer(http.Dir("./static"))
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fs))
// the actual note handlers
r.HandleFunc("/{path:[a-zA-Z0-9/]*}", pathGetHandler).Methods("GET")
r.HandleFunc("/{path:[a-zA-Z0-9/]*}", pathPostHandler).Methods("POST")
http.ListenAndServe(":8080", r)
// pathGetHandler handles GET requests to any path, thus handling the notes themselves
func pathGetHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
// fetch all possible notes
treeSlice := recursiveDive("")
// read the current note
file := readFile(vars["path"])
templateContent := map[string]interface{}{
"Tree": treeSlice,
"File": file,
"Path": fmt.Sprintf("/%s", vars["path"]),
// define the template used to render the frontend
t := template.New("")
t, err := t.Parse(tmpl) // tmpl is a global var defined using initTemplate()
if err != nil {
fmt.Printf("error parsing the template: %s", err)
// execute the template using the values defined in the templateContent map
err = t.ExecuteTemplate(w, "", templateContent)
if err != nil {
fmt.Printf("error executing the template: %s", err)
// pathPostHandler handles POST requests to the individual notes. This gets
// triggered when a note gets saved
func pathPostHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
// parse the post form
if err := r.ParseForm(); err != nil {
fmt.Printf("error parsing the post form: %s", err)
// extrace the file and covert it to a byte array for writing
file := r.FormValue("file")
d1 := []byte(file)
// write the notes content to its file
err := ioutil.WriteFile(fmt.Sprintf("%s/%s.txt", path, vars["path"]), d1, 0644)
if err != nil {
fmt.Printf("error writing the note to it's file: %s", err)
http.Redirect(w, r, fmt.Sprintf("/%s", vars["path"]), http.StatusSeeOther)
// recursiveDive recursively iterates over all notes starting at the given
// divePath
func recursiveDive(divePath string) []string {
var returnSlice []string
c, err := ioutil.ReadDir(fmt.Sprintf("%s/%s", path, divePath))
if err != nil {
fmt.Printf("error reading the dir: %s", err)
return []string{}
for _, entry := range c {
if entry.IsDir() {
newDivePath := fmt.Sprintf("%s/%s", divePath, entry.Name())
newElements := recursiveDive(newDivePath)
// recursively call the function for each dir in the current dir
for _, element := range newElements {
returnSlice = append(returnSlice, strings.TrimSuffix(element, ".txt"))
} else {
returnSlice = append(returnSlice, strings.TrimSuffix(fmt.Sprintf("%s/%s", divePath, entry.Name()), ".txt"))
return returnSlice
// readFile reads a files content and returns it. Amazing, isn't it?
func readFile(filePath string) string {
fullFilePath := fmt.Sprintf("%s/%s.txt", path, filePath)
dat, err := ioutil.ReadFile(fullFilePath)
if err != nil {
// handle the error
if strings.Contains(err.Error(), "no such file or directory") {
// if the file path contains a /, strip stuff and create the folder...
if strings.Contains(fullFilePath, "/") {
extractedPath := fullFilePath[0:strings.LastIndex(fullFilePath, "/")]
err := os.MkdirAll(extractedPath, 0755)
if err != nil {
// ...then create the actual file
fmt.Printf("Creating %s\n", fullFilePath)
_, err = os.Create(fullFilePath)
if err != nil {
return string(dat)
func initFlags() {
flag.StringVar(&path, "path", ".", "folder where the notes should be stored")
// initTemplate is down here, as it may become a bit longer and the focus
// should stay on the code above
func initTemplate() {
tmpl = `<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/static/bootstrap.min.css">
<div class="row m-1 mt-3">
<div class="col-4">
<div class="list-group">
{{ range .Tree }}
<a href="{{ . }}" class="list-group-item list-group-item-action">{{ . -}}</a>
{{ end }}
<div class="col-8">
<div class="mb-3">
<form method="POST" action="{{ .Path }}">
<textarea class="form-control" id="file" name="file" rows="15">{{ .File }}</textarea>
<button type="submit" class="btn btn-outline-secondary mt-3">Save</button>
<button type="submit" formaction="/outoutout" class="btn btn-outline-danger mt-3">Delete</button>