aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--go.mod3
-rw-r--r--go.sum6
-rw-r--r--plugins/carddav/plugin.go7
-rw-r--r--plugins/carddav/public/address-book.html2
-rw-r--r--plugins/carddav/public/update-address-object.html23
-rw-r--r--plugins/carddav/routes.go67
-rw-r--r--themes/sourcehut/address-book.html8
7 files changed, 112 insertions, 4 deletions
diff --git a/go.mod b/go.mod
index 70f3f93..2dc7ba1 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index c2cff43..67a10ac 100644
--- a/go.sum
+++ b/go.sum
@@ -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>