71df2833c812 — Peter Sanchez tip 18 days ago
Merge tedu upstream
7 files changed, 149 insertions(+), 390 deletions(-)

M README
M activity.go
M admin.go
M docs/changelog.txt
M go.mod
M go.sum
M util.go
M README +5 -4
@@ 35,11 35,12 @@ Development sources: hg clone https://hu
 honk expects to be fronted by a TLS terminating reverse proxy.
 
 First, create the database. This will ask four questions.
+
 ./honk init
-username: (the username you want)
-password: (the password you want)
-listenaddr: (tcp or unix: 127.0.0.1:31337, /var/www/honk.sock, etc.)
-servername: (public DNS name: honk.example.com)
+listen address: (tcp or unix: 127.0.0.1:31337, /var/www/honk.sock, etc.)
+   server name: (public DNS name: honk.example.com)
+      username: (the username you want)
+      password: (the password you want)
 
 Then run honk.
 ./honk

          
M activity.go +6 -6
@@ 832,13 832,13 @@ func xonksaver2(user *WhatAbout, item ju
 			fallthrough
 		case "Audio":
 			fallthrough
+		case "Video":
+			fallthrough
 		case "Image":
-			if what == "Image" {
+			if what == "Image" || what == "Video" {
 				preferorig = true
 			}
 			fallthrough
-		case "Video":
-			fallthrough
 		case "Question":
 			fallthrough
 		case "Commit":

          
@@ 1073,6 1073,9 @@ func xonksaver2(user *WhatAbout, item ju
 						xonk.Noise += fmt.Sprintf(`<p><a href="%s">%s</a>`, u, u)
 						return
 					}
+					if u == id {
+						return
+					}
 					if name == "" {
 						name = u
 					}

          
@@ 1082,9 1085,6 @@ func xonksaver2(user *WhatAbout, item ju
 				if skipMedia(&xonk) {
 					localize = false
 				}
-				if preferorig && !localize {
-					return
-				}
 				donk := savedonk(u, name, desc, mt, localize)
 				if donk != nil {
 					xonk.Donks = append(xonk.Donks, donk)

          
M admin.go +58 -242
@@ 15,285 15,101 @@ 
 
 package main
 
-/*
-#include <termios.h>
-void
-clearecho(struct termios *tio)
-{
-	tio->c_lflag = tio->c_lflag & ~(ECHO|ICANON);
-}
-*/
-import "C"
 import (
-	"bufio"
-	"fmt"
-	"os"
-	"os/signal"
 	"strings"
 
+	"humungus.tedunangst.com/r/termvc"
 	"humungus.tedunangst.com/r/webs/log"
 )
 
 func adminscreen() {
 	log.Init(log.Options{Progname: "honk", Alllogname: "null"})
-	stdout := bufio.NewWriter(os.Stdout)
-	esc := "\x1b"
-	smcup := esc + "[?1049h"
-	rmcup := esc + "[?1049l"
 
 	var avatarColors string
 	getconfig("avatarcolors", &avatarColors)
 	loadLingo()
 
 	type adminfield struct {
-		name    string
-		label   string
-		text    string
-		oneline bool
+		name  string
+		label string
+		text  string
+		ptr   *string
 	}
 
 	messages := []*adminfield{
 		{
 			name:  "servermsg",
-			label: "server",
+			label: "server banner",
 			text:  string(serverMsg),
 		},
 		{
 			name:  "aboutmsg",
-			label: "about",
+			label: "about page message",
 			text:  string(aboutMsg),
 		},
 		{
 			name:  "loginmsg",
-			label: "login",
+			label: "login banner",
 			text:  string(loginMsg),
 		},
 		{
-			name:    "avatarcolors",
-			label:   "avatar colors (4 RGBA hex numbers)",
-			text:    string(avatarColors),
-			oneline: true,
+			name:  "avatarcolors",
+			label: "avatar colors (4 RGBA hex numbers)",
+			text:  string(avatarColors),
 		},
 	}
-	for _, l := range []string{"honked", "bonked", "honked back", "qonked", "evented"} {
-		messages = append(messages, &adminfield{
-			name:    "lingo-" + strings.ReplaceAll(l, " ", ""),
-			label:   "lingo for " + l,
-			text:    relingo[l],
-			oneline: true,
-		})
-	}
-	cursel := 0
-
-	hidecursor := func() {
-		stdout.WriteString(esc + "[?25l")
-	}
-	showcursor := func() {
-		stdout.WriteString(esc + "[?12;25h")
-	}
-	movecursor := func(x, y int) {
-		stdout.WriteString(fmt.Sprintf(esc+"[%d;%dH", y, x))
-	}
-	moveleft := func() {
-		stdout.WriteString(esc + "[1D")
-	}
-	clearscreen := func() {
-		stdout.WriteString(esc + "[2J")
-	}
-	//clearline := func() { stdout.WriteString(esc + "[2K") }
-	colorfn := func(code int) func(string) string {
-		return func(s string) string {
-			return fmt.Sprintf(esc+"[%dm"+"%s"+esc+"[0m", code, s)
-		}
-	}
-	reverse := colorfn(7)
-	magenta := colorfn(35)
-	readchar := func() byte {
-		var buf [1]byte
-		os.Stdin.Read(buf[:])
-		c := buf[0]
-		return c
-	}
-
-	savedtio := new(C.struct_termios)
-	C.tcgetattr(1, savedtio)
-	restore := func() {
-		stdout.WriteString(rmcup)
-		showcursor()
-		stdout.Flush()
-		C.tcsetattr(1, C.TCSAFLUSH, savedtio)
-	}
-	defer restore()
-	go func() {
-		sig := make(chan os.Signal, 1)
-		signal.Notify(sig, os.Interrupt)
-		<-sig
-		restore()
-		os.Exit(0)
-	}()
-
-	init := func() {
-		tio := new(C.struct_termios)
-		C.tcgetattr(1, tio)
-		C.clearecho(tio)
-		C.tcsetattr(1, C.TCSADRAIN, tio)
-
-		hidecursor()
-		stdout.WriteString(smcup)
-		clearscreen()
-		movecursor(1, 1)
-		stdout.Flush()
-	}
-
-	editing := false
-
-	linecount := func(s string) int {
-		lines := 1
-		for i := range s {
-			if s[i] == '\n' {
-				lines++
-			}
-		}
-		return lines
-	}
-
-	msglineno := func(idx int) int {
-		off := 1
-		if idx == -1 {
-			return off
-		}
-		for i, m := range messages {
-			off += 1
-			if i == idx {
-				return off
-			}
-			if !m.oneline {
-				off += 1
-				off += linecount(m.text)
-			}
-		}
-		off += 2
-		return off
-	}
-
-	forscreen := func(s string) string {
-		return strings.Replace(s, "\n", "\n   ", -1)
-	}
 
-	drawmessage := func(idx int) {
-		line := msglineno(idx)
-		movecursor(4, line)
-		label := messages[idx].label
-		if idx == cursel {
-			label = reverse(label)
-		}
-		label = magenta(label)
-		text := forscreen(messages[idx].text)
-		if messages[idx].oneline {
-			stdout.WriteString(fmt.Sprintf("%s\t   %s", label, text))
-		} else {
-			stdout.WriteString(fmt.Sprintf("%s\n   %s", label, text))
-		}
+	termvc.Start()
+	defer termvc.Restore()
+	go termvc.Catch(nil)
+
+	app := termvc.NewApp()
+	scr := termvc.NewScreen()
+	scr.DefaultColor(35)
+	var tabs []termvc.Element
+	insns := termvc.NewTextLabel("honk admin")
+	tabs = append(tabs, insns)
+
+	for _, m := range messages {
+		input := termvc.NewTextArea()
+		input.Label = m.label
+		input.Set(m.text)
+		m.ptr = &input.Value
+		tabs = append(tabs, input)
 	}
-
-	drawscreen := func() {
-		clearscreen()
-		movecursor(4, msglineno(-1))
-		stdout.WriteString(magenta(serverName + " admin panel"))
-		for i := range messages {
-			if !editing || i != cursel {
-				drawmessage(i)
-			}
+	{
+		var inputs []termvc.Element
+		var offset int
+		for _, l := range []string{"honked", "bonked", "honked back", "qonked", "evented"} {
+			field := termvc.NewTextInput(l, &offset)
+			field.Set(relingo[l])
+			inputs = append(inputs, field)
+			messages = append(messages, &adminfield{
+				name: "lingo-" + strings.ReplaceAll(l, " ", ""),
+				ptr:  &field.Value,
+			})
 		}
-		movecursor(4, msglineno(len(messages)))
-		dir := "j/k to move - q to quit - enter to edit"
-		if editing {
-			dir = "esc to end"
-		}
-		stdout.WriteString(magenta(dir))
-		if editing {
-			drawmessage(cursel)
-		}
-		stdout.Flush()
+		form := termvc.NewForm(inputs...)
+		tabs = append(tabs, form)
 	}
-
-	selectnext := func() {
-		if cursel < len(messages)-1 {
-			movecursor(4, msglineno(cursel))
-			stdout.WriteString(magenta(messages[cursel].label))
-			cursel++
-			movecursor(4, msglineno(cursel))
-			stdout.WriteString(reverse(magenta(messages[cursel].label)))
-			stdout.Flush()
-		}
-	}
-	selectprev := func() {
-		if cursel > 0 {
-			movecursor(4, msglineno(cursel))
-			stdout.WriteString(magenta(messages[cursel].label))
-			cursel--
-			movecursor(4, msglineno(cursel))
-			stdout.WriteString(reverse(magenta(messages[cursel].label)))
-			stdout.Flush()
+	btn := termvc.NewButton("save")
+	btn.Submit = func() {
+		app.Quit()
+		termvc.Restore()
+		for _, m := range messages {
+			setconfig(m.name, *m.ptr)
 		}
 	}
-	editsel := func() {
-		editing = true
-		showcursor()
-		drawscreen()
-		m := messages[cursel]
-	loop:
-		for {
-			c := readchar()
-			switch c {
-			case '\x1b':
-				break loop
-			case '\n':
-				if m.oneline {
-					break loop
-				}
-				m.text += "\n"
-				drawscreen()
-			case 127:
-				if len(m.text) > 0 {
-					last := m.text[len(m.text)-1]
-					m.text = m.text[:len(m.text)-1]
-					if last == '\n' {
-						drawscreen()
-					} else {
-						moveleft()
-						stdout.WriteString(" ")
-						moveleft()
-					}
-				}
-			default:
-				m.text += string(c)
-				stdout.WriteString(string(c))
-			}
-			stdout.Flush()
-		}
-		editing = false
-		setconfig(m.name, m.text)
-		hidecursor()
-		drawscreen()
-	}
+	tabs = append(tabs, btn)
+	group := termvc.NewTabGroup(tabs...)
+	group.SetSkip(0, true)
+	group.SetHeight(0, 1)
+	group.SetFocus(1)
+	group.SetHeight(len(tabs)-1, 1)
+	group.SetHeight(len(tabs)-2, 6)
+	pane := termvc.NewMainPanel(group)
 
-	init()
-	drawscreen()
-
-	for {
-		c := readchar()
-		switch c {
-		case 'q':
-			return
-		case 'j':
-			selectnext()
-		case 'k':
-			selectprev()
-		case '\n':
-			editsel()
-		default:
-
-		}
-	}
+	app.Element = pane
+	app.Screen = scr
+	app.Loop()
 }

          
M docs/changelog.txt +2 -0
@@ 2,6 2,8 @@ changelog
 
 ### next
 
++ Rework setup and admin screens.
+
 + Add some compat for forgefed activities.
 
 + TOTP for those who live dangerously.

          
M go.mod +1 -0
@@ 10,6 10,7 @@ require (
 	golang.org/x/net v0.21.0
 	humungus.tedunangst.com/r/go-sqlite3 v1.2.1
 	humungus.tedunangst.com/r/gonix v0.1.4
+	humungus.tedunangst.com/r/termvc v0.1.0
 	humungus.tedunangst.com/r/webs v0.7.12
 )
 

          
M go.sum +2 -0
@@ 50,5 50,7 @@ humungus.tedunangst.com/r/go-sqlite3 v1.
 humungus.tedunangst.com/r/go-sqlite3 v1.2.1/go.mod h1:YrRIH0O7uePPLbJriXrER44ym5aQ0QxK8CnaT/GWOkg=
 humungus.tedunangst.com/r/gonix v0.1.4 h1:FuvWYQlFIzmfHxfvIfq5SYpSiHhFcpJqq3pi+w45s78=
 humungus.tedunangst.com/r/gonix v0.1.4/go.mod h1:VFBc2bPDXr1ayHOmHUutxYu8fSM+pkwK8o36h4rkORg=
+humungus.tedunangst.com/r/termvc v0.1.0 h1:Xe5ImK7W4jZqAOtZhTiec1Lc7CbNpmC2UMuDBUMcVwk=
+humungus.tedunangst.com/r/termvc v0.1.0/go.mod h1:TnlG9PbH77OpEf46iDyb/H9drjegQNwhpXalmGGrbhU=
 humungus.tedunangst.com/r/webs v0.7.12 h1:SbAOmzwn4LPB5AmAzc1KLIA1zymEkXFyFMV2Cp3MDdo=
 humungus.tedunangst.com/r/webs v0.7.12/go.mod h1:ylhqHSPI0Oi7b4nsnx5mSO7AjLXN7wFpEHayLfN/ugk=

          
M util.go +75 -138
@@ 15,37 15,19 @@ 
 
 package main
 
-/*
-#include <termios.h>
-
-void
-termecho(int on)
-{
-	struct termios t;
-	tcgetattr(1, &t);
-	if (on)
-		t.c_lflag |= ECHO;
-	else
-		t.c_lflag &= ~ECHO;
-	tcsetattr(1, TCSADRAIN, &t);
-}
-*/
-import "C"
-
 import (
-	"bufio"
 	"crypto/rand"
 	"crypto/rsa"
 	"database/sql"
 	"fmt"
 	"net"
 	"os"
-	"os/signal"
 	"regexp"
 	"strings"
 
 	"golang.org/x/crypto/bcrypt"
 	"humungus.tedunangst.com/r/go-sqlite3"
+	"humungus.tedunangst.com/r/termvc"
 	"humungus.tedunangst.com/r/webs/httpsig"
 	"humungus.tedunangst.com/r/webs/login"
 )

          
@@ 78,21 60,6 @@ func initdb() {
 		elog.Fatal(err)
 	}
 	alreadyopendb = db
-	defer func() {
-		os.Remove(dbname)
-		os.Remove(blobdbname)
-		os.Exit(1)
-	}()
-	c := make(chan os.Signal, 1)
-	signal.Notify(c, os.Interrupt)
-	go func() {
-		<-c
-		C.termecho(1)
-		fmt.Printf("\n")
-		os.Remove(dbname)
-		os.Remove(blobdbname)
-		os.Exit(1)
-	}()
 
 	_, err = db.Exec("PRAGMA journal_mode=WAL")
 	if err != nil {

          
@@ 106,48 73,80 @@ func initdb() {
 			return
 		}
 	}
-	r := bufio.NewReader(os.Stdin)
-
 	initblobdb(blobdbname)
 
 	prepareStatements(db)
 
-	err = createuser(db, r)
-	if err != nil {
-		elog.Print(err)
-		return
+	cleanup := func() {
+		os.Remove(dbname)
+		os.Remove(blobdbname)
+		os.Exit(1)
 	}
-	// must came later or user above will have negative id
-	err = createserveruser(db)
-	if err != nil {
-		elog.Print(err)
+
+	termvc.Start()
+	defer cleanup()
+	defer termvc.Restore()
+	go termvc.Catch(cleanup)
+
+	app := termvc.NewApp()
+	t1 := termvc.NewTextArea()
+	t1.Value = "\n\n\tHello.\n\t\tWelcome to honk setup."
+	var inputs []termvc.Element
+	var offset int
+	listenfield := termvc.NewTextInput("listen address", &offset)
+	inputs = append(inputs, listenfield)
+	serverfield := termvc.NewTextInput("server name", &offset)
+	inputs = append(inputs, serverfield)
+	namefield := termvc.NewTextInput("username", &offset)
+	inputs = append(inputs, namefield)
+	passfield := termvc.NewPasswordInput("password", &offset)
+	inputs = append(inputs, passfield)
+	okay := false
+	btn := termvc.NewButton("let's go!")
+	left := 25
+	inputs = append(inputs, termvc.NewHPad(&left, btn, nil))
+	form := termvc.NewForm(inputs...)
+	t2 := termvc.NewTextArea()
+	group := termvc.NewVStack(t1, form, t2)
+	group.SetFocus(1)
+	app.Element = termvc.NewMainPanel(group)
+	app.Screen = termvc.NewScreen()
+	btn.Submit = func() {
+		t2.Value = ""
+		addr := listenfield.Value
+		if len(addr) < 1 {
+			t2.Value += "listen address is way too short.\n"
+			elog.Print("that's way too short")
+			return
+		}
+		setconfig("listenaddr", addr)
+
+		addr = serverfield.Value
+		if len(addr) < 1 {
+			t2.Value += "server name is way too short.\n"
+			return
+		}
+		setconfig("servername", addr)
+		err = createuser(db, namefield.Value, passfield.Value)
+		if err != nil {
+			t2.Value += fmt.Sprintf("error: %s\n", err)
+			return
+		}
+		// must came later or user above will have negative id
+		err = createserveruser(db)
+		if err != nil {
+			elog.Print(err)
+			return
+		}
+		okay = true
+		app.Quit()
+	}
+
+	app.Loop()
+	if !okay {
 		return
 	}
 
-	fmt.Printf("listen address: ")
-	addr, err := r.ReadString('\n')
-	if err != nil {
-		elog.Print(err)
-		return
-	}
-	addr = addr[:len(addr)-1]
-	if len(addr) < 1 {
-		elog.Print("that's way too short")
-		return
-	}
-	setconfig("listenaddr", addr)
-	fmt.Printf("server name: ")
-	addr, err = r.ReadString('\n')
-	if err != nil {
-		elog.Print(err)
-		return
-	}
-	addr = addr[:len(addr)-1]
-	if len(addr) < 1 {
-		elog.Print("that's way too short")
-		return
-	}
-	setconfig("servername", addr)
 	var randbytes [16]byte
 	rand.Read(randbytes[:])
 	key := fmt.Sprintf("%x", randbytes)

          
@@ 160,6 159,7 @@ func initdb() {
 	setconfig("devel", 0)
 
 	db.Close()
+	termvc.Restore()
 	fmt.Printf("done.\n")
 	os.Exit(0)
 }

          
@@ 193,28 193,7 @@ func initblobdb(blobdbname string) {
 }
 
 func adduser() {
-	db := opendatabase()
-	defer func() {
-		os.Exit(1)
-	}()
-	c := make(chan os.Signal, 1)
-	signal.Notify(c, os.Interrupt)
-	go func() {
-		<-c
-		C.termecho(1)
-		fmt.Printf("\n")
-		os.Exit(1)
-	}()
-
-	r := bufio.NewReader(os.Stdin)
-
-	err := createuser(db, r)
-	if err != nil {
-		elog.Print(err)
-		return
-	}
-
-	os.Exit(0)
+	panic("todo")
 }
 
 func deluser(username string) {

          
@@ 244,66 223,25 @@ func deluser(username string) {
 }
 
 func chpass(username string) {
+	panic("todo")
 	user, err := butwhatabout(username)
 	if err != nil {
 		elog.Fatal(err)
 	}
-	defer func() {
-		os.Exit(1)
-	}()
-	c := make(chan os.Signal, 1)
-	signal.Notify(c, os.Interrupt)
-	go func() {
-		<-c
-		C.termecho(1)
-		fmt.Printf("\n")
-		os.Exit(1)
-	}()
-
 	db := opendatabase()
 	login.Init(login.InitArgs{Db: db, Logger: ilog})
 
-	r := bufio.NewReader(os.Stdin)
-
-	pass, err := askpassword(r)
-	if err != nil {
-		elog.Print(err)
-		return
-	}
+	pass := "password"
 	err = login.SetPassword(int64(user.ID), pass)
 	if err != nil {
 		elog.Print(err)
 		return
 	}
-	fmt.Printf("done\n")
-	os.Exit(0)
 }
 
-func askpassword(r *bufio.Reader) (string, error) {
-	C.termecho(0)
-	fmt.Printf("password: ")
-	pass, err := r.ReadString('\n')
-	C.termecho(1)
-	fmt.Printf("\n")
-	if err != nil {
-		return "", err
-	}
-	pass = pass[:len(pass)-1]
-	if len(pass) < 6 {
-		return "", fmt.Errorf("that's way too short")
-	}
-	return pass, nil
-}
-
-func createuser(db *sql.DB, r *bufio.Reader) error {
-	fmt.Printf("username: ")
-	name, err := r.ReadString('\n')
-	if err != nil {
-		return err
-	}
-	name = name[:len(name)-1]
+func createuser(db *sql.DB, name, pass string) error {
 	if len(name) < 1 {
-		return fmt.Errorf("that's way too short")
+		return fmt.Errorf("username is way too short")
 	}
 	if !re_plainname.MatchString(name) {
 		return fmt.Errorf("alphanumeric only please")

          
@@ 311,9 249,8 @@ func createuser(db *sql.DB, r *bufio.Rea
 	if _, err := butwhatabout(name); err == nil {
 		return fmt.Errorf("user already exists")
 	}
-	pass, err := askpassword(r)
-	if err != nil {
-		return err
+	if len(pass) < 6 {
+		return fmt.Errorf("password is way too short")
 	}
 	hash, err := bcrypt.GenerateFromPassword([]byte(pass), 12)
 	if err != nil {