Compare commits

...

2 Commits

Author SHA1 Message Date
7aa3c4fb3f Get and set site name 2024-11-16 18:21:51 -07:00
beef4523c2 Set URL after loading database 2024-11-15 11:55:31 -07:00
11 changed files with 303 additions and 118 deletions

71
db.go
View File

@ -1,6 +1,7 @@
package peachy
import (
_ "embed"
"errors"
"fmt"
"log"
@ -29,8 +30,10 @@ type DB struct {
conn *sqlite.Conn
}
func (db *DB) Close() {
db.conn.Close()
func (db DB) Close() {
if db.conn != nil {
db.conn.Close()
}
}
var tableNameMatcher = match.MustCompile("(%s%s)")
@ -72,7 +75,7 @@ func setupConn(conn *sqlite.Conn) error {
})
}
func Open(path string) (db *DB, err error) {
func Open(path string) (db DB, err error) {
var conn *sqlite.Conn
defer func() {
if err != nil {
@ -84,11 +87,11 @@ func Open(path string) (db *DB, err error) {
switch sqlite.ErrCode(err) {
case sqlite.ResultOK:
case sqlite.ResultCantOpen:
return nil, ErrFileNotExist
return db, ErrFileNotExist
case sqlite.ResultNotADB:
return nil, ErrInvalidDB
return db, ErrInvalidDB
default:
return nil, DBError{err}
return db, DBError{err}
}
var goodAppID bool
@ -98,16 +101,20 @@ func Open(path string) (db *DB, err error) {
return nil
}})
if !goodAppID {
return nil, ErrInvalidDB
return db, ErrInvalidDB
}
err = setupConn(conn)
if err != nil {
return nil, err
return db, err
}
return &DB{conn: conn}, nil
return DB{conn: conn}, nil
}
func Create(path string) (db *DB, err error) {
//go:embed metadata_schema.sql
var metadataSchema string
func Create(path string) (db DB, err error) {
fmt.Println("metadata schema:\n", metadataSchema)
var conn *sqlite.Conn
defer func() {
if err != nil {
@ -117,23 +124,29 @@ func Create(path string) (db *DB, err error) {
finfo, _ := os.Stat(path)
if finfo != nil {
return nil, ErrFileExists
return db, ErrFileExists
}
conn, err = sqlite.OpenConn(path, sqlite.OpenCreate|sqlite.OpenReadWrite|sqlite.OpenWAL)
if err != nil {
return nil, fmt.Errorf("could not create database: %w", err)
return db, fmt.Errorf("could not create database: %w", err)
}
query := fmt.Sprintf("PRAGMA application_id=%d", AppID)
err = sqlitex.ExecuteTransient(conn, query, nil)
if err != nil {
return nil, DBError{err}
return db, DBError{err}
}
err = sqlitex.ExecuteScript(conn, metadataSchema, nil)
if err != nil {
return db, DBError{err}
}
err = setupConn(conn)
if err != nil {
return nil, err
return db, err
}
return &DB{conn: conn}, nil
return DB{conn: conn}, nil
}
func quoteName(name string) string {
@ -161,7 +174,7 @@ const addAbstractCompositeTypeQuery = `CREATE TABLE %s (
%s_value_id INTEGER NOT NULL REFERENCES %s(id)
);`
func (db *DB) AddCompositeType(name string, kind CompositeKind) error {
func (db DB) AddCompositeType(name string, kind CompositeKind) error {
var kindStr string
switch kind {
case Record:
@ -201,7 +214,7 @@ FROM sqlite_schema
WHERE parse_table_name(name, 'class') IN ('record-value', 'variant-value')
ORDER BY parse_table_name(name, 'name') ASC;`
func (db *DB) GetCompositeTypes() ([]CompositeType, error) {
func (db DB) GetCompositeTypes() ([]CompositeType, error) {
var results []CompositeType
err := sqlitex.Execute(db.conn, getCompositeTypesQuery, &sqlitex.ExecOptions{
ResultFunc: func(stmt *sqlite.Stmt) error {
@ -220,3 +233,27 @@ func (db *DB) GetCompositeTypes() ([]CompositeType, error) {
}
return results, nil
}
func (db DB) SiteName() (string, error) {
var name string
err := sqlitex.Execute(db.conn, "SELECT name FROM site_metadata", &sqlitex.ExecOptions{
ResultFunc: func(stmt *sqlite.Stmt) error {
name = stmt.ColumnText(0)
return nil
},
})
if err != nil {
err = fmt.Errorf("SiteName: %w", err)
}
return name, err
}
func (db DB) SetSiteName(name string) error {
err := sqlitex.Execute(db.conn, "UPDATE site_metadata SET name = ?", &sqlitex.ExecOptions{
Args: []any{name},
})
if err != nil {
return fmt.Errorf("SetSiteName: %w", err)
}
return nil
}

View File

@ -6,6 +6,7 @@ import (
"testing"
"git.codemonkeysoftware.net/b/peachy-go"
"github.com/shoenig/test"
"github.com/shoenig/test/must"
"zombiezen.com/go/sqlite"
)
@ -14,12 +15,6 @@ func pending(t *testing.T) {
t.Fatalf("test not implemented")
}
func closeIfExists(db *peachy.DB) {
if db != nil {
db.Close()
}
}
func TestOpen(t *testing.T) {
t.Run("Open succeeds on DB created by Create", func(t *testing.T) {
dir := t.TempDir()
@ -29,8 +24,7 @@ func TestOpen(t *testing.T) {
db.Close()
db, err = peachy.Open(dbPath)
defer closeIfExists(db)
must.NotNil(t, db)
defer db.Close()
must.NoError(t, err)
// TODO Make sure the DB works once it has any methods.
@ -43,16 +37,14 @@ func TestOpen(t *testing.T) {
conn.Close()
db, err := peachy.Open(dbPath)
defer closeIfExists(db)
must.Nil(t, db)
defer db.Close()
must.ErrorIs(t, err, peachy.ErrInvalidDB)
})
t.Run("Open fails on nonexistent file", func(t *testing.T) {
dir := t.TempDir()
dbPath := filepath.Join(dir, "test.db")
db, err := peachy.Open(dbPath)
defer closeIfExists(db)
must.Nil(t, db)
defer db.Close()
must.ErrorIs(t, err, peachy.ErrFileNotExist)
must.FileNotExists(t, dbPath)
})
@ -62,12 +54,27 @@ func TestOpen(t *testing.T) {
must.NoError(t, err)
db, err := peachy.Open(path)
defer closeIfExists(db)
must.Nil(t, db)
defer db.Close()
must.ErrorIs(t, err, peachy.ErrInvalidDB, must.Sprint(sqlite.ErrCode(err)))
})
}
func TestSiteName(t *testing.T) {
db, err := peachy.Create(":memory:")
must.NoError(t, err)
initial, err := db.SiteName()
test.NoError(t, err)
test.EqOp(t, "", initial)
const expected = "aoeuhtns"
test.NoError(t, db.SetSiteName(expected))
actual, err := db.SiteName()
test.NoError(t, err)
test.EqOp(t, expected, actual)
}
func TestCompositeTypes(t *testing.T) {
db, err := peachy.Create(":memory:")
must.NoError(t, err)

View File

@ -1,3 +0,0 @@
package app
// TODO: Add your code here

5
desktop/fields/fields.go Normal file
View File

@ -0,0 +1,5 @@
package fields
const (
SiteName = "sitename"
)

View File

@ -2,9 +2,13 @@ package main
import (
"embed"
"fmt"
"log"
"log/slog"
"net/http"
"net/url"
"git.codemonkeysoftware.net/b/peachy-go"
"git.codemonkeysoftware.net/b/peachy-go/desktop/routes"
"github.com/wailsapp/wails/v3/pkg/application"
)
@ -12,15 +16,67 @@ import (
//go:embed all:static
var assets embed.FS
const ListenPort = "9245"
const WindowNameMain = "mainwindow"
var globalDB peachy.DB
func closeDB() {
globalDB.Close()
}
// SetURl is a workaround for WebviewWindow.SetURL not adding the base URL
// to relative URLs.
// TODO Report this or something.
func SetURL(win application.Window, path string) application.Window {
slog.Info("setting URL the hard way")
u, _ := url.JoinPath("http://wails.localhost:"+ListenPort+"/", path)
return win.SetURL(u)
}
func setDB(db peachy.DB) error {
slog.Info("opened DB, setting URL")
globalDB = db
SetURL(application.Get().GetWindowByName(WindowNameMain), "/site")
return nil
}
func createDB(filename string) error {
db, err := peachy.Create(filename)
if err != nil {
return fmt.Errorf("Failed to create database: %w", err)
}
return setDB(db)
}
func openDB(filename string) error {
db, err := peachy.Open(filename)
if err != nil {
return fmt.Errorf("Failed to open database: %w", err)
}
return setDB(db)
}
func showError(err error) {
if err != nil {
application.ErrorDialog().SetMessage(err.Error()).Show()
}
}
func dbFileFilter() []application.FileFilter {
return []application.FileFilter{
{DisplayName: "Peachy database", Pattern: "*.pit"},
{DisplayName: "All Files", Pattern: "*.*"},
}
}
func main() {
r := routes.NewChiRouter(ListenPort, &globalDB)
r := routes.NewChiRouter()
// Create the application
app := application.New(application.Options{
Name: "Peachy",
Description: "A demo of using raw HTML & CSS", // Description of the application
Assets: application.AssetOptions{ // Assets to embed (our static files)
Description: "A desktop CMS for static sites",
Assets: application.AssetOptions{
Handler: application.AssetFileServerFS(assets),
Middleware: func(next http.Handler) http.Handler {
r.NotFound(next.ServeHTTP)
@ -31,9 +87,12 @@ func main() {
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
})
app.OnShutdown(func() {
closeDB()
})
// V3 introduces multiple windows, so we need to create a window
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Name: WindowNameMain,
Title: "Your Project",
Mac: application.MacWindow{
Backdrop: application.MacBackdropTranslucent,
@ -52,31 +111,24 @@ func main() {
menu.FindByRole(application.NewFile).OnClick(func(ctx *application.Context) {
d := application.SaveFileDialog()
d.SetOptions(&application.SaveFileDialogOptions{
Filters: []application.FileFilter{
{DisplayName: "Peachy database", Pattern: "*.pit"},
{DisplayName: "All Files", Pattern: "*.*"},
},
Filters: dbFileFilter(),
AllowOtherFileTypes: true,
Title: "Create new database",
})
path, err := d.PromptForSingleSelection()
if err != nil {
application.InfoDialog().SetMessage("Error: " + err.Error()).Show()
}
path, _ := d.PromptForSingleSelection()
if path != "" {
application.InfoDialog().SetMessage(path).Show()
} else {
application.InfoDialog().SetMessage("No file selected").Show()
showError(createDB(path))
}
})
menu.FindByRole(application.Open).OnClick(func(ctx *application.Context) {
path, _ := application.OpenFileDialog().
CanChooseFiles(true).
PromptForSingleSelection()
d := application.OpenFileDialog()
d.SetOptions(&application.OpenFileDialogOptions{
Filters: dbFileFilter(),
CanChooseFiles: true,
})
path, _ := d.PromptForSingleSelection()
if path != "" {
application.InfoDialog().SetMessage(path).Show()
} else {
application.InfoDialog().SetMessage("No file selected").Show()
showError(openDB(path))
}
})
app.SetMenu(menu)

View File

@ -4,37 +4,39 @@ import (
h "git.codemonkeysoftware.net/b/hatmill"
a "git.codemonkeysoftware.net/b/hatmill/attribute"
e "git.codemonkeysoftware.net/b/hatmill/element"
"git.codemonkeysoftware.net/b/peachy-go"
"git.codemonkeysoftware.net/b/peachy-go/desktop/fields"
"git.codemonkeysoftware.net/b/peachy-go/hx"
)
func hxGet(url string) h.Attrib {
return h.Attrib{
Key: "hx-get",
Value: a.String(url),
func HomePage(db *peachy.DB) (func() h.ParentElement, error) {
siteName, err := db.SiteName()
if err != nil {
return nil, err
}
return func() h.ParentElement {
return e.Div()(
e.Input(
a.Type("text"),
a.Value(siteName),
a.Placeholder("site name"),
a.Name(fields.SiteName),
hx.Trigger("change"),
hx.Target("this"),
hx.Patch("/site"),
hx.Swap(hx.OuterHTML),
),
e.Button(
a.Type("button"),
hx.Get("/hello"),
hx.Trigger("click"),
hx.Target("#hello"),
)(h.Text("Click Here!")),
e.Div(a.Id("hello"))(),
)
}, nil
}
func hxTrigger(event string) h.Attrib {
return h.Attrib{
Key: "hx-trigger",
Value: a.String(event),
}
}
func hxTarget(target string) h.Attrib {
return h.Attrib{
Key: "hx-target",
Value: a.String(target),
}
}
func HomePage() h.ParentElement {
return e.Div()(
e.Button(
a.Type("button"),
hxGet("/hello"),
hxTrigger("click"),
hxTarget("#hello"),
)(h.Text("Click Here!")),
e.Div(a.Id("hello"))(),
)
func Splash() h.Term {
return h.Terms{}
}

View File

@ -4,7 +4,9 @@ import (
"log/slog"
"net/http"
"git.codemonkeysoftware.net/b/peachy-go"
"git.codemonkeysoftware.net/b/peachy-go/desktop/components"
"git.codemonkeysoftware.net/b/peachy-go/desktop/fields"
"git.codemonkeysoftware.net/b/peachy-go/desktop/pages"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
@ -14,13 +16,11 @@ import (
/*
Create a new chi router, configure it and return it.
*/
func NewChiRouter() *chi.Mux {
func NewChiRouter(port string, db *peachy.DB) *chi.Mux {
r := chi.NewRouter()
// Useful middleware, see : https://pkg.go.dev/github.com/go-chi/httplog/v2@v2.1.1#NewLogger
logger := httplog.NewLogger("app-logger", httplog.Options{
// All log
LogLevel: slog.LevelInfo,
Concise: true,
})
@ -40,29 +40,40 @@ func NewChiRouter() *chi.Mux {
})
*/
// Serve static files.
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
// Home page
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
// Render home page
HXRender(w, r, pages.HomePage)
HXRender(w, r, pages.Splash)
})
// 200 OK status
r.Get("/site", func(w http.ResponseWriter, r *http.Request) {
page, err := pages.HomePage(db)
if err != nil {
logger.Error("internal error", "path", r.URL.Path, "error", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
HXRender(w, r, page)
})
r.Patch("/site", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
if r.Form.Has(fields.SiteName) {
err := db.SetSiteName(r.Form.Get(fields.SiteName))
if err != nil {
logger.Error("internal error", "path", r.URL.Path, "error", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
w.WriteHeader(http.StatusOK)
})
// Hello page
r.Get("/hello", func(w http.ResponseWriter, r *http.Request) {
// Render hello
HXRender(w, r, components.HelloWorld)
// 200 OK status
w.WriteHeader(http.StatusOK)
})
// Listen to port 3000.
go http.ListenAndServe(":9245", r)
go http.ListenAndServe(":"+port, r)
return r
}

12
go.mod
View File

@ -9,21 +9,21 @@ require (
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/shoenig/test v1.11.0
pgregory.net/rapid v1.1.0
zombiezen.com/go/sqlite v1.3.0
zombiezen.com/go/sqlite v1.4.0
)
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/sys v0.21.0 // indirect
modernc.org/libc v1.41.0 // indirect
golang.org/x/sys v0.22.0 // indirect
modernc.org/libc v1.55.3 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/sqlite v1.29.1 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/sqlite v1.33.1 // indirect
)
replace git.codemonkeysoftware.net/b/gigaparsec v0.0.0-20240917174243-4fa3c2a46611 => ../gigaparsec

40
go.sum
View File

@ -52,8 +52,8 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
@ -169,8 +169,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
@ -204,15 +204,31 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/sqlite v1.29.1 h1:19GY2qvWB4VPw0HppFlZCPAbmxFU41r+qjKZQdQ1ryA=
modernc.org/sqlite v1.29.1/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
zombiezen.com/go/sqlite v1.3.0 h1:98g1gnCm+CNz6AuQHu0gqyw7gR2WU3O3PJufDOStpUs=
zombiezen.com/go/sqlite v1.3.0/go.mod h1:yRl27//s/9aXU3RWs8uFQwjkTG9gYNGEls6+6SvrclY=
zombiezen.com/go/sqlite v1.4.0 h1:N1s3RIljwtp4541Y8rM880qgGIgq3fTD2yks1xftnKU=
zombiezen.com/go/sqlite v1.4.0/go.mod h1:0w9F1DN9IZj9AcLS9YDKMboubCACkwYCGkzoy3eG5ik=

54
hx/hx.go Normal file
View File

@ -0,0 +1,54 @@
package hx
import (
h "git.codemonkeysoftware.net/b/hatmill"
a "git.codemonkeysoftware.net/b/hatmill/attribute"
)
func Get(url string) h.Attrib {
return h.Attrib{
Key: "hx-get",
Value: a.String(url),
}
}
func Patch(url string) h.Attrib {
return h.Attrib{
Key: "hx-patch",
Value: a.String(url),
}
}
func Trigger(event string) h.Attrib {
return h.Attrib{
Key: "hx-trigger",
Value: a.String(event),
}
}
func Target(target string) h.Attrib {
return h.Attrib{
Key: "hx-target",
Value: a.String(target),
}
}
type SwapKind string
const (
InnerHTML SwapKind = "innerHTML"
OuterHTML SwapKind = "outerHTML"
AfterBegin SwapKind = "afterbegin"
BeforeBegin SwapKind = "beforebegin"
BeforeEnd SwapKind = "beforeend"
AfterEnd SwapKind = "afterend"
Delete SwapKind = "delete"
None SwapKind = "none"
)
func Swap(kind SwapKind) h.Attrib {
return h.Attrib{
Key: "hx-target",
Value: a.String(kind),
}
}

4
metadata_schema.sql Normal file
View File

@ -0,0 +1,4 @@
CREATE TABLE site_metadata (
name TEXT NOT NULL
);
INSERT INTO site_metadata(name) VALUES ('');