wren/val Public Public: anyone can read this; nobody can change it.

forked from benjivers/val

workbenchthis machine
Files a07c6a614323699908d79cb29213e15bff012908
1
/**
2
 * val, in a terminal. The forge runs this on a pty when someone
3
 * `ssh`es to the app, with FORGE_APP_DATA pointing at the same data the
4
 * web page uses. j/k move, a adds a note, d deletes the one under the
5
 * cursor, q leaves. Plain ANSI, no dependencies.
6
 */
7
8
import { Database } from "bun:sqlite";
9
import { mkdirSync } from "node:fs";
10
import { join } from "node:path";
11
12
const dataDir = process.env.FORGE_APP_DATA ?? "./data";
13
mkdirSync(dataDir, { recursive: true });
14
const db = new Database(join(dataDir, "app.db"));
15
db.exec("PRAGMA journal_mode = WAL");
16
db.exec(
17
  "CREATE TABLE IF NOT EXISTS note (id INTEGER PRIMARY KEY, body TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')))",
18
);
19
const where = process.env.FORGE_APP_ENV === "preview" ? `preview of #${process.env.FORGE_APP_PULL ?? "?"}` : (process.env.FORGE_APP_ENV ?? "local");
20
21
type Note = { id: number; body: string; created_at: string };
22
const notes = (): Note[] => db.query<Note, []>("SELECT id, body, created_at FROM note ORDER BY id DESC").all();
23
24
let cursor = 0;
25
let typing: string | undefined;
26
const out = (s: string): void => { process.stdout.write(s); };
27
const esc = "\x1b[";
28
29
function draw(): void {
30
  const rows = notes();
31
  const lines: string[] = [];
32
  lines.push(`${esc}1mval${esc}0m  ${esc}2m${where}${esc}0m`);
33
  lines.push("");
34
  if (rows.length === 0) lines.push(`${esc}2mNo notes yet. Press a to add one.${esc}0m`);
35
  rows.forEach((n, i) => {
36
    const mark = i === cursor ? "▸" : " ";
37
    const body = i === cursor ? `${esc}1m${n.body}${esc}0m` : n.body;
38
    lines.push(`${mark} ${body}  ${esc}2m${n.created_at}${esc}0m`);
39
  });
40
  lines.push("");
41
  lines.push(typing === undefined ? `${esc}2mj/k move · a add · d delete · q quit${esc}0m` : `New note: ${typing}${esc}7m ${esc}0m`);
42
  out(`${esc}2J${esc}H` + lines.join("\r\n"));
43
}
44
45
process.stdin.setRawMode?.(true);
46
process.stdin.resume();
47
process.stdin.setEncoding("utf8");
48
draw();
49
process.stdin.on("data", (key: string) => {
50
  if (typing !== undefined) {
51
    if (key === "\r") {
52
      const body = typing.trim();
53
      if (body) db.query("INSERT INTO note (body) VALUES (?1)").run(body.slice(0, 500));
54
      typing = undefined;
55
      cursor = 0;
56
    } else if (key === "\x1b") typing = undefined;
57
    else if (key === "\x7f") typing = typing.slice(0, -1);
58
    else if (key >= " ") typing += key;
59
    draw();
60
    return;
61
  }
62
  if (key === "q" || key === "\x03") { out(`${esc}2J${esc}H`); process.exit(0); }
63
  const count = notes().length;
64
  if (key === "j" || key === "\x1b[B") cursor = Math.min(cursor + 1, Math.max(0, count - 1));
65
  if (key === "k" || key === "\x1b[A") cursor = Math.max(cursor - 1, 0);
66
  if (key === "a") typing = "";
67
  if (key === "d") {
68
    const n = notes()[cursor];
69
    if (n) db.query("DELETE FROM note WHERE id = ?1").run(n.id);
70
    cursor = Math.min(cursor, Math.max(0, notes().length - 1));
71
  }
72
  draw();
73
});
74
process.stdout.on("resize", draw);
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts