NoteClient/main.go

256 lines
4.1 KiB
Go
Raw Normal View History

2023-08-25 22:20:07 +02:00
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"strings"
"github.com/gdamore/tcell/v2"
_ "github.com/gdamore/tcell/v2/encoding"
"github.com/mattn/go-runewidth"
)
// TODO new, delete
type note struct {
Id int64 `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
}
const lineHeight = 2
const editor = "vim"
const endpoint = "http://localhost:8080/api"
var selectedNote = 0
var notes []note
func emitStr(s tcell.Screen, x int, y int, style tcell.Style, str string) {
for _, c := range str {
var comb []rune
w := runewidth.RuneWidth(c)
if w == 0 {
comb = []rune{c}
c = ' '
w = 1
}
s.SetContent(x, y, c, comb, style)
x += w
}
}
func showNotes(s tcell.Screen) {
s.Fill(' ', tcell.StyleDefault)
for idx, note := range notes {
style := tcell.StyleDefault
if idx == selectedNote {
style = style.Foreground(tcell.ColorBlack).Background(tcell.ColorWhite)
}
text := fmt.Sprint(idx) + ". " + note.Title
emitStr(s, 1, 1+idx*lineHeight, style, text)
}
_, h := s.Size()
emitStr(s, 1, h-1, tcell.StyleDefault.Foreground(tcell.ColorBlue), "n - new, r - rename, d - delete, Enter - edit, Esc - exit")
}
func loadNotes() {
res, err := http.Get(endpoint + "/notes")
if err != nil {
log.Fatalln(err)
}
data, err := io.ReadAll(res.Body)
if err != nil {
log.Fatalln(err)
}
var loadedNotes []note
json.Unmarshal(data, &loadedNotes)
notes = loadedNotes
}
func edit(s tcell.Screen, text string, ext string) (string, error) {
cmd := exec.Command("vipe", "--suffix", ext)
cmd.Env = append(cmd.Env, "EDITOR="+editor)
pipe, _ := cmd.StdinPipe()
io.WriteString(pipe, text)
pipe.Close()
err := s.Suspend()
if err != nil {
return "", err
}
output, err := cmd.Output()
if err != nil {
return "", err
}
text = string(output)
err = s.Resume()
if err != nil {
return "", err
}
return text, nil
}
func putNote(n note) error {
json, err := json.Marshal(n)
if err != nil {
return err
}
req, err := http.NewRequest("PUT", endpoint+"/notes/"+fmt.Sprint(n.Id), bytes.NewReader(json))
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
if res.StatusCode != 200 {
return errors.New("Request failed: " + res.Status)
}
return nil
}
func editNote(s tcell.Screen, n note) (note, error) {
newContent, err := edit(s, n.Content, "md")
if err != nil {
return note{}, err
}
n.Content = newContent
err = putNote(n)
if err != nil {
return note{}, nil
}
showNotes(s)
s.Sync()
return n, nil
}
func editNoteTitle(s tcell.Screen, n note) (note, error) {
newTitle, err := edit(s, n.Title, "txt")
if err != nil {
return note{}, err
}
idx := strings.Index(newTitle, "\n")
if idx != -1 {
newTitle = newTitle[:idx]
}
if len(newTitle) == 0 {
return n, nil
}
n.Title = newTitle
err = putNote(n)
if err != nil {
return note{}, nil
}
showNotes(s)
s.Sync()
return n, nil
}
func main() {
loadNotes()
s, err := tcell.NewScreen()
if err != nil {
log.Fatalln(err)
}
if err := s.Init(); err != nil {
log.Fatalln(err)
}
defStyle := tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(tcell.ColorWhite)
s.SetStyle(defStyle)
showNotes(s)
for {
switch ev := s.PollEvent().(type) {
case *tcell.EventResize:
showNotes(s)
s.Sync()
case *tcell.EventKey:
switch ev.Key() {
case tcell.KeyUp:
if selectedNote > 0 {
selectedNote--
}
showNotes(s)
s.Show()
case tcell.KeyDown:
if selectedNote < len(notes)-1 {
selectedNote++
}
showNotes(s)
s.Show()
case tcell.KeyEnter:
n, err := editNote(s, notes[selectedNote])
if err != nil {
s.Fini()
log.Fatalln(err)
}
notes[selectedNote] = n
case tcell.KeyRune:
if ev.Rune() == 'r' {
n, err := editNoteTitle(s, notes[selectedNote])
if err != nil {
s.Fini()
log.Fatalln(err)
}
notes[selectedNote] = n
showNotes(s)
s.Sync()
}
case tcell.KeyEscape:
s.Fini()
os.Exit(1)
}
}
}
}