plugins/carddav: add page to create new contact
This commit is contained in:
parent
e24e20e528
commit
c4ff33e645
7 changed files with 112 additions and 4 deletions
3
go.mod
3
go.mod
|
@ -13,7 +13,8 @@ require (
|
||||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b
|
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b
|
||||||
github.com/emersion/go-smtp v0.12.1
|
github.com/emersion/go-smtp v0.12.1
|
||||||
github.com/emersion/go-vcard v0.0.0-20191221110513-5f81fa0d3cc7
|
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/gorilla/css v1.0.0 // indirect
|
||||||
github.com/labstack/echo/v4 v4.1.15-0.20200203180927-504f39abaf32
|
github.com/labstack/echo/v4 v4.1.15-0.20200203180927-504f39abaf32
|
||||||
github.com/labstack/gommon v0.3.0
|
github.com/labstack/gommon v0.3.0
|
||||||
|
|
6
go.sum
6
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-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 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-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.20200212201052-236dc078374e h1:VPvgfZ37O60LoVIU0Vb2qUVzReiKUQ7MV0mmoBqs4Ww=
|
||||||
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/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 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
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=
|
github.com/labstack/echo/v4 v4.1.15-0.20200203180927-504f39abaf32 h1:UiIEDYxPPmjl6mMIY4QPDguD/QAtK+gR4IRMSrjWmeA=
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.sr.ht/~emersion/koushin"
|
"git.sr.ht/~emersion/koushin"
|
||||||
koushinbase "git.sr.ht/~emersion/koushin/plugins/base"
|
koushinbase "git.sr.ht/~emersion/koushin/plugins/base"
|
||||||
|
@ -107,6 +108,12 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) {
|
||||||
|
|
||||||
registerRoutes(p)
|
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 {
|
p.Inject("compose.html", func(ctx *koushin.Context, _data koushin.RenderData) error {
|
||||||
data := _data.(*koushinbase.ComposeRenderData)
|
data := _data.(*koushinbase.ComposeRenderData)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<h1>koushin</h1>
|
<h1>koushin</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="/">Back</a>
|
<a href="/">Back</a> · <a href="/contacts/create">Create new contact</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Contacts: {{.AddressBook.Name}}</h2>
|
<h2>Contacts: {{.AddressBook.Name}}</h2>
|
||||||
|
|
23
plugins/carddav/public/update-address-object.html
Normal file
23
plugins/carddav/public/update-address-object.html
Normal file
|
@ -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"}}
|
|
@ -3,10 +3,13 @@ package koushincarddav
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.sr.ht/~emersion/koushin"
|
"git.sr.ht/~emersion/koushin"
|
||||||
"github.com/emersion/go-vcard"
|
"github.com/emersion/go-vcard"
|
||||||
"github.com/emersion/go-webdav/carddav"
|
"github.com/emersion/go-webdav/carddav"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AddressBookRenderData struct {
|
type AddressBookRenderData struct {
|
||||||
|
@ -21,6 +24,12 @@ type AddressObjectRenderData struct {
|
||||||
AddressObject *carddav.AddressObject
|
AddressObject *carddav.AddressObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateAddressObjectRenderData struct {
|
||||||
|
koushin.BaseRenderData
|
||||||
|
AddressObject *carddav.AddressObject // nil if creating a new contact
|
||||||
|
Card vcard.Card
|
||||||
|
}
|
||||||
|
|
||||||
func registerRoutes(p *plugin) {
|
func registerRoutes(p *plugin) {
|
||||||
p.GET("/contacts", func(ctx *koushin.Context) error {
|
p.GET("/contacts", func(ctx *koushin.Context) error {
|
||||||
queryText := ctx.QueryParam("query")
|
queryText := ctx.QueryParam("query")
|
||||||
|
@ -107,4 +116,62 @@ func registerRoutes(p *plugin) {
|
||||||
AddressObject: addr,
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,14 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 header-tabbed">
|
<div class="col-md-12 header-tabbed">
|
||||||
<h2>Contacts</h2>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue