mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-19 02:27:03 +00:00
Merge branch 'feature/keybd-nav' into develop
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
module Layout exposing (Page(..), frame)
|
module Layout exposing (Model, Msg, Page(..), frame, init, reset, update)
|
||||||
|
|
||||||
import Data.Session as Session exposing (Session)
|
import Data.Session as Session exposing (Session)
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
@@ -29,56 +29,124 @@ type Page
|
|||||||
| Status
|
| Status
|
||||||
|
|
||||||
|
|
||||||
type alias FrameControls msg =
|
type alias Model msg =
|
||||||
{ viewMailbox : String -> msg
|
{ mapMsg : Msg -> msg
|
||||||
, mailboxOnInput : String -> msg
|
, menuVisible : Bool
|
||||||
, mailboxValue : String
|
, recentVisible : Bool
|
||||||
, recentOptions : List String
|
, mailboxName : String
|
||||||
, recentActive : String
|
|
||||||
, clearFlash : msg
|
|
||||||
, showMenu : Bool
|
|
||||||
, toggleMenu : msg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
frame : FrameControls msg -> Session -> Page -> Maybe (Html msg) -> List (Html msg) -> Html msg
|
init : (Msg -> msg) -> Model msg
|
||||||
frame controls session activePage modal content =
|
init mapMsg =
|
||||||
|
{ mapMsg = mapMsg
|
||||||
|
, menuVisible = False
|
||||||
|
, recentVisible = False
|
||||||
|
, mailboxName = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{-| Resets layout state, used when navigating to a new page.
|
||||||
|
-}
|
||||||
|
reset : Model msg -> Model msg
|
||||||
|
reset model =
|
||||||
|
{ model
|
||||||
|
| menuVisible = False
|
||||||
|
, recentVisible = False
|
||||||
|
, mailboxName = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= ClearFlash
|
||||||
|
| OnMailboxNameInput String
|
||||||
|
| OpenMailbox
|
||||||
|
| ShowRecent Bool
|
||||||
|
| ToggleMenu
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model msg -> Session -> ( Model msg, Session, Cmd msg )
|
||||||
|
update msg model session =
|
||||||
|
case msg of
|
||||||
|
ClearFlash ->
|
||||||
|
( model
|
||||||
|
, Session.clearFlash session
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
OnMailboxNameInput name ->
|
||||||
|
( { model | mailboxName = name }
|
||||||
|
, session
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
OpenMailbox ->
|
||||||
|
( model
|
||||||
|
, session
|
||||||
|
, Route.pushUrl session.key (Route.Mailbox model.mailboxName)
|
||||||
|
)
|
||||||
|
|
||||||
|
ShowRecent visible ->
|
||||||
|
( { model | recentVisible = visible }
|
||||||
|
, session
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
ToggleMenu ->
|
||||||
|
( { model | menuVisible = not model.menuVisible }
|
||||||
|
, session
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type alias State msg =
|
||||||
|
{ model : Model msg
|
||||||
|
, session : Session
|
||||||
|
, activePage : Page
|
||||||
|
, activeMailbox : String
|
||||||
|
, modal : Maybe (Html msg)
|
||||||
|
, content : List (Html msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
frame : State msg -> Html msg
|
||||||
|
frame { model, session, activePage, activeMailbox, modal, content } =
|
||||||
div [ class "app" ]
|
div [ class "app" ]
|
||||||
[ header []
|
[ header []
|
||||||
[ nav [ class "navbar" ]
|
[ nav [ class "navbar" ]
|
||||||
[ span [ class "navbar-toggle", Events.onClick controls.toggleMenu ]
|
[ button [ class "navbar-toggle", Events.onClick (ToggleMenu |> model.mapMsg) ]
|
||||||
[ i [ class "fas fa-bars" ] [] ]
|
[ i [ class "fas fa-bars" ] [] ]
|
||||||
, span [ class "navbar-brand" ]
|
, span [ class "navbar-brand" ]
|
||||||
[ a [ Route.href Route.Home ] [ text "@ inbucket" ] ]
|
[ a [ Route.href Route.Home ] [ text "@ inbucket" ] ]
|
||||||
, ul [ classList [ ( "main-nav", True ), ( "active", controls.showMenu ) ] ]
|
, ul [ class "main-nav", classList [ ( "active", model.menuVisible ) ] ]
|
||||||
[ li [ class "navbar-mailbox" ]
|
[ if session.config.monitorVisible then
|
||||||
[ form [ Events.onSubmit (controls.viewMailbox controls.mailboxValue) ]
|
|
||||||
[ input
|
|
||||||
[ type_ "text"
|
|
||||||
, placeholder "mailbox"
|
|
||||||
, value controls.mailboxValue
|
|
||||||
, Events.onInput controls.mailboxOnInput
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, if session.config.monitorVisible then
|
|
||||||
navbarLink Monitor Route.Monitor [ text "Monitor" ] activePage
|
navbarLink Monitor Route.Monitor [ text "Monitor" ] activePage
|
||||||
|
|
||||||
else
|
else
|
||||||
text ""
|
text ""
|
||||||
, navbarLink Status Route.Status [ text "Status" ] activePage
|
, navbarLink Status Route.Status [ text "Status" ] activePage
|
||||||
, navbarRecent activePage controls
|
, navbarRecent activePage activeMailbox model session
|
||||||
|
, li [ class "navbar-mailbox" ]
|
||||||
|
[ form [ Events.onSubmit (OpenMailbox |> model.mapMsg) ]
|
||||||
|
[ input
|
||||||
|
[ type_ "text"
|
||||||
|
, placeholder "mailbox"
|
||||||
|
, value model.mailboxName
|
||||||
|
, Events.onInput (OnMailboxNameInput >> model.mapMsg)
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
, div [ class "navbar-bg" ] [ text "" ]
|
, div [ class "navbar-bg" ] [ text "" ]
|
||||||
, frameModal modal
|
, frameModal modal
|
||||||
, div [ class "page" ] ([ errorFlash controls session.flash ] ++ content)
|
, div [ class "page" ] ([ errorFlash model session.flash ] ++ content)
|
||||||
, footer []
|
, footer []
|
||||||
[ div [ class "footer" ]
|
[ div [ class "footer" ]
|
||||||
[ externalLink "https://www.inbucket.org" "Inbucket"
|
[ externalLink "https://www.inbucket.org" "Inbucket"
|
||||||
, text " is an open source projected hosted at "
|
, text " is an open source project hosted on "
|
||||||
, externalLink "https://github.com/jhillyerd/inbucket" "GitHub"
|
, externalLink "https://github.com/jhillyerd/inbucket" "GitHub"
|
||||||
, text "."
|
, text "."
|
||||||
]
|
]
|
||||||
@@ -98,8 +166,8 @@ frameModal maybeModal =
|
|||||||
text ""
|
text ""
|
||||||
|
|
||||||
|
|
||||||
errorFlash : FrameControls msg -> Maybe Session.Flash -> Html msg
|
errorFlash : Model msg -> Maybe Session.Flash -> Html msg
|
||||||
errorFlash controls maybeFlash =
|
errorFlash model maybeFlash =
|
||||||
let
|
let
|
||||||
row ( heading, message ) =
|
row ( heading, message ) =
|
||||||
tr []
|
tr []
|
||||||
@@ -115,7 +183,7 @@ errorFlash controls maybeFlash =
|
|||||||
div [ class "well well-error" ]
|
div [ class "well well-error" ]
|
||||||
[ div [ class "flash-header" ]
|
[ div [ class "flash-header" ]
|
||||||
[ h2 [] [ text flash.title ]
|
[ h2 [] [ text flash.title ]
|
||||||
, a [ href "#", Events.onClick controls.clearFlash ] [ text "Close" ]
|
, a [ href "#", Events.onClick (ClearFlash |> model.mapMsg) ] [ text "Close" ]
|
||||||
]
|
]
|
||||||
, div [ class "flash-table" ] (List.map row flash.table)
|
, div [ class "flash-table" ] (List.map row flash.table)
|
||||||
]
|
]
|
||||||
@@ -129,21 +197,22 @@ externalLink url title =
|
|||||||
navbarLink : Page -> Route -> List (Html a) -> Page -> Html a
|
navbarLink : Page -> Route -> List (Html a) -> Page -> Html a
|
||||||
navbarLink page route linkContent activePage =
|
navbarLink page route linkContent activePage =
|
||||||
li [ classList [ ( "navbar-active", page == activePage ) ] ]
|
li [ classList [ ( "navbar-active", page == activePage ) ] ]
|
||||||
[ a [ class "navbar-active-bg", Route.href route ] linkContent ]
|
[ a [ Route.href route ] linkContent ]
|
||||||
|
|
||||||
|
|
||||||
{-| Renders list of recent mailboxes, selecting the currently active mailbox.
|
{-| Renders list of recent mailboxes, selecting the currently active mailbox.
|
||||||
-}
|
-}
|
||||||
navbarRecent : Page -> FrameControls msg -> Html msg
|
navbarRecent : Page -> String -> Model msg -> Session -> Html msg
|
||||||
navbarRecent page controls =
|
navbarRecent page activeMailbox model session =
|
||||||
let
|
let
|
||||||
|
-- Active means we are viewing a specific mailbox.
|
||||||
active =
|
active =
|
||||||
page == Mailbox
|
page == Mailbox
|
||||||
|
|
||||||
-- Recent tab title is the name of the current mailbox when active.
|
-- Recent tab title is the name of the current mailbox when active.
|
||||||
title =
|
title =
|
||||||
if active then
|
if active then
|
||||||
controls.recentActive
|
activeMailbox
|
||||||
|
|
||||||
else
|
else
|
||||||
"Recent Mailboxes"
|
"Recent Mailboxes"
|
||||||
@@ -151,18 +220,46 @@ navbarRecent page controls =
|
|||||||
-- Mailboxes to show in recent list, doesn't include active mailbox.
|
-- Mailboxes to show in recent list, doesn't include active mailbox.
|
||||||
recentMailboxes =
|
recentMailboxes =
|
||||||
if active then
|
if active then
|
||||||
List.tail controls.recentOptions |> Maybe.withDefault []
|
List.tail session.persistent.recentMailboxes |> Maybe.withDefault []
|
||||||
|
|
||||||
else
|
else
|
||||||
controls.recentOptions
|
session.persistent.recentMailboxes
|
||||||
|
|
||||||
|
dropdownExpanded =
|
||||||
|
if model.recentVisible then
|
||||||
|
"true"
|
||||||
|
|
||||||
|
else
|
||||||
|
"false"
|
||||||
|
|
||||||
recentLink mailbox =
|
recentLink mailbox =
|
||||||
a [ Route.href (Route.Mailbox mailbox) ] [ text mailbox ]
|
a [ Route.href (Route.Mailbox mailbox) ] [ text mailbox ]
|
||||||
in
|
in
|
||||||
li
|
li
|
||||||
[ class "navbar-recent"
|
[ class "navbar-dropdown-container"
|
||||||
, classList [ ( "navbar-dropdown", True ), ( "navbar-active", active ) ]
|
, classList [ ( "navbar-active", active ) ]
|
||||||
|
, attribute "aria-haspopup" "true"
|
||||||
|
, ariaExpanded model.recentVisible
|
||||||
|
, Events.onMouseOver (ShowRecent True |> model.mapMsg)
|
||||||
|
, Events.onMouseOut (ShowRecent False |> model.mapMsg)
|
||||||
]
|
]
|
||||||
[ span [ class "navbar-active-bg" ] [ text title ]
|
[ span [ class "navbar-dropdown" ]
|
||||||
|
[ text title
|
||||||
|
, button
|
||||||
|
[ class "navbar-dropdown-button"
|
||||||
|
, Events.onClick (ShowRecent (not model.recentVisible) |> model.mapMsg)
|
||||||
|
]
|
||||||
|
[ i [ class "fas fa-chevron-down" ] [] ]
|
||||||
|
]
|
||||||
, div [ class "navbar-dropdown-content" ] (List.map recentLink recentMailboxes)
|
, div [ class "navbar-dropdown-content" ] (List.map recentLink recentMailboxes)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
ariaExpanded : Bool -> Attribute msg
|
||||||
|
ariaExpanded value =
|
||||||
|
attribute "aria-expanded" <|
|
||||||
|
if value then
|
||||||
|
"true"
|
||||||
|
|
||||||
|
else
|
||||||
|
"false"
|
||||||
|
|||||||
@@ -23,9 +23,8 @@ import Url exposing (Url)
|
|||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ page : PageModel
|
{ layout : Layout.Model Msg
|
||||||
, mailboxName : String
|
, page : PageModel
|
||||||
, showMenu : Bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -62,9 +61,8 @@ init configValue location key =
|
|||||||
Home.init session
|
Home.init session
|
||||||
|
|
||||||
initModel =
|
initModel =
|
||||||
{ page = Home subModel
|
{ layout = Layout.init LayoutMsg
|
||||||
, mailboxName = ""
|
, page = Home subModel
|
||||||
, showMenu = False
|
|
||||||
}
|
}
|
||||||
|
|
||||||
route =
|
route =
|
||||||
@@ -81,10 +79,7 @@ type Msg
|
|||||||
| LinkClicked UrlRequest
|
| LinkClicked UrlRequest
|
||||||
| SessionUpdated (Result D.Error Session.Persistent)
|
| SessionUpdated (Result D.Error Session.Persistent)
|
||||||
| TimeZoneLoaded Time.Zone
|
| TimeZoneLoaded Time.Zone
|
||||||
| ClearFlash
|
| LayoutMsg Layout.Msg
|
||||||
| OnMailboxNameInput String
|
|
||||||
| ViewMailbox String
|
|
||||||
| ToggleMenu
|
|
||||||
| HomeMsg Home.Msg
|
| HomeMsg Home.Msg
|
||||||
| MailboxMsg Mailbox.Msg
|
| MailboxMsg Mailbox.Msg
|
||||||
| MonitorMsg Monitor.Msg
|
| MonitorMsg Monitor.Msg
|
||||||
@@ -182,11 +177,6 @@ updateMain msg model session =
|
|||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
|
|
||||||
ClearFlash ->
|
|
||||||
( applyToModelSession Session.clearFlash model
|
|
||||||
, Cmd.none
|
|
||||||
)
|
|
||||||
|
|
||||||
SessionUpdated (Ok persistent) ->
|
SessionUpdated (Ok persistent) ->
|
||||||
( updateSession model { session | persistent = persistent }
|
( updateSession model { session | persistent = persistent }
|
||||||
, Cmd.none
|
, Cmd.none
|
||||||
@@ -208,17 +198,15 @@ updateMain msg model session =
|
|||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
|
|
||||||
OnMailboxNameInput name ->
|
LayoutMsg subMsg ->
|
||||||
( { model | mailboxName = name }, Cmd.none )
|
let
|
||||||
|
( layout, newSession, cmd ) =
|
||||||
ViewMailbox name ->
|
Layout.update subMsg model.layout session
|
||||||
( applyToModelSession Session.clearFlash { model | mailboxName = "" }
|
in
|
||||||
, Route.pushUrl session.key (Route.Mailbox name)
|
( updateSession { model | layout = layout } newSession
|
||||||
|
, cmd
|
||||||
)
|
)
|
||||||
|
|
||||||
ToggleMenu ->
|
|
||||||
( { model | showMenu = not model.showMenu }, Cmd.none )
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
updatePage msg model
|
updatePage msg model
|
||||||
|
|
||||||
@@ -256,7 +244,7 @@ changeRouteTo route model =
|
|||||||
getSession model
|
getSession model
|
||||||
|
|
||||||
newModel =
|
newModel =
|
||||||
{ model | showMenu = False }
|
{ model | layout = Layout.reset model.layout }
|
||||||
in
|
in
|
||||||
case route of
|
case route of
|
||||||
Route.Unknown path ->
|
Route.Unknown path ->
|
||||||
@@ -372,17 +360,6 @@ view model =
|
|||||||
_ ->
|
_ ->
|
||||||
""
|
""
|
||||||
|
|
||||||
controls =
|
|
||||||
{ viewMailbox = ViewMailbox
|
|
||||||
, mailboxOnInput = OnMailboxNameInput
|
|
||||||
, mailboxValue = model.mailboxName
|
|
||||||
, recentOptions = session.persistent.recentMailboxes
|
|
||||||
, recentActive = mailbox
|
|
||||||
, clearFlash = ClearFlash
|
|
||||||
, showMenu = model.showMenu
|
|
||||||
, toggleMenu = ToggleMenu
|
|
||||||
}
|
|
||||||
|
|
||||||
framePage :
|
framePage :
|
||||||
Layout.Page
|
Layout.Page
|
||||||
-> (msg -> Msg)
|
-> (msg -> Msg)
|
||||||
@@ -391,11 +368,13 @@ view model =
|
|||||||
framePage page toMsg { title, modal, content } =
|
framePage page toMsg { title, modal, content } =
|
||||||
Document title
|
Document title
|
||||||
[ Layout.frame
|
[ Layout.frame
|
||||||
controls
|
{ model = model.layout
|
||||||
session
|
, session = session
|
||||||
page
|
, activePage = page
|
||||||
(Maybe.map (Html.map toMsg) modal)
|
, activeMailbox = mailbox
|
||||||
(List.map (Html.map toMsg) content)
|
, modal = Maybe.map (Html.map toMsg) modal
|
||||||
|
, content = List.map (Html.map toMsg) content
|
||||||
|
}
|
||||||
]
|
]
|
||||||
in
|
in
|
||||||
case model.page of
|
case model.page of
|
||||||
|
|||||||
@@ -17,15 +17,16 @@ import Html.Attributes
|
|||||||
, id
|
, id
|
||||||
, placeholder
|
, placeholder
|
||||||
, property
|
, property
|
||||||
|
, tabindex
|
||||||
, target
|
, target
|
||||||
, type_
|
, type_
|
||||||
, value
|
, value
|
||||||
)
|
)
|
||||||
import Html.Events exposing (..)
|
import Html.Events as Events
|
||||||
import Http exposing (Error)
|
import Http exposing (Error)
|
||||||
import HttpUtil
|
import HttpUtil
|
||||||
import Json.Decode as Decode exposing (Decoder)
|
import Json.Decode as D
|
||||||
import Json.Encode as Encode
|
import Json.Encode as E
|
||||||
import Ports
|
import Ports
|
||||||
import Route
|
import Route
|
||||||
import Task
|
import Task
|
||||||
@@ -135,6 +136,7 @@ subscriptions model =
|
|||||||
type Msg
|
type Msg
|
||||||
= ListLoaded (Result HttpUtil.Error (List MessageHeader))
|
= ListLoaded (Result HttpUtil.Error (List MessageHeader))
|
||||||
| ClickMessage MessageID
|
| ClickMessage MessageID
|
||||||
|
| ListKeyPress String Int
|
||||||
| OpenMessage MessageID
|
| OpenMessage MessageID
|
||||||
| CloseMessage
|
| CloseMessage
|
||||||
| MessageLoaded (Result HttpUtil.Error Message)
|
| MessageLoaded (Result HttpUtil.Error Message)
|
||||||
@@ -165,7 +167,7 @@ update msg model =
|
|||||||
)
|
)
|
||||||
|
|
||||||
OpenMessage id ->
|
OpenMessage id ->
|
||||||
updateOpenMessage model.session model id
|
updateOpenMessage model id
|
||||||
|
|
||||||
CloseMessage ->
|
CloseMessage ->
|
||||||
case model.state of
|
case model.state of
|
||||||
@@ -186,6 +188,14 @@ update msg model =
|
|||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ListKeyPress id keyCode ->
|
||||||
|
case keyCode of
|
||||||
|
13 ->
|
||||||
|
updateOpenMessage model id
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
( model, Cmd.none )
|
||||||
|
|
||||||
ListLoaded (Ok headers) ->
|
ListLoaded (Ok headers) ->
|
||||||
case model.state of
|
case model.state of
|
||||||
LoadingList selection ->
|
LoadingList selection ->
|
||||||
@@ -197,7 +207,7 @@ update msg model =
|
|||||||
in
|
in
|
||||||
case selection of
|
case selection of
|
||||||
Just id ->
|
Just id ->
|
||||||
updateOpenMessage model.session newModel id
|
updateOpenMessage newModel id
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
( { newModel
|
( { newModel
|
||||||
@@ -458,8 +468,8 @@ updateMarkMessageSeen model message =
|
|||||||
( model, Cmd.none )
|
( model, Cmd.none )
|
||||||
|
|
||||||
|
|
||||||
updateOpenMessage : Session -> Model -> String -> ( Model, Cmd Msg )
|
updateOpenMessage : Model -> String -> ( Model, Cmd Msg )
|
||||||
updateOpenMessage session model id =
|
updateOpenMessage model id =
|
||||||
let
|
let
|
||||||
newModel =
|
newModel =
|
||||||
{ model | session = Session.addRecent model.mailboxName model.session }
|
{ model | session = Session.addRecent model.mailboxName model.session }
|
||||||
@@ -492,12 +502,12 @@ view model =
|
|||||||
[ input
|
[ input
|
||||||
[ type_ "search"
|
[ type_ "search"
|
||||||
, placeholder "search"
|
, placeholder "search"
|
||||||
, onInput OnSearchInput
|
, Events.onInput OnSearchInput
|
||||||
, value model.searchInput
|
, value model.searchInput
|
||||||
]
|
]
|
||||||
[]
|
[]
|
||||||
, button
|
, button
|
||||||
[ onClick PurgeMailboxPrompt
|
[ Events.onClick PurgeMailboxPrompt
|
||||||
, alt "Purge Mailbox"
|
, alt "Purge Mailbox"
|
||||||
]
|
]
|
||||||
[ i [ class "fas fa-trash" ] [] ]
|
[ i [ class "fas fa-trash" ] [] ]
|
||||||
@@ -533,8 +543,8 @@ viewModal promptPurge =
|
|||||||
div []
|
div []
|
||||||
[ p [] [ text "Are you sure you want to delete all messages in this mailbox?" ]
|
[ p [] [ text "Are you sure you want to delete all messages in this mailbox?" ]
|
||||||
, div [ class "button-bar" ]
|
, div [ class "button-bar" ]
|
||||||
[ button [ onClick PurgeMailboxConfirmed, class "danger" ] [ text "Yes" ]
|
[ button [ Events.onClick PurgeMailboxConfirmed, class "danger" ] [ text "Yes" ]
|
||||||
, button [ onClick PurgeMailboxCanceled ] [ text "Cancel" ]
|
, button [ Events.onClick PurgeMailboxCanceled ] [ text "Cancel" ]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -559,12 +569,14 @@ viewMessageList session model =
|
|||||||
messageChip : Model -> Maybe MessageID -> MessageHeader -> Html Msg
|
messageChip : Model -> Maybe MessageID -> MessageHeader -> Html Msg
|
||||||
messageChip model selected message =
|
messageChip model selected message =
|
||||||
div
|
div
|
||||||
[ classList
|
[ class "message-list-entry"
|
||||||
[ ( "message-list-entry", True )
|
, classList
|
||||||
, ( "selected", selected == Just message.id )
|
[ ( "selected", selected == Just message.id )
|
||||||
, ( "unseen", not message.seen )
|
, ( "unseen", not message.seen )
|
||||||
]
|
]
|
||||||
, onClick (ClickMessage message.id)
|
, Events.onClick (ClickMessage message.id)
|
||||||
|
, onKeyUp (ListKeyPress message.id)
|
||||||
|
, tabindex 0
|
||||||
]
|
]
|
||||||
[ div [ class "subject" ] [ text message.subject ]
|
[ div [ class "subject" ] [ text message.subject ]
|
||||||
, div [ class "from" ] [ text message.from ]
|
, div [ class "from" ] [ text message.from ]
|
||||||
@@ -586,18 +598,16 @@ viewMessage zone message bodyMode =
|
|||||||
text ""
|
text ""
|
||||||
|
|
||||||
else
|
else
|
||||||
a
|
a [ href htmlUrl, target "_blank" ]
|
||||||
[ href htmlUrl, target "_blank" ]
|
[ button [ tabindex -1 ] [ text "Raw HTML" ] ]
|
||||||
[ button [] [ text "Raw HTML" ] ]
|
|
||||||
in
|
in
|
||||||
div []
|
div []
|
||||||
[ div [ class "button-bar" ]
|
[ div [ class "button-bar" ]
|
||||||
[ button [ class "message-close light", onClick CloseMessage ]
|
[ button [ class "message-close light", Events.onClick CloseMessage ]
|
||||||
[ i [ class "fas fa-arrow-left" ] [] ]
|
[ i [ class "fas fa-arrow-left" ] [] ]
|
||||||
, button [ class "danger", onClick (DeleteMessage message) ] [ text "Delete" ]
|
, button [ class "danger", Events.onClick (DeleteMessage message) ] [ text "Delete" ]
|
||||||
, a
|
, a [ href sourceUrl, target "_blank" ]
|
||||||
[ href sourceUrl, target "_blank" ]
|
[ button [ tabindex -1 ] [ text "Source" ] ]
|
||||||
[ button [] [ text "Source" ] ]
|
|
||||||
, htmlButton
|
, htmlButton
|
||||||
]
|
]
|
||||||
, dl [ class "message-header" ]
|
, dl [ class "message-header" ]
|
||||||
@@ -644,7 +654,7 @@ messageBody message bodyMode =
|
|||||||
bodyModeTab mode label =
|
bodyModeTab mode label =
|
||||||
a
|
a
|
||||||
[ classList [ ( "active", bodyMode == mode ) ]
|
[ classList [ ( "active", bodyMode == mode ) ]
|
||||||
, onClick (MessageBody mode)
|
, Events.onClick (MessageBody mode)
|
||||||
, href "#"
|
, href "#"
|
||||||
]
|
]
|
||||||
[ text label ]
|
[ text label ]
|
||||||
@@ -667,10 +677,10 @@ messageBody message bodyMode =
|
|||||||
, article [ class "message-body" ]
|
, article [ class "message-body" ]
|
||||||
[ case bodyMode of
|
[ case bodyMode of
|
||||||
SafeHtmlBody ->
|
SafeHtmlBody ->
|
||||||
Html.node "rendered-html" [ property "content" (Encode.string message.html) ] []
|
Html.node "rendered-html" [ property "content" (E.string message.html) ] []
|
||||||
|
|
||||||
TextBody ->
|
TextBody ->
|
||||||
Html.node "rendered-html" [ property "content" (Encode.string message.text) ] []
|
Html.node "rendered-html" [ property "content" (E.string message.text) ] []
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -750,3 +760,8 @@ filterMessageList list =
|
|||||||
|| String.contains list.searchFilter (String.toLower header.from)
|
|| String.contains list.searchFilter (String.toLower header.from)
|
||||||
in
|
in
|
||||||
List.filter matches list.headers
|
List.filter matches list.headers
|
||||||
|
|
||||||
|
|
||||||
|
onKeyUp : (Int -> msg) -> Attribute msg
|
||||||
|
onKeyUp tagger =
|
||||||
|
Events.on "keyup" (D.map tagger Events.keyCode)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ type Msg
|
|||||||
| MessageReceived D.Value
|
| MessageReceived D.Value
|
||||||
| Clear
|
| Clear
|
||||||
| OpenMessage MessageHeader
|
| OpenMessage MessageHeader
|
||||||
|
| MessageKeyPress MessageHeader Int
|
||||||
|
|
||||||
|
|
||||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||||
@@ -69,9 +70,22 @@ update msg model =
|
|||||||
( { model | messages = [] }, Cmd.none )
|
( { model | messages = [] }, Cmd.none )
|
||||||
|
|
||||||
OpenMessage header ->
|
OpenMessage header ->
|
||||||
( model
|
openMessage header model
|
||||||
, Route.pushUrl model.session.key (Route.Message header.mailbox header.id)
|
|
||||||
)
|
MessageKeyPress header keyCode ->
|
||||||
|
case keyCode of
|
||||||
|
13 ->
|
||||||
|
openMessage header model
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
( model, Cmd.none )
|
||||||
|
|
||||||
|
|
||||||
|
openMessage : MessageHeader -> Model -> ( Model, Cmd Msg )
|
||||||
|
openMessage header model =
|
||||||
|
( model
|
||||||
|
, Route.pushUrl model.session.key (Route.Message header.mailbox header.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -121,7 +135,11 @@ view model =
|
|||||||
|
|
||||||
viewMessage : Time.Zone -> MessageHeader -> Html Msg
|
viewMessage : Time.Zone -> MessageHeader -> Html Msg
|
||||||
viewMessage zone message =
|
viewMessage zone message =
|
||||||
tr [ Events.onClick (OpenMessage message) ]
|
tr
|
||||||
|
[ tabindex 0
|
||||||
|
, Events.onClick (OpenMessage message)
|
||||||
|
, onKeyUp (MessageKeyPress message)
|
||||||
|
]
|
||||||
[ td [] [ shortDate zone message.date ]
|
[ td [] [ shortDate zone message.date ]
|
||||||
, td [ class "desktop" ] [ text message.from ]
|
, td [ class "desktop" ] [ text message.from ]
|
||||||
, td [] [ text message.mailbox ]
|
, td [] [ text message.mailbox ]
|
||||||
@@ -147,3 +165,8 @@ shortDate zone date =
|
|||||||
zone
|
zone
|
||||||
date
|
date
|
||||||
|> text
|
|> text
|
||||||
|
|
||||||
|
|
||||||
|
onKeyUp : (Int -> msg) -> Attribute msg
|
||||||
|
onKeyUp tagger =
|
||||||
|
Events.on "keyup" (D.map tagger Events.keyCode)
|
||||||
|
|||||||
@@ -18,6 +18,18 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-list-controls button,
|
||||||
|
.message-list-controls input[type="search"] {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-list-controls button {
|
||||||
|
color: var(--low-color);
|
||||||
|
margin-left: 1px;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.message-list-controls input[type="search"] {
|
.message-list-controls input[type="search"] {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
|||||||
a, abbr, acronym, address, big, cite, code,
|
a, abbr, acronym, address, big, cite, code,
|
||||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||||
small, strike, strong, sub, sup, tt, var,
|
small, strike, strong, sub, sup, tt, var,
|
||||||
b, u, i, center,
|
b, u, i, center, button,
|
||||||
dl, dt, dd, ol, ul, li,
|
dl, dt, dd, ol, ul, li,
|
||||||
fieldset, form, label, legend,
|
fieldset, form, label, legend,
|
||||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||||
@@ -44,13 +44,17 @@ body {
|
|||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
body, input, table {
|
body, button, input, table {
|
||||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.43;
|
line-height: 1.43;
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6, p {
|
h1, h2, h3, h4, h5, h6, p {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,29 +30,35 @@
|
|||||||
.navbar-toggle {
|
.navbar-toggle {
|
||||||
color: var(--navbar-color);
|
color: var(--navbar-color);
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
padding: 0 6px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
right: 20px;
|
right: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar a,
|
.navbar a,
|
||||||
.navbar-dropdown span {
|
.navbar-dropdown {
|
||||||
color: var(--navbar-color);
|
color: var(--navbar-color);
|
||||||
display: block;
|
display: block;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This takes precendence over .navbar a above */
|
||||||
.navbar-brand a {
|
.navbar-brand a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 12px 15px;
|
padding: 12px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar-dropdown-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.navbar li {
|
.navbar li {
|
||||||
color: var(--navbar-color);
|
color: var(--navbar-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
li.navbar-active .navbar-active-bg {
|
li.navbar-active > *:first-child {
|
||||||
background-color: #080808;
|
background-color: #080808;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,8 +89,7 @@ li.navbar-active span,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1000px) {
|
@media screen and (min-width: 1000px) {
|
||||||
.main-nav,
|
.main-nav {
|
||||||
.navbar-bg {
|
|
||||||
height: var(--navbar-height);
|
height: var(--navbar-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,16 +115,17 @@ li.navbar-active span,
|
|||||||
background-image: var(--navbar-image);
|
background-image: var(--navbar-image);
|
||||||
grid-column: 1 / 4;
|
grid-column: 1 / 4;
|
||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
|
height: var(--navbar-height);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-brand {
|
.navbar-toggle {
|
||||||
margin-left: -15px;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-recent {
|
.navbar-brand {
|
||||||
margin: 0 auto;
|
margin-left: -15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-mailbox {
|
.navbar-mailbox {
|
||||||
@@ -131,6 +137,22 @@ li.navbar-active span,
|
|||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar-dropdown-container {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-dropdown {
|
||||||
|
padding: 15px 19px 15px 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-dropdown-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--navbar-color);
|
||||||
|
display: inline;
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
.navbar-dropdown-content {
|
.navbar-dropdown-content {
|
||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
@@ -143,7 +165,7 @@ li.navbar-active span,
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-dropdown:hover .navbar-dropdown-content {
|
.navbar-dropdown-container[aria-expanded="true"] .navbar-dropdown-content {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,8 +178,4 @@ li.navbar-active span,
|
|||||||
color: var(--primary-color) !important;
|
color: var(--primary-color) !important;
|
||||||
background-color: var(--selected-color);
|
background-color: var(--selected-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-toggle {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user