mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-21 18:08:25 +00:00
Provide email proposals from address book
This commit is contained in:
@ -12,6 +12,7 @@ module Api exposing
|
||||
, deleteUser
|
||||
, getCollective
|
||||
, getCollectiveSettings
|
||||
, getContacts
|
||||
, getEquipments
|
||||
, getInsights
|
||||
, getItemProposals
|
||||
@ -64,6 +65,7 @@ import Api.Model.AuthResult exposing (AuthResult)
|
||||
import Api.Model.BasicResult exposing (BasicResult)
|
||||
import Api.Model.Collective exposing (Collective)
|
||||
import Api.Model.CollectiveSettings exposing (CollectiveSettings)
|
||||
import Api.Model.ContactList exposing (ContactList)
|
||||
import Api.Model.DirectionValue exposing (DirectionValue)
|
||||
import Api.Model.EmailSettings exposing (EmailSettings)
|
||||
import Api.Model.EmailSettingsList exposing (EmailSettingsList)
|
||||
@ -98,6 +100,7 @@ import Api.Model.User exposing (User)
|
||||
import Api.Model.UserList exposing (UserList)
|
||||
import Api.Model.UserPass exposing (UserPass)
|
||||
import Api.Model.VersionInfo exposing (VersionInfo)
|
||||
import Data.ContactType exposing (ContactType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import File exposing (File)
|
||||
import Http
|
||||
@ -370,6 +373,48 @@ setCollectiveSettings flags settings receive =
|
||||
}
|
||||
|
||||
|
||||
getContacts :
|
||||
Flags
|
||||
-> Maybe ContactType
|
||||
-> Maybe String
|
||||
-> (Result Http.Error ContactList -> msg)
|
||||
-> Cmd msg
|
||||
getContacts flags kind q receive =
|
||||
let
|
||||
pk =
|
||||
case kind of
|
||||
Just k ->
|
||||
[ "kind=" ++ Data.ContactType.toString k ]
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
|
||||
pq =
|
||||
case q of
|
||||
Just str ->
|
||||
[ "q=" ++ str ]
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
|
||||
params =
|
||||
pk ++ pq
|
||||
|
||||
query =
|
||||
case String.join "&" params of
|
||||
"" ->
|
||||
""
|
||||
|
||||
str ->
|
||||
"?" ++ str
|
||||
in
|
||||
Http2.authGet
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/collective/contacts" ++ query
|
||||
, account = getAccount flags
|
||||
, expect = Http.expectJson receive Api.Model.ContactList.decoder
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- Tags
|
||||
|
||||
|
190
modules/webapp/src/main/elm/Comp/EmailInput.elm
Normal file
190
modules/webapp/src/main/elm/Comp/EmailInput.elm
Normal file
@ -0,0 +1,190 @@
|
||||
module Comp.EmailInput exposing
|
||||
( Model
|
||||
, Msg
|
||||
, init
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Api
|
||||
import Api.Model.ContactList exposing (ContactList)
|
||||
import Data.ContactType
|
||||
import Data.Flags exposing (Flags)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick, onInput)
|
||||
import Http
|
||||
import Util.Html exposing (onKeyUp)
|
||||
import Util.List
|
||||
import Util.Maybe
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ input : String
|
||||
, menuOpen : Bool
|
||||
, candidates : List String
|
||||
, active : Maybe String
|
||||
}
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ input = ""
|
||||
, menuOpen = False
|
||||
, candidates = []
|
||||
, active = Nothing
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= SetInput String
|
||||
| ContactResp (Result Http.Error ContactList)
|
||||
| KeyPress Int
|
||||
| AddEmail String
|
||||
| RemoveEmail String
|
||||
|
||||
|
||||
getCandidates : Flags -> Model -> Cmd Msg
|
||||
getCandidates flags model =
|
||||
case Util.Maybe.fromString model.input of
|
||||
Just q ->
|
||||
Api.getContacts flags (Just Data.ContactType.Email) (Just q) ContactResp
|
||||
|
||||
Nothing ->
|
||||
Cmd.none
|
||||
|
||||
|
||||
update : Flags -> List String -> Msg -> Model -> ( Model, Cmd Msg, List String )
|
||||
update flags current msg model =
|
||||
case msg of
|
||||
SetInput str ->
|
||||
let
|
||||
nm =
|
||||
{ model | input = str, menuOpen = str /= "" }
|
||||
in
|
||||
( nm, getCandidates flags nm, current )
|
||||
|
||||
ContactResp (Ok list) ->
|
||||
( { model
|
||||
| candidates = List.map .value (List.take 10 list.items)
|
||||
, active = Nothing
|
||||
, menuOpen = list.items /= []
|
||||
}
|
||||
, Cmd.none
|
||||
, current
|
||||
)
|
||||
|
||||
ContactResp (Err _) ->
|
||||
( model, Cmd.none, current )
|
||||
|
||||
KeyPress code ->
|
||||
let
|
||||
addCurrent =
|
||||
let
|
||||
email =
|
||||
Maybe.withDefault model.input model.active
|
||||
in
|
||||
update flags current (AddEmail email) model
|
||||
in
|
||||
case Util.Html.intToKeyCode code of
|
||||
Just Util.Html.Up ->
|
||||
let
|
||||
prev =
|
||||
case model.active of
|
||||
Nothing ->
|
||||
List.reverse model.candidates
|
||||
|> List.head
|
||||
|
||||
Just act ->
|
||||
Util.List.findPrev (\e -> e == act) model.candidates
|
||||
in
|
||||
( { model | active = prev }, Cmd.none, current )
|
||||
|
||||
Just Util.Html.Down ->
|
||||
let
|
||||
next =
|
||||
case model.active of
|
||||
Nothing ->
|
||||
List.head model.candidates
|
||||
|
||||
Just act ->
|
||||
Util.List.findNext (\e -> e == act) model.candidates
|
||||
in
|
||||
( { model | active = next }, Cmd.none, current )
|
||||
|
||||
Just Util.Html.Enter ->
|
||||
addCurrent
|
||||
|
||||
Just Util.Html.Space ->
|
||||
addCurrent
|
||||
|
||||
_ ->
|
||||
( model, Cmd.none, current )
|
||||
|
||||
AddEmail str ->
|
||||
( { model | input = "", menuOpen = False }
|
||||
, Cmd.none
|
||||
, Util.List.distinct (current ++ [ String.trim str ])
|
||||
)
|
||||
|
||||
RemoveEmail str ->
|
||||
( model, Cmd.none, List.filter (\e -> e /= str) current )
|
||||
|
||||
|
||||
view : List String -> Model -> Html Msg
|
||||
view values model =
|
||||
div
|
||||
[ classList
|
||||
[ ( "ui search dropdown multiple selection", True )
|
||||
, ( "open", model.menuOpen )
|
||||
]
|
||||
]
|
||||
(List.map renderValue values
|
||||
++ [ input
|
||||
[ type_ "text"
|
||||
, class "search"
|
||||
, placeholder "Recipients…"
|
||||
, onKeyUp KeyPress
|
||||
, onInput SetInput
|
||||
]
|
||||
[ text model.input
|
||||
]
|
||||
]
|
||||
++ [ renderMenu model ]
|
||||
)
|
||||
|
||||
|
||||
renderValue : String -> Html Msg
|
||||
renderValue str =
|
||||
a
|
||||
[ class "ui label"
|
||||
, href "#"
|
||||
, onClick (RemoveEmail str)
|
||||
]
|
||||
[ text str
|
||||
, i [ class "delete icon" ] []
|
||||
]
|
||||
|
||||
|
||||
renderMenu : Model -> Html Msg
|
||||
renderMenu model =
|
||||
let
|
||||
mkItem v =
|
||||
a
|
||||
[ classList
|
||||
[ ( "item", True )
|
||||
, ( "active", model.active == Just v )
|
||||
]
|
||||
, href "#"
|
||||
, onClick (AddEmail v)
|
||||
]
|
||||
[ text v
|
||||
]
|
||||
in
|
||||
div
|
||||
[ classList
|
||||
[ ( "menu", True )
|
||||
, ( "transition visible", model.menuOpen )
|
||||
]
|
||||
]
|
||||
(List.map mkItem model.candidates)
|
@ -715,12 +715,12 @@ update key flags next msg model =
|
||||
|
||||
ItemMailMsg m ->
|
||||
let
|
||||
( im, fa ) =
|
||||
Comp.ItemMail.update m model.itemMail
|
||||
( im, ic, fa ) =
|
||||
Comp.ItemMail.update flags m model.itemMail
|
||||
in
|
||||
case fa of
|
||||
Comp.ItemMail.FormNone ->
|
||||
( { model | itemMail = im }, Cmd.none )
|
||||
( { model | itemMail = im }, Cmd.map ItemMailMsg ic )
|
||||
|
||||
Comp.ItemMail.FormCancel ->
|
||||
( { model
|
||||
@ -728,7 +728,7 @@ update key flags next msg model =
|
||||
, mailOpen = False
|
||||
, mailSendResult = Nothing
|
||||
}
|
||||
, Cmd.none
|
||||
, Cmd.map ItemMailMsg ic
|
||||
)
|
||||
|
||||
Comp.ItemMail.FormSend sm ->
|
||||
@ -739,7 +739,12 @@ update key flags next msg model =
|
||||
, conn = sm.conn
|
||||
}
|
||||
in
|
||||
( model, Api.sendMail flags mail SendMailResp )
|
||||
( model
|
||||
, Cmd.batch
|
||||
[ Cmd.map ItemMailMsg ic
|
||||
, Api.sendMail flags mail SendMailResp
|
||||
]
|
||||
)
|
||||
|
||||
ToggleMail ->
|
||||
( { model | mailOpen = not model.mailOpen }, Cmd.none )
|
||||
|
@ -13,6 +13,7 @@ import Api
|
||||
import Api.Model.EmailSettingsList exposing (EmailSettingsList)
|
||||
import Api.Model.SimpleMail exposing (SimpleMail)
|
||||
import Comp.Dropdown
|
||||
import Comp.EmailInput
|
||||
import Data.Flags exposing (Flags)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
@ -24,7 +25,8 @@ import Util.Http
|
||||
type alias Model =
|
||||
{ connectionModel : Comp.Dropdown.Model String
|
||||
, subject : String
|
||||
, receiver : String
|
||||
, recipients : List String
|
||||
, recipientsModel : Comp.EmailInput.Model
|
||||
, body : String
|
||||
, attachAll : Bool
|
||||
, formError : Maybe String
|
||||
@ -33,7 +35,7 @@ type alias Model =
|
||||
|
||||
type Msg
|
||||
= SetSubject String
|
||||
| SetReceiver String
|
||||
| RecipientMsg Comp.EmailInput.Msg
|
||||
| SetBody String
|
||||
| ConnMsg (Comp.Dropdown.Msg String)
|
||||
| ConnResp (Result Http.Error EmailSettingsList)
|
||||
@ -62,7 +64,8 @@ emptyModel =
|
||||
, placeholder = "Select connection..."
|
||||
}
|
||||
, subject = ""
|
||||
, receiver = ""
|
||||
, recipients = []
|
||||
, recipientsModel = Comp.EmailInput.init
|
||||
, body = ""
|
||||
, attachAll = True
|
||||
, formError = Nothing
|
||||
@ -78,22 +81,29 @@ clear : Model -> Model
|
||||
clear model =
|
||||
{ model
|
||||
| subject = ""
|
||||
, receiver = ""
|
||||
, recipients = []
|
||||
, body = ""
|
||||
}
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, FormAction )
|
||||
update msg model =
|
||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, FormAction )
|
||||
update flags msg model =
|
||||
case msg of
|
||||
SetSubject str ->
|
||||
( { model | subject = str }, FormNone )
|
||||
( { model | subject = str }, Cmd.none, FormNone )
|
||||
|
||||
SetReceiver str ->
|
||||
( { model | receiver = str }, FormNone )
|
||||
RecipientMsg m ->
|
||||
let
|
||||
( em, ec, rec ) =
|
||||
Comp.EmailInput.update flags model.recipients m model.recipientsModel
|
||||
in
|
||||
( { model | recipients = rec, recipientsModel = em }
|
||||
, Cmd.map RecipientMsg ec
|
||||
, FormNone
|
||||
)
|
||||
|
||||
SetBody str ->
|
||||
( { model | body = str }, FormNone )
|
||||
( { model | body = str }, Cmd.none, FormNone )
|
||||
|
||||
ConnMsg m ->
|
||||
let
|
||||
@ -101,10 +111,10 @@ update msg model =
|
||||
--TODO dropdown doesn't use cmd!!
|
||||
Comp.Dropdown.update m model.connectionModel
|
||||
in
|
||||
( { model | connectionModel = cm }, FormNone )
|
||||
( { model | connectionModel = cm }, Cmd.none, FormNone )
|
||||
|
||||
ToggleAttachAll ->
|
||||
( { model | attachAll = not model.attachAll }, FormNone )
|
||||
( { model | attachAll = not model.attachAll }, Cmd.none, FormNone )
|
||||
|
||||
ConnResp (Ok list) ->
|
||||
let
|
||||
@ -128,35 +138,33 @@ update msg model =
|
||||
else
|
||||
Nothing
|
||||
}
|
||||
, Cmd.none
|
||||
, FormNone
|
||||
)
|
||||
|
||||
ConnResp (Err err) ->
|
||||
( { model | formError = Just (Util.Http.errorToString err) }, FormNone )
|
||||
( { model | formError = Just (Util.Http.errorToString err) }, Cmd.none, FormNone )
|
||||
|
||||
Cancel ->
|
||||
( model, FormCancel )
|
||||
( model, Cmd.none, FormCancel )
|
||||
|
||||
Send ->
|
||||
case ( model.formError, Comp.Dropdown.getSelected model.connectionModel ) of
|
||||
( Nothing, conn :: [] ) ->
|
||||
let
|
||||
rec =
|
||||
String.split "," model.receiver
|
||||
|
||||
sm =
|
||||
SimpleMail rec model.subject model.body model.attachAll []
|
||||
SimpleMail model.recipients model.subject model.body model.attachAll []
|
||||
in
|
||||
( model, FormSend { conn = conn, mail = sm } )
|
||||
( model, Cmd.none, FormSend { conn = conn, mail = sm } )
|
||||
|
||||
_ ->
|
||||
( model, FormNone )
|
||||
( model, Cmd.none, FormNone )
|
||||
|
||||
|
||||
isValid : Model -> Bool
|
||||
isValid model =
|
||||
model.receiver
|
||||
/= ""
|
||||
model.recipients
|
||||
/= []
|
||||
&& model.subject
|
||||
/= ""
|
||||
&& model.body
|
||||
@ -182,16 +190,9 @@ view model =
|
||||
]
|
||||
, div [ class "field" ]
|
||||
[ label []
|
||||
[ text "Receiver(s)"
|
||||
, span [ class "muted" ]
|
||||
[ text "Separate multiple recipients by comma" ]
|
||||
[ text "Recipient(s)"
|
||||
]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, onInput SetReceiver
|
||||
, value model.receiver
|
||||
]
|
||||
[]
|
||||
, Html.map RecipientMsg (Comp.EmailInput.view model.recipients model.recipientsModel)
|
||||
]
|
||||
, div [ class "field" ]
|
||||
[ label [] [ text "Subject" ]
|
||||
|
@ -18,6 +18,7 @@ type KeyCode
|
||||
| Left
|
||||
| Right
|
||||
| Enter
|
||||
| Space
|
||||
|
||||
|
||||
intToKeyCode : Int -> Maybe KeyCode
|
||||
@ -38,6 +39,9 @@ intToKeyCode code =
|
||||
13 ->
|
||||
Just Enter
|
||||
|
||||
32 ->
|
||||
Just Space
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
|
@ -29,8 +29,12 @@
|
||||
}
|
||||
|
||||
.default-layout .ui.multiple.search.dropdown>input.search {
|
||||
width: 3.5em;
|
||||
width: auto;
|
||||
}
|
||||
/* .default-layout .ui.multiple.search.dropdown>input.search.long-search { */
|
||||
/* width: auto; */
|
||||
/* } */
|
||||
|
||||
|
||||
.default-layout .job-log {
|
||||
background: #181819;
|
||||
@ -111,6 +115,10 @@ span.small-info {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.ui.form textarea.search {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.login-layout, .register-layout, .newinvite-layout {
|
||||
background: #708090;
|
||||
height: 101vh;
|
||||
|
Reference in New Issue
Block a user