diff options
-rw-r--r-- | go.mod | 3 | ||||
-rw-r--r-- | go.sum | 6 | ||||
-rw-r--r-- | plugins/carddav/plugin.go | 7 | ||||
-rw-r--r-- | plugins/carddav/public/address-book.html | 2 | ||||
-rw-r--r-- | plugins/carddav/public/update-address-object.html | 23 | ||||
-rw-r--r-- | plugins/carddav/routes.go | 67 | ||||
-rw-r--r-- | themes/sourcehut/address-book.html | 8 |
7 files changed, 112 insertions, 4 deletions
@@ -13,7 +13,8 @@ require ( github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b github.com/emersion/go-smtp v0.12.1 github.com/emersion/go-vcard v0.0.0-20191221110513-5f81fa0d3cc7 - github.com/emersion/go-webdav v0.2.1-0.20200212161312-a892cc58df3f + github.com/emersion/go-webdav v0.2.1-0.20200212201052-236dc078374e + github.com/google/uuid v1.1.1 github.com/gorilla/css v1.0.0 // indirect github.com/labstack/echo/v4 v4.1.15-0.20200203180927-504f39abaf32 github.com/labstack/gommon v0.3.0 @@ -29,8 +29,10 @@ github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0 github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= github.com/emersion/go-vcard v0.0.0-20191221110513-5f81fa0d3cc7 h1:SE+tcd+0kn0cT4MqTo66gmkjqWHF1Z+Yha5/rhLs/H8= github.com/emersion/go-vcard v0.0.0-20191221110513-5f81fa0d3cc7/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM= -github.com/emersion/go-webdav v0.2.1-0.20200212161312-a892cc58df3f h1:xTDJnX1CigheBULqYTvDR+8SiwtSRdqWxdnS92Pc0Dc= -github.com/emersion/go-webdav v0.2.1-0.20200212161312-a892cc58df3f/go.mod h1:dfzmdPRkPLLUQU00fDfwPJomgFmdblZpMoh3CaUsGCc= +github.com/emersion/go-webdav v0.2.1-0.20200212201052-236dc078374e h1:VPvgfZ37O60LoVIU0Vb2qUVzReiKUQ7MV0mmoBqs4Ww= +github.com/emersion/go-webdav v0.2.1-0.20200212201052-236dc078374e/go.mod h1:dfzmdPRkPLLUQU00fDfwPJomgFmdblZpMoh3CaUsGCc= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/labstack/echo/v4 v4.1.15-0.20200203180927-504f39abaf32 h1:UiIEDYxPPmjl6mMIY4QPDguD/QAtK+gR4IRMSrjWmeA= diff --git a/plugins/carddav/plugin.go b/plugins/carddav/plugin.go index 947a754..5afdde2 100644 --- a/plugins/carddav/plugin.go +++ b/plugins/carddav/plugin.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "git.sr.ht/~emersion/koushin" koushinbase "git.sr.ht/~emersion/koushin/plugins/base" @@ -107,6 +108,12 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { registerRoutes(p) + p.TemplateFuncs(map[string]interface{}{ + "join": func(l []string, sep string) string { + return strings.Join(l, sep) + }, + }) + p.Inject("compose.html", func(ctx *koushin.Context, _data koushin.RenderData) error { data := _data.(*koushinbase.ComposeRenderData) diff --git a/plugins/carddav/public/address-book.html b/plugins/carddav/public/address-book.html index f521564..3c31017 100644 --- a/plugins/carddav/public/address-book.html +++ b/plugins/carddav/public/address-book.html @@ -3,7 +3,7 @@ <h1>koushin</h1> <p> - <a href="/">Back</a> + <a href="/">Back</a> ยท <a href="/contacts/create">Create new contact</a> </p> <h2>Contacts: {{.AddressBook.Name}}</h2> diff --git a/plugins/carddav/public/update-address-object.html b/plugins/carddav/public/update-address-object.html new file mode 100644 index 0000000..b0ab20f --- /dev/null +++ b/plugins/carddav/public/update-address-object.html @@ -0,0 +1,23 @@ +{{template "head.html"}} + +<h1>koushin</h1> + +<p> + <a href="/contacts">Back</a> +</p> + +<h2>Edit contact</h2> + +<form method="post"> + <label for="fn">Name:</label> + <input type="text" name="fn" id="fn" value="{{.Card.PreferredValue "FN"}}"> + <br> + + <label for="emails">E-mails:</label> + <input type="email" name="emails" id="emails" multiple value="{{join (.Card.Values "EMAIL") ", "}}"> + <br> + + <input type="submit" value="Save"> +</form> + +{{template "foot.html"}} diff --git a/plugins/carddav/routes.go b/plugins/carddav/routes.go index 45a6237..e05f32d 100644 --- a/plugins/carddav/routes.go +++ b/plugins/carddav/routes.go @@ -3,10 +3,13 @@ package koushincarddav import ( "fmt" "net/http" + "path" + "strings" "git.sr.ht/~emersion/koushin" "github.com/emersion/go-vcard" "github.com/emersion/go-webdav/carddav" + "github.com/google/uuid" ) type AddressBookRenderData struct { @@ -21,6 +24,12 @@ type AddressObjectRenderData struct { AddressObject *carddav.AddressObject } +type UpdateAddressObjectRenderData struct { + koushin.BaseRenderData + AddressObject *carddav.AddressObject // nil if creating a new contact + Card vcard.Card +} + func registerRoutes(p *plugin) { p.GET("/contacts", func(ctx *koushin.Context) error { queryText := ctx.QueryParam("query") @@ -107,4 +116,62 @@ func registerRoutes(p *plugin) { AddressObject: addr, }) }) + + createContact := func(ctx *koushin.Context) error { + card := make(vcard.Card) + + if ctx.Request().Method == "POST" { + fn := ctx.FormValue("fn") + emails := strings.Split(ctx.FormValue("emails"), ",") + + // Some CardDAV servers (e.g. Google) don't support vCard 4.0 + // TODO: get supported formats from server, use highest version + if _, ok := card[vcard.FieldVersion]; !ok { + card.SetValue(vcard.FieldVersion, "3.0") + } + + if field := card.Preferred(vcard.FieldFormattedName); field != nil { + field.Value = fn + } else { + card.Add(vcard.FieldFormattedName, &vcard.Field{Value: fn}) + } + + // TODO: Google wants a "N" field, fails with a 400 otherwise + + // TODO: params are lost here + var emailFields []*vcard.Field + for _, email := range emails { + emailFields = append(emailFields, &vcard.Field{ + Value: strings.TrimSpace(email), + }) + } + card[vcard.FieldEmail] = emailFields + + id := uuid.New() + if _, ok := card[vcard.FieldUID]; !ok { + card.SetValue(vcard.FieldUID, id.URN()) + } + + c, addressBook, err := p.clientWithAddressBook(ctx.Session) + if err != nil { + return err + } + + p := path.Join(addressBook.Path, id.String() + ".vcf") + _, err = c.PutAddressObject(p, card) + if err != nil { + return fmt.Errorf("failed to put address object: %v", err) + } + // TODO: check if the returned AddressObject's path matches, if not + // fetch the new UID (the server may mutate it) + + return ctx.Redirect(http.StatusFound, "/contacts/" + card.Value(vcard.FieldUID)) + } + + return ctx.Render(http.StatusOK, "update-address-object.html", &UpdateAddressObjectRenderData{ + BaseRenderData: *koushin.NewBaseRenderData(ctx), + }) + } + p.GET("/contacts/create", createContact) + p.POST("/contacts/create", createContact) } diff --git a/themes/sourcehut/address-book.html b/themes/sourcehut/address-book.html index 2df65c1..666e08a 100644 --- a/themes/sourcehut/address-book.html +++ b/themes/sourcehut/address-book.html @@ -5,6 +5,14 @@ <div class="row"> <div class="col-md-12 header-tabbed"> <h2>Contacts</h2> + <ul class="nav nav-tabs"> + <li class="nav-item"> + <a class="nav-link active" href="/contacts">Contacts</a> + </li> + <li class="nav-item"> + <a class="nav-link" href="/contacts/create">Create contact</a> + </li> + </ul> </div> </div> </div> |