web: upgrade to yesod 0.8
This commit is contained in:
parent
883bc240c9
commit
024cfdb7b1
@ -1,4 +1,4 @@
|
|||||||
{-# LANGUAGE CPP, QuasiQuotes, TemplateHaskell, TypeFamilies, GeneralizedNewtypeDeriving #-}
|
{-# LANGUAGE CPP, QuasiQuotes, TemplateHaskell, TypeFamilies, GeneralizedNewtypeDeriving, OverloadedStrings #-}
|
||||||
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, MultiParamTypeClasses, FlexibleContexts #-}
|
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, MultiParamTypeClasses, FlexibleContexts #-}
|
||||||
{-|
|
{-|
|
||||||
The web app providing a richer interface to hledger's data.
|
The web app providing a richer interface to hledger's data.
|
||||||
@ -11,11 +11,14 @@ module Hledger.Web.App
|
|||||||
where
|
where
|
||||||
import Control.Applicative ((<$>), (<*>))
|
import Control.Applicative ((<$>), (<*>))
|
||||||
-- import Control.Failure
|
-- import Control.Failure
|
||||||
-- import qualified Data.ByteString.Lazy as L
|
import qualified Data.ByteString.Lazy as L
|
||||||
import Data.Either
|
import Data.Either
|
||||||
-- import System.Directory
|
-- import qualified Data.Text as T
|
||||||
|
import Data.Text(Text,pack,unpack)
|
||||||
|
import System.Directory
|
||||||
import System.FilePath ((</>), takeFileName)
|
import System.FilePath ((</>), takeFileName)
|
||||||
import System.IO.Storage (putValue, getValue)
|
import System.IO.Storage (putValue, getValue)
|
||||||
|
import Text.Jasmine (minifym)
|
||||||
import Text.ParserCombinators.Parsec hiding (string)
|
import Text.ParserCombinators.Parsec hiding (string)
|
||||||
|
|
||||||
-- import Database.Persist.GenericSql (ConnectionPool, SqlPersist, runMigration, migrate)
|
-- import Database.Persist.GenericSql (ConnectionPool, SqlPersist, runMigration, migrate)
|
||||||
@ -37,24 +40,24 @@ import Hledger.Cli.Version (version)
|
|||||||
import Hledger.Data hiding (insert, today)
|
import Hledger.Data hiding (insert, today)
|
||||||
import Hledger.Read (journalFromPathAndString)
|
import Hledger.Read (journalFromPathAndString)
|
||||||
import Hledger.Read.JournalReader (someamount)
|
import Hledger.Read.JournalReader (someamount)
|
||||||
import Hledger.Web.Settings (
|
import Hledger.Web.Settings
|
||||||
-- withConnectionPool
|
-- -- withConnectionPool
|
||||||
-- , runConnectionPool
|
-- -- , runConnectionPool
|
||||||
-- , staticroot
|
-- -- , staticroot
|
||||||
datadir
|
-- datadir
|
||||||
-- , hamletFile
|
-- -- , hamletFile
|
||||||
-- , cassiusFile
|
-- -- , cassiusFile
|
||||||
-- , juliusFile
|
-- -- , juliusFile
|
||||||
, hledgerorgurl
|
-- -- , hledgerorgurl
|
||||||
, manualurl
|
-- , manualurl
|
||||||
, style_css
|
-- -- , style_css
|
||||||
, hledger_js
|
-- -- , hledger_js
|
||||||
, jquery_js
|
-- -- , jquery_js
|
||||||
, jquery_url_js
|
-- -- , jquery_url_js
|
||||||
, dhtmlxcommon_js
|
-- -- , dhtmlxcommon_js
|
||||||
, dhtmlxcombo_js
|
-- -- , dhtmlxcombo_js
|
||||||
, robots_txt
|
-- , robots_txt
|
||||||
)
|
-- )
|
||||||
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
@ -80,12 +83,12 @@ import Hledger.Web.Settings (
|
|||||||
-- run-time data kept by the web app.
|
-- run-time data kept by the web app.
|
||||||
data App = App
|
data App = App
|
||||||
{ -- appConnPool :: Maybe ConnectionPool
|
{ -- appConnPool :: Maybe ConnectionPool
|
||||||
appRoot :: String
|
appRoot :: Text
|
||||||
,appDataDir :: FilePath
|
,appDataDir :: FilePath
|
||||||
,appOpts :: [Opt]
|
,appOpts :: [Opt]
|
||||||
,appArgs :: [String]
|
,appArgs :: [String]
|
||||||
|
,appStaticSettings :: Static
|
||||||
,appJournal :: Journal
|
,appJournal :: Journal
|
||||||
,appStatic :: Static
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-- parseRoutes (quasi-quoter from web-routes) defines a list of route patterns for the web app.
|
-- parseRoutes (quasi-quoter from web-routes) defines a list of route patterns for the web app.
|
||||||
@ -93,9 +96,9 @@ data App = App
|
|||||||
-- /auth AuthR Auth getAuth
|
-- /auth AuthR Auth getAuth
|
||||||
mkYesod "App" [$parseRoutes|
|
mkYesod "App" [$parseRoutes|
|
||||||
/ IndexR GET
|
/ IndexR GET
|
||||||
|
/static StaticR Static appStaticSettings
|
||||||
/favicon.ico FaviconR GET
|
/favicon.ico FaviconR GET
|
||||||
/robots.txt RobotsR GET
|
/robots.txt RobotsR GET
|
||||||
/static StaticR Static appStatic
|
|
||||||
/journalonly JournalOnlyR GET POST
|
/journalonly JournalOnlyR GET POST
|
||||||
/registeronly RegisterOnlyR GET
|
/registeronly RegisterOnlyR GET
|
||||||
/accounts AccountsOnlyR GET
|
/accounts AccountsOnlyR GET
|
||||||
@ -108,6 +111,24 @@ type Handler = GHandler App App
|
|||||||
|
|
||||||
instance Yesod App where
|
instance Yesod App where
|
||||||
approot = appRoot
|
approot = appRoot
|
||||||
|
-- This function creates static content files in the static folder
|
||||||
|
-- and names them based on a hash of their content. This allows
|
||||||
|
-- expiration dates to be set far in the future without worry of
|
||||||
|
-- users receiving stale content.
|
||||||
|
addStaticContent ext' _ content = do
|
||||||
|
let fn = base64md5 content ++ '.' : unpack ext'
|
||||||
|
let content' =
|
||||||
|
if ext' == "js"
|
||||||
|
then case minifym content of
|
||||||
|
Left _ -> content
|
||||||
|
Right y -> y
|
||||||
|
else content
|
||||||
|
let statictmp = Hledger.Web.Settings.datadir ++ "/tmp/"
|
||||||
|
liftIO $ createDirectoryIfMissing True statictmp
|
||||||
|
let fn' = statictmp ++ fn
|
||||||
|
exists <- liftIO $ doesFileExist fn'
|
||||||
|
unless exists $ liftIO $ L.writeFile fn' content'
|
||||||
|
return $ Just $ Right (StaticR $ StaticRoute ["tmp", pack fn] [], [])
|
||||||
|
|
||||||
-- defaultLayout widget = do
|
-- defaultLayout widget = do
|
||||||
-- mmsg <- getMessage
|
-- mmsg <- getMessage
|
||||||
@ -230,6 +251,7 @@ withApp app f = toWaiApp app >>= f
|
|||||||
-- handler utilities, common templates
|
-- handler utilities, common templates
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
nulltemplate :: Hamlet AppRoute
|
||||||
nulltemplate = [$hamlet||]
|
nulltemplate = [$hamlet||]
|
||||||
|
|
||||||
-- | A bundle of useful data passed to templates.
|
-- | A bundle of useful data passed to templates.
|
||||||
@ -243,6 +265,7 @@ data TemplateData = TD {
|
|||||||
,today :: Day -- ^ the current day
|
,today :: Day -- ^ the current day
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mktd :: TemplateData
|
||||||
mktd = TD {
|
mktd = TD {
|
||||||
here = IndexR
|
here = IndexR
|
||||||
,title = "hledger"
|
,title = "hledger"
|
||||||
@ -260,11 +283,11 @@ mktd = TD {
|
|||||||
-- message if any.
|
-- message if any.
|
||||||
getHandlerData :: Handler (String, String, [Opt], FilterSpec, Journal, Maybe Html, AppRoute)
|
getHandlerData :: Handler (String, String, [Opt], FilterSpec, Journal, Maybe Html, AppRoute)
|
||||||
getHandlerData = do
|
getHandlerData = do
|
||||||
Just here <- getCurrentRoute
|
Just here' <- getCurrentRoute
|
||||||
(a, p, opts, fspec) <- getReportParameters
|
(a, p, opts, fspec) <- getReportParameters
|
||||||
(j, err) <- getLatestJournal opts
|
(j, err) <- getLatestJournal opts
|
||||||
msg <- getMessage' err
|
msg <- getMessage' err
|
||||||
return (a, p, opts, fspec, j, msg, here)
|
return (a, p, opts, fspec, j, msg, here')
|
||||||
where
|
where
|
||||||
-- | Get current report parameters for this request.
|
-- | Get current report parameters for this request.
|
||||||
getReportParameters :: Handler (String, String, [Opt], FilterSpec)
|
getReportParameters :: Handler (String, String, [Opt], FilterSpec)
|
||||||
@ -273,10 +296,11 @@ getHandlerData = do
|
|||||||
t <- liftIO $ getCurrentLocalTime
|
t <- liftIO $ getCurrentLocalTime
|
||||||
a <- fromMaybe "" <$> lookupGetParam "a"
|
a <- fromMaybe "" <$> lookupGetParam "a"
|
||||||
p <- fromMaybe "" <$> lookupGetParam "p"
|
p <- fromMaybe "" <$> lookupGetParam "p"
|
||||||
let opts = appOpts app ++ [Period p]
|
let (a',p') = (unpack a, unpack p)
|
||||||
args = appArgs app ++ words' a
|
opts = appOpts app ++ [Period p']
|
||||||
|
args = appArgs app ++ words' a'
|
||||||
fspec = optsToFilterSpec opts args t
|
fspec = optsToFilterSpec opts args t
|
||||||
return (a, p, opts, fspec)
|
return (a', p', opts, fspec)
|
||||||
|
|
||||||
-- | Quote-sensitive words, ie don't split on spaces which are inside quotes.
|
-- | Quote-sensitive words, ie don't split on spaces which are inside quotes.
|
||||||
words' :: String -> [String]
|
words' :: String -> [String]
|
||||||
@ -297,52 +321,52 @@ getHandlerData = do
|
|||||||
else case jE of
|
else case jE of
|
||||||
Right j' -> do liftIO $ putValue "hledger" "journal" j'
|
Right j' -> do liftIO $ putValue "hledger" "journal" j'
|
||||||
return (j',Nothing)
|
return (j',Nothing)
|
||||||
Left e -> do setMessage $ string "error while reading" {- ++ ": " ++ e-}
|
Left e -> do setMessage $ "error while reading" {- ++ ": " ++ e-}
|
||||||
return (j, Just e)
|
return (j, Just e)
|
||||||
|
|
||||||
-- | Helper to work around a yesod feature (can't set and get a message in the same request.)
|
-- | Helper to work around a yesod feature (can't set and get a message in the same request.)
|
||||||
getMessage' :: Maybe String -> Handler (Maybe Html)
|
getMessage' :: Maybe String -> Handler (Maybe Html)
|
||||||
getMessage' newmsgstr = do
|
getMessage' newmsgstr = do
|
||||||
oldmsg <- getMessage
|
oldmsg <- getMessage
|
||||||
return $ maybe oldmsg (Just . string) newmsgstr
|
return $ maybe oldmsg (Just . toHtml) newmsgstr
|
||||||
|
|
||||||
-- | Wrap a template with the standard hledger web ui page layout.
|
-- | Wrap a template with the standard hledger web ui page layout.
|
||||||
pageLayout :: TemplateData -> Hamlet AppRoute -> Hamlet AppRoute
|
pageLayout :: TemplateData -> Hamlet AppRoute -> Hamlet AppRoute
|
||||||
pageLayout td@TD{title=basetitle, msg=msg, p=p, j=j, today=today} content = [$hamlet|
|
pageLayout td@TD{title=basetitle, msg=msg, p=p, j=j, today=today} content = [$hamlet|
|
||||||
!!!
|
!!!
|
||||||
%html
|
<html
|
||||||
%head
|
<head
|
||||||
%title $title'$
|
<title>#{title'}
|
||||||
%meta!http-equiv=Content-Type!content=$metacontent$
|
<meta http-equiv=Content-Type content=#{metacontent}
|
||||||
%script!type=text/javascript!src=@StaticR.jquery_js@
|
<script type=text/javascript src=@{StaticR jquery_js}
|
||||||
%script!type=text/javascript!src=@StaticR.jquery_url_js@
|
<script type=text/javascript src=@{StaticR jquery_url_js}
|
||||||
%script!type=text/javascript!src=@StaticR.dhtmlxcommon_js@
|
<script type=text/javascript src=@{StaticR dhtmlxcommon_js}
|
||||||
%script!type=text/javascript!src=@StaticR.dhtmlxcombo_js@
|
<script type=text/javascript src=@{StaticR dhtmlxcombo_js}
|
||||||
%script!type=text/javascript!src=@StaticR.hledger_js@
|
<script type=text/javascript src=@{StaticR hledger_js}
|
||||||
%link!rel=stylesheet!type=text/css!media=all!href=@StaticR.style_css@
|
<link rel=stylesheet type=text/css media=all href=@{StaticR style_css}
|
||||||
%body
|
<body
|
||||||
^navbar.td^
|
^{navbar td}
|
||||||
#messages $m$
|
<div#messages>#{m}
|
||||||
#content
|
<div#content
|
||||||
^content^
|
^{content}
|
||||||
|]
|
|]
|
||||||
where title' = basetitle ++ " - " ++ journaltitle
|
where title' = basetitle ++ " - " ++ journaltitle
|
||||||
(journaltitle, _) = journalTitleDesc j p today
|
(journaltitle, _) = journalTitleDesc j p today
|
||||||
metacontent = "text/html; charset=utf-8"
|
metacontent = "text/html; charset=utf-8" :: String
|
||||||
m = fromMaybe (string "") msg
|
m = fromMaybe "" msg
|
||||||
|
|
||||||
-- | Global toolbar/heading area.
|
-- | Global toolbar/heading area.
|
||||||
navbar :: TemplateData -> Hamlet AppRoute
|
navbar :: TemplateData -> Hamlet AppRoute
|
||||||
navbar TD{p=p,j=j,today=today} = [$hamlet|
|
navbar TD{p=p,j=j,today=today} = [$hamlet|
|
||||||
#navbar
|
<div#navbar
|
||||||
%a.topleftlink!href=$hledgerorgurl$
|
<a.topleftlink href=#{hledgerorgurl}
|
||||||
hledger
|
hledger
|
||||||
<br />
|
<br />
|
||||||
$version$
|
#{version}
|
||||||
%a.toprightlink!href=$manualurl$!target=hledgerhelp manual
|
<a.toprightlink href=#{manualurl} target=hledgerhelp manual
|
||||||
%h1 $title$
|
<h1>#{title}
|
||||||
\ $
|
\ #
|
||||||
%span#journaldesc $desc$
|
<span#journaldesc>#{desc}
|
||||||
|]
|
|]
|
||||||
where (title, desc) = journalTitleDesc j p today
|
where (title, desc) = journalTitleDesc j p today
|
||||||
|
|
||||||
@ -360,73 +384,73 @@ journalTitleDesc j p today = (title, desc)
|
|||||||
-- | Links to the main views.
|
-- | Links to the main views.
|
||||||
navlinks :: TemplateData -> Hamlet AppRoute
|
navlinks :: TemplateData -> Hamlet AppRoute
|
||||||
navlinks td = [$hamlet|
|
navlinks td = [$hamlet|
|
||||||
#navlinks
|
<div#navlinks
|
||||||
^accountsjournallink^
|
^{accountsjournallink}
|
||||||
\ | $
|
\ | #
|
||||||
^accountsregisterlink^
|
^{accountsregisterlink}
|
||||||
\ | $
|
\ | #
|
||||||
%a#addformlink!href!onclick="return addformToggle(event)" add transaction
|
<a#addformlink href onclick="return addformToggle(event)">add transaction
|
||||||
%a#importformlink!href!onclick="return importformToggle(event)"!style=display:none; import transactions
|
<a#importformlink href onclick="return importformToggle(event)" style="display:none;">import transactions
|
||||||
\ | $
|
\ | #
|
||||||
%a#editformlink!href!onclick="return editformToggle(event)" edit journal
|
<a#editformlink href onclick="return editformToggle(event)">edit journal
|
||||||
|]
|
|]
|
||||||
-- \ | $
|
-- \ | #
|
||||||
where
|
where
|
||||||
accountsjournallink = navlink td "journal" JournalR
|
accountsjournallink = navlink td "journal" JournalR
|
||||||
accountsregisterlink = navlink td "register" RegisterR
|
accountsregisterlink = navlink td "register" RegisterR
|
||||||
|
|
||||||
navlink :: TemplateData -> String -> AppRoute -> Hamlet AppRoute
|
navlink :: TemplateData -> String -> AppRoute -> Hamlet AppRoute
|
||||||
navlink TD{here=here,a=a,p=p} s dest = [$hamlet|%a#$s$link.$style$!href=@?u@ $s$|]
|
navlink TD{here=here,a=a,p=p} s dest = [$hamlet|<a##{s}link.#{style} href=@?{u}>#{s}</a>|]
|
||||||
where u = (dest, concat [(if null a then [] else [("a", a)])
|
where u = (dest, concat [(if null a then [] else [("a", pack a)])
|
||||||
,(if null p then [] else [("p", p)])])
|
,(if null p then [] else [("p", pack p)])])
|
||||||
style | dest == here = "navlinkcurrent"
|
style | dest == here = "navlinkcurrent"
|
||||||
| otherwise = "navlink"
|
| otherwise = "navlink" :: Text
|
||||||
|
|
||||||
-- | Form controlling journal filtering parameters.
|
-- | Form controlling journal filtering parameters.
|
||||||
filterform :: TemplateData -> Hamlet AppRoute
|
filterform :: TemplateData -> Hamlet AppRoute
|
||||||
filterform TD{here=here,a=a,p=p} = [$hamlet|
|
filterform TD{here=here,a=a,p=p} = [$hamlet|
|
||||||
#filterformdiv
|
<div#filterformdiv
|
||||||
%form#filterform.form!method=GET!style=display:$visible$;
|
<form#filterform.form method=GET style=display:#{visible};
|
||||||
%table.form
|
<table.form
|
||||||
%tr.$filteringperiodclass$
|
<tr.#{filteringperiodclass}
|
||||||
%td
|
<td
|
||||||
filter by period:
|
filter by period:
|
||||||
\ $
|
\ #
|
||||||
%td
|
<td
|
||||||
%input!name=p!size=60!value=$p$
|
<input name=p size=60 value=#{p}
|
||||||
^phelp^
|
^{phelp}
|
||||||
\ $
|
\ #
|
||||||
%td!align=right
|
<td align=right
|
||||||
^stopfilteringperiod^
|
^{stopfilteringperiod}
|
||||||
%tr.$filteringclass$
|
<tr.#{filteringclass}
|
||||||
%td
|
<td
|
||||||
filter by account/description:
|
filter by account/description:
|
||||||
\ $
|
\ #
|
||||||
%td
|
<td
|
||||||
%input!name=a!size=60!value=$a$
|
<input name=a size=60 value=#{a}
|
||||||
^ahelp^
|
^{ahelp}
|
||||||
\ $
|
\ #
|
||||||
%input!type=submit!value=filter $
|
<input type=submit value=filter #
|
||||||
\ $
|
\ #
|
||||||
%td!align=right
|
<td align=right
|
||||||
^stopfiltering^
|
^{stopfiltering}
|
||||||
|]
|
|]
|
||||||
where
|
where
|
||||||
ahelp = helplink "filter-patterns" "?"
|
ahelp = helplink "filter-patterns" "?"
|
||||||
phelp = helplink "period-expressions" "?"
|
phelp = helplink "period-expressions" "?"
|
||||||
filtering = not $ null a
|
filtering = not $ null a
|
||||||
filteringperiod = not $ null p
|
filteringperiod = not $ null p
|
||||||
visible = "block"
|
visible = "block" :: String
|
||||||
filteringclass = if filtering then "filtering" else ""
|
filteringclass = if filtering then "filtering" else "" :: String
|
||||||
filteringperiodclass = if filteringperiod then "filtering" else ""
|
filteringperiodclass = if filteringperiod then "filtering" else "" :: String
|
||||||
stopfiltering = if filtering then [$hamlet|%a#stopfilterlink!href=@?u@ clear filter|] else nulltemplate
|
stopfiltering = if filtering then [$hamlet|<a#stopfilterlink href=@?{u} clear filter|] else nulltemplate
|
||||||
where u = (here, if filteringperiod then [("p", p)] else [])
|
where u = (here, if filteringperiod then [("p", pack p)] else [])
|
||||||
stopfilteringperiod = if filteringperiod then [$hamlet|%a#stopfilterlink!href=@?u@ clear filter|] else nulltemplate
|
stopfilteringperiod = if filteringperiod then [$hamlet|<a#stopfilterlink href=@?{u} clear filter|] else nulltemplate
|
||||||
where u = (here, if filtering then [("a", a)] else [])
|
where u = (here, if filtering then [("a", pack a)] else [])
|
||||||
|
|
||||||
-- | Link to a topic in the manual.
|
-- | Link to a topic in the manual.
|
||||||
helplink :: String -> String -> Hamlet AppRoute
|
helplink :: String -> String -> Hamlet AppRoute
|
||||||
helplink topic label = [$hamlet|%a!href=$u$!target=hledgerhelp $label$|]
|
helplink topic label = [$hamlet|<a href=#{u} target=hledgerhelp>#{label}|]
|
||||||
where u = manualurl ++ if null topic then "" else '#':topic
|
where u = manualurl ++ if null topic then "" else '#':topic
|
||||||
|
|
||||||
{-
|
{-
|
||||||
@ -521,17 +545,17 @@ getJournalR = do
|
|||||||
td = mktd{here=here, title="hledger journal", msg=msg, a=a, p=p, j=j, today=today}
|
td = mktd{here=here, title="hledger journal", msg=msg, a=a, p=p, j=j, today=today}
|
||||||
editform' = editform td
|
editform' = editform td
|
||||||
hamletToRepHtml $ pageLayout td [$hamlet|
|
hamletToRepHtml $ pageLayout td [$hamlet|
|
||||||
%div#content
|
<div#content
|
||||||
%div#sidebar
|
<div#sidebar
|
||||||
^sidecontent^
|
^{sidecontent}
|
||||||
%div#main.journal
|
<div#main.journal
|
||||||
^navlinks.td^
|
^{navlinks td}
|
||||||
%div#transactions
|
<div#transactions
|
||||||
^filterform.td^
|
^{filterform td}
|
||||||
^maincontent^
|
^{maincontent}
|
||||||
^addform.td^
|
^{addform td}
|
||||||
^editform'^
|
^{editform'}
|
||||||
^importform^
|
^{importform}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
postJournalR :: Handler RepPlain
|
postJournalR :: Handler RepPlain
|
||||||
@ -554,17 +578,17 @@ getRegisterR = do
|
|||||||
td = mktd{here=here, title="hledger register", msg=msg, a=a, p=p, j=j, today=today}
|
td = mktd{here=here, title="hledger register", msg=msg, a=a, p=p, j=j, today=today}
|
||||||
editform' = editform td
|
editform' = editform td
|
||||||
hamletToRepHtml $ pageLayout td [$hamlet|
|
hamletToRepHtml $ pageLayout td [$hamlet|
|
||||||
%div#content
|
<div#content
|
||||||
%div#sidebar
|
<div#sidebar
|
||||||
^sidecontent^
|
^{sidecontent}
|
||||||
%div#main.journal
|
<div#main.journal
|
||||||
^navlinks.td^
|
^{navlinks td}
|
||||||
%div#transactions
|
<div#transactions
|
||||||
^filterform.td^
|
^{filterform td}
|
||||||
^maincontent^
|
^{maincontent}
|
||||||
^addform.td^
|
^{addform td}
|
||||||
^editform'^
|
^{editform'}
|
||||||
^importform^
|
^{importform}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
postRegisterR :: Handler RepPlain
|
postRegisterR :: Handler RepPlain
|
||||||
@ -583,57 +607,57 @@ getAccountsOnlyR = do
|
|||||||
-- | Render a balance report as HTML.
|
-- | Render a balance report as HTML.
|
||||||
balanceReportAsHtml :: [Opt] -> TemplateData -> BalanceReport -> Hamlet AppRoute
|
balanceReportAsHtml :: [Opt] -> TemplateData -> BalanceReport -> Hamlet AppRoute
|
||||||
balanceReportAsHtml _ td@TD{here=here,a=a,p=p} (items,total) = [$hamlet|
|
balanceReportAsHtml _ td@TD{here=here,a=a,p=p} (items,total) = [$hamlet|
|
||||||
^accountsheading^
|
^{accountsheading}
|
||||||
%table.balancereport
|
<table.balancereport>
|
||||||
$forall items i
|
$forall i <- items
|
||||||
^itemAsHtml' i^
|
^{itemAsHtml' i}
|
||||||
%tr.totalrule
|
<tr.totalrule>
|
||||||
%td!colspan=2
|
<td colspan=2>
|
||||||
%tr
|
<tr>
|
||||||
%td
|
<td>
|
||||||
%td!align=right $mixedAmountAsHtml.total$
|
<td align=right>#{mixedAmountAsHtml total} >
|
||||||
|]
|
|]
|
||||||
where
|
where
|
||||||
accountsheading = [$hamlet|
|
accountsheading = [$hamlet|
|
||||||
#accountsheading
|
<div#accountsheading
|
||||||
accounts
|
accounts
|
||||||
\ $
|
\ #
|
||||||
^showlinks^
|
^{showlinks}
|
||||||
|]
|
|] :: Hamlet AppRoute
|
||||||
where
|
where
|
||||||
filteringaccts = not $ null a
|
filteringaccts = not $ null a
|
||||||
showlinks = [$hamlet|%span#showmoreaccounts ^showmore^ ^showall^|]
|
showlinks = [$hamlet|<span#showmoreaccounts ^{showmore} ^{showall}|] :: Hamlet AppRoute
|
||||||
showmore = case (filteringaccts, items) of
|
showmore = case (filteringaccts, items) of
|
||||||
-- cunning parent account logic
|
-- cunning parent account logic
|
||||||
(True, ((acct, _, _, _):_)) ->
|
(True, ((acct, _, _, _):_)) ->
|
||||||
let a' = if isAccountRegex a then a else acct
|
let a' = if isAccountRegex a then a else acct
|
||||||
a'' = accountNameToAccountRegex $ parentAccountName $ accountRegexToAccountName a'
|
a'' = accountNameToAccountRegex $ parentAccountName $ accountRegexToAccountName a'
|
||||||
parenturl = (here, [("a",a''), ("p",p)])
|
parenturl = (here, [("a",pack a''), ("p",pack p)])
|
||||||
in [$hamlet|
|
in [$hamlet|
|
||||||
\ | $
|
\ | #
|
||||||
%a!href=@?parenturl@ show more ↑
|
<a href=@?{parenturl} show more ↑
|
||||||
|]
|
|]
|
||||||
_ -> nulltemplate
|
_ -> nulltemplate
|
||||||
showall = if filteringaccts
|
showall = if filteringaccts
|
||||||
then [$hamlet|
|
then [$hamlet|
|
||||||
\ | $
|
\ | #
|
||||||
%a!href=@?allurl@ show all
|
<a href=@?{allurl} show all
|
||||||
|]
|
|]
|
||||||
else nulltemplate
|
else nulltemplate
|
||||||
where allurl = (here, [("p",p)])
|
where allurl = (here, [("p",pack p)])
|
||||||
itemAsHtml' = itemAsHtml td
|
itemAsHtml' = itemAsHtml td
|
||||||
itemAsHtml :: TemplateData -> BalanceReportItem -> Hamlet AppRoute
|
itemAsHtml :: TemplateData -> BalanceReportItem -> Hamlet AppRoute
|
||||||
itemAsHtml TD{p=p} (acct, adisplay, adepth, abal) = [$hamlet|
|
itemAsHtml TD{p=p} (acct, adisplay, adepth, abal) = [$hamlet|
|
||||||
%tr.item
|
<tr.item
|
||||||
%td.account
|
<td.account
|
||||||
$indent$
|
#{indent}
|
||||||
%a!href=$aurl$ $adisplay$
|
<a href=#{aurl}>#{adisplay}
|
||||||
%td.balance!align=right $mixedAmountAsHtml.abal$
|
<td.balance align=right>#{mixedAmountAsHtml abal}
|
||||||
|] where
|
|] where
|
||||||
-- current = if not (null a) && containsRegex a acct then "current" else ""
|
-- current = if not (null a) && containsRegex a acct then "current" else ""
|
||||||
indent = preEscapedString $ concat $ replicate (2 * adepth) " "
|
indent = preEscapedString $ concat $ replicate (2 * adepth) " "
|
||||||
aurl = printf ".?a=%s%s" (accountNameToAccountRegex acct) p' :: String
|
aurl = printf ".?a=%s%s" (accountNameToAccountRegex acct) p' :: String
|
||||||
p' = if null p then "" else printf "&p=%s" p
|
p' = if null p then "" else printf "&p=%s" p :: String
|
||||||
|
|
||||||
accountNameToAccountRegex :: String -> String
|
accountNameToAccountRegex :: String -> String
|
||||||
accountNameToAccountRegex "" = ""
|
accountNameToAccountRegex "" = ""
|
||||||
@ -656,39 +680,39 @@ getJournalOnlyR = do
|
|||||||
editform' = editform td
|
editform' = editform td
|
||||||
txns = journalReportAsHtml opts td $ journalReport opts fspec j
|
txns = journalReportAsHtml opts td $ journalReport opts fspec j
|
||||||
hamletToRepHtml $ pageLayout td [$hamlet|
|
hamletToRepHtml $ pageLayout td [$hamlet|
|
||||||
%div#journal
|
<div#journal
|
||||||
%div.nav2
|
<div.nav2
|
||||||
%a#addformlink!href!onclick="return addformToggle(event)" add one transaction
|
<a#addformlink href onclick="return addformToggle(event)" add one transaction
|
||||||
\ | $
|
\ | #
|
||||||
%a#editformlink!href!onclick="return editformToggle(event)" edit the whole journal
|
<a#editformlink href onclick="return editformToggle(event)" edit the whole journal
|
||||||
#transactions ^txns^
|
<div#transactions ^{txns}
|
||||||
^addform.td^
|
^{addform td}
|
||||||
^editform'^
|
^{editform'}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
-- | Render a journal report as HTML.
|
-- | Render a journal report as HTML.
|
||||||
journalReportAsHtml :: [Opt] -> TemplateData -> JournalReport -> Hamlet AppRoute
|
journalReportAsHtml :: [Opt] -> TemplateData -> JournalReport -> Hamlet AppRoute
|
||||||
journalReportAsHtml _ td items = [$hamlet|
|
journalReportAsHtml _ td items = [$hamlet|
|
||||||
%table.journalreport
|
<table.journalreport>
|
||||||
$forall number.items i
|
$forall i <- number items
|
||||||
^itemAsHtml' i^
|
^{itemAsHtml' i}
|
||||||
|]
|
|]
|
||||||
where
|
where
|
||||||
number = zip [1..]
|
number = zip [1..]
|
||||||
itemAsHtml' = itemAsHtml td
|
itemAsHtml' = itemAsHtml td
|
||||||
itemAsHtml :: TemplateData -> (Int, JournalReportItem) -> Hamlet AppRoute
|
itemAsHtml :: TemplateData -> (Int, JournalReportItem) -> Hamlet AppRoute
|
||||||
itemAsHtml _ (n, t) = [$hamlet|
|
itemAsHtml _ (n, t) = [$hamlet|
|
||||||
%tr.item.$evenodd$
|
<tr.item.#{evenodd} >
|
||||||
%td.transaction
|
<td.transaction>
|
||||||
%pre $txn$
|
<pre> #{txn}
|
||||||
|] where
|
|] where
|
||||||
evenodd = if even n then "even" else "odd"
|
evenodd = if even n then "even" else "odd" :: String
|
||||||
txn = trimnl $ showTransaction t where trimnl = reverse . dropWhile (=='\n') . reverse
|
txn = trimnl $ showTransaction t where trimnl = reverse . dropWhile (=='\n') . reverse
|
||||||
|
|
||||||
addform :: TemplateData -> Hamlet AppRoute
|
addform :: TemplateData -> Hamlet AppRoute
|
||||||
addform td = [$hamlet|
|
addform td = [$hamlet|
|
||||||
%script!type=text/javascript
|
<script type=text/javascript>
|
||||||
$$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
/* dhtmlxcombo setup */
|
/* dhtmlxcombo setup */
|
||||||
window.dhx_globalImgPath="../static/";
|
window.dhx_globalImgPath="../static/";
|
||||||
var desccombo = new dhtmlXCombo("description");
|
var desccombo = new dhtmlXCombo("description");
|
||||||
@ -703,50 +727,50 @@ addform td = [$hamlet|
|
|||||||
/* desccombo.enableOptionAutoHeight(true, 20); */
|
/* desccombo.enableOptionAutoHeight(true, 20); */
|
||||||
/* desccombo.setOptionHeight(200); */
|
/* desccombo.setOptionHeight(200); */
|
||||||
});
|
});
|
||||||
%form#addform!method=POST!style=display:none;
|
<form#addform method=POST style=display:none;
|
||||||
%table.form
|
<table.form
|
||||||
%tr
|
<tr
|
||||||
%td!colspan=4
|
<td colspan=4
|
||||||
%table
|
<table
|
||||||
%tr#descriptionrow
|
<tr#descriptionrow
|
||||||
%td
|
<td
|
||||||
Date:
|
Date:
|
||||||
%td
|
<td
|
||||||
%input.textinput!size=15!name=date!value=$date$
|
<input.textinput size=15 name=date value=#{date}
|
||||||
%td!style=padding-left:1em;
|
<td style=padding-left:1em;
|
||||||
Description:
|
Description:
|
||||||
%td
|
<td
|
||||||
%select!id=description!name=description
|
<select id=description name=description
|
||||||
%option
|
<option
|
||||||
$forall descriptions d
|
$forall d <- descriptions
|
||||||
%option!value=$d$ $d$
|
<option value=#{d}>#{d}
|
||||||
%tr.helprow
|
<tr.helprow
|
||||||
%td
|
<td
|
||||||
%td
|
<td
|
||||||
.help $datehelp$ $
|
<span.help>#{datehelp} #
|
||||||
%td
|
<td
|
||||||
%td
|
<td
|
||||||
.help $deschelp$
|
<span.help>#{deschelp}
|
||||||
^postingsfields.td^
|
^{postingsfields td}
|
||||||
%tr#addbuttonrow
|
<tr#addbuttonrow
|
||||||
%td!colspan=4
|
<td colspan=4
|
||||||
%input!type=hidden!name=action!value=add
|
<input type=hidden name=action value=add
|
||||||
%input!type=submit!name=submit!value="add transaction"
|
<input type=submit name=submit value="add transaction"
|
||||||
$if manyfiles
|
$if manyfiles
|
||||||
\ to: ^journalselect.files.j.td^
|
\ to: ^{journalselect $ files $ j td}
|
||||||
|]
|
|]
|
||||||
where
|
where
|
||||||
-- datehelplink = helplink "dates" "..."
|
-- datehelplink = helplink "dates" "..."
|
||||||
datehelp = "eg: 2010/7/20"
|
datehelp = "eg: 2010/7/20" :: String
|
||||||
deschelp = "eg: supermarket (optional)"
|
deschelp = "eg: supermarket (optional)" :: String
|
||||||
date = "today"
|
date = "today" :: String
|
||||||
descriptions = sort $ nub $ map tdescription $ jtxns $ j td
|
descriptions = sort $ nub $ map tdescription $ jtxns $ j td
|
||||||
manyfiles = (length $ files $ j td) > 1
|
manyfiles = (length $ files $ j td) > 1
|
||||||
|
|
||||||
postingsfields :: TemplateData -> Hamlet AppRoute
|
postingsfields :: TemplateData -> Hamlet AppRoute
|
||||||
postingsfields td = [$hamlet|
|
postingsfields td = [$hamlet|
|
||||||
^p1^
|
^{p1}
|
||||||
^p2^
|
^{p2}
|
||||||
|]
|
|]
|
||||||
where
|
where
|
||||||
p1 = postingfields td 1
|
p1 = postingfields td 1
|
||||||
@ -754,21 +778,21 @@ postingsfields td = [$hamlet|
|
|||||||
|
|
||||||
postingfields :: TemplateData -> Int -> Hamlet AppRoute
|
postingfields :: TemplateData -> Int -> Hamlet AppRoute
|
||||||
postingfields TD{j=j} n = [$hamlet|
|
postingfields TD{j=j} n = [$hamlet|
|
||||||
%tr#postingrow
|
<tr#postingrow
|
||||||
%td!align=right $acctlabel$:
|
<td align=right>#{acctlabel}:
|
||||||
%td
|
<td
|
||||||
%select!id=$acctvar$!name=$acctvar$
|
<select id=#{acctvar} name=#{acctvar}
|
||||||
%option
|
<option
|
||||||
$forall acctnames a
|
$forall a <- acctnames
|
||||||
%option!value=$a$ $a$
|
<option value=#{a}>#{a}
|
||||||
^amtfield^
|
^{amtfield}
|
||||||
%tr.helprow
|
<tr.helprow
|
||||||
%td
|
<td
|
||||||
%td
|
<td
|
||||||
.help $accthelp$
|
<span.help>#{accthelp}
|
||||||
%td
|
<td
|
||||||
%td
|
<td
|
||||||
.help $amthelp$
|
<span.help>#{amthelp}
|
||||||
|]
|
|]
|
||||||
where
|
where
|
||||||
numbered = (++ show n)
|
numbered = (++ show n)
|
||||||
@ -779,41 +803,41 @@ postingfields TD{j=j} n = [$hamlet|
|
|||||||
| n == 1 = ("To account"
|
| n == 1 = ("To account"
|
||||||
,"eg: expenses:food"
|
,"eg: expenses:food"
|
||||||
,[$hamlet|
|
,[$hamlet|
|
||||||
%td!style=padding-left:1em;
|
<td style=padding-left:1em;
|
||||||
Amount:
|
Amount:
|
||||||
%td
|
<td
|
||||||
%input.textinput!size=15!name=$amtvar$!value=""
|
<input.textinput size=15 name=#{amtvar} value=""
|
||||||
|]
|
|]
|
||||||
,"eg: $6"
|
,"eg: $6"
|
||||||
)
|
)
|
||||||
| otherwise = ("From account"
|
| otherwise = ("From account" :: String
|
||||||
,"eg: assets:bank:checking"
|
,"eg: assets:bank:checking" :: String
|
||||||
,nulltemplate
|
,nulltemplate
|
||||||
,""
|
,"" :: String
|
||||||
)
|
)
|
||||||
|
|
||||||
editform :: TemplateData -> Hamlet AppRoute
|
editform :: TemplateData -> Hamlet AppRoute
|
||||||
editform TD{j=j} = [$hamlet|
|
editform TD{j=j} = [$hamlet|
|
||||||
%form#editform!method=POST!style=display:none;
|
<form#editform method=POST style=display:none;
|
||||||
%table.form#editform
|
<table.form#editform
|
||||||
$if manyfiles
|
$if manyfiles
|
||||||
%tr
|
<tr
|
||||||
%td!colspan=2
|
<td colspan=2
|
||||||
Editing ^journalselect.files.j^
|
Editing ^{journalselect $ files j}
|
||||||
%tr
|
<tr
|
||||||
%td!colspan=2
|
<td colspan=2
|
||||||
$forall files.j f
|
$forall f <- files j
|
||||||
%textarea!id=$fst.f$_textarea!name=text!rows=25!cols=80!style=display:none;!disabled=disabled
|
<textarea id=#{fst f}_textarea name=text rows=25 cols=80 style=display:none; disabled=disabled
|
||||||
$snd.f$
|
#{snd f}
|
||||||
%tr#addbuttonrow
|
<tr#addbuttonrow
|
||||||
%td
|
<td
|
||||||
%span.help ^formathelp^
|
<span.help ^{formathelp}
|
||||||
%td!align=right
|
<td align=right
|
||||||
%span.help Are you sure ? This will overwrite the journal. $
|
<span.help Are you sure ? This will overwrite the journal. #
|
||||||
%input!type=hidden!name=action!value=edit
|
<input type=hidden name=action value=edit
|
||||||
%input!type=submit!name=submit!value="save journal"
|
<input type=submit name=submit value="save journal"
|
||||||
\ or $
|
\ or #
|
||||||
%a!href!onclick="return editformToggle(event)" cancel
|
<a href onclick="return editformToggle(event)">cancel
|
||||||
|] -- XXX textarea ids are unquoted journal file paths, which is not valid html
|
|] -- XXX textarea ids are unquoted journal file paths, which is not valid html
|
||||||
where
|
where
|
||||||
manyfiles = (length $ files j) > 1
|
manyfiles = (length $ files j) > 1
|
||||||
@ -821,22 +845,22 @@ editform TD{j=j} = [$hamlet|
|
|||||||
|
|
||||||
journalselect :: [(FilePath,String)] -> Hamlet AppRoute
|
journalselect :: [(FilePath,String)] -> Hamlet AppRoute
|
||||||
journalselect journalfiles = [$hamlet|
|
journalselect journalfiles = [$hamlet|
|
||||||
%select!id=journalselect!name=journal!onchange="editformJournalSelect(event)"
|
<select id=journalselect name=journal onchange="editformJournalSelect(event)"
|
||||||
$forall journalfiles f
|
$forall f <- journalfiles
|
||||||
%option!value=$fst.f$ $fst.f$
|
<option value=#{fst f}>#{fst f}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
importform :: Hamlet AppRoute
|
importform :: Hamlet AppRoute
|
||||||
importform = [$hamlet|
|
importform = [$hamlet|
|
||||||
%form#importform!method=POST!style=display:none;
|
<form#importform method=POST style=display:none;
|
||||||
%table.form
|
<table.form
|
||||||
%tr
|
<tr
|
||||||
%td
|
<td
|
||||||
%input!type=file!name=file
|
<input type=file name=file
|
||||||
%input!type=hidden!name=action!value=import
|
<input type=hidden name=action value=import
|
||||||
%input!type=submit!name=submit!value="import from file"
|
<input type=submit name=submit value="import from file"
|
||||||
\ or $
|
\ or #
|
||||||
%a!href!onclick="return importformToggle(event)" cancel
|
<a href onclick="return importformToggle(event)" cancel
|
||||||
|]
|
|]
|
||||||
|
|
||||||
postJournalOnlyR :: Handler RepPlain
|
postJournalOnlyR :: Handler RepPlain
|
||||||
@ -862,16 +886,18 @@ postAddForm = do
|
|||||||
<*> maybeStringInput "amount2"
|
<*> maybeStringInput "amount2"
|
||||||
<*> maybeStringInput "journal"
|
<*> maybeStringInput "journal"
|
||||||
-- supply defaults and parse date and amounts, or get errors.
|
-- supply defaults and parse date and amounts, or get errors.
|
||||||
let dateE = maybe (Left "date required") (either (\e -> Left $ showDateParseError e) Right . fixSmartDateStrEither today) dateM
|
let dateE = maybe (Left "date required") (either (\e -> Left $ showDateParseError e) Right . fixSmartDateStrEither today . unpack) dateM
|
||||||
descE = Right $ fromMaybe "" descM
|
descE = Right $ maybe "" unpack descM
|
||||||
acct1E = maybe (Left "to account required") Right acct1M
|
acct1E = maybe (Left "to account required") (Right . unpack) acct1M
|
||||||
acct2E = maybe (Left "from account required") Right acct2M
|
acct2E = maybe (Left "from account required") (Right . unpack) acct2M
|
||||||
amt1E = maybe (Left "amount required") (either (const $ Left "could not parse amount") Right . parseWithCtx nullctx someamount) amt1M
|
amt1E = maybe (Left "amount required") (either (const $ Left "could not parse amount") Right . parseWithCtx nullctx someamount . unpack) amt1M
|
||||||
amt2E = maybe (Right missingamt) (either (const $ Left "could not parse amount") Right . parseWithCtx nullctx someamount) amt2M
|
amt2E = maybe (Right missingamt) (either (const $ Left "could not parse amount") Right . parseWithCtx nullctx someamount . unpack) amt2M
|
||||||
journalE = maybe (Right $ journalFilePath j)
|
journalE = maybe (Right $ journalFilePath j)
|
||||||
(\f -> if f `elem` journalFilePaths j
|
(\f -> let f' = unpack f in
|
||||||
then Right f
|
if f' `elem` journalFilePaths j
|
||||||
else Left $ "unrecognised journal file path: " ++ f)
|
then Right f'
|
||||||
|
else Left $ "unrecognised journal file path: " ++ f'
|
||||||
|
)
|
||||||
journalM
|
journalM
|
||||||
strEs = [dateE, descE, acct1E, acct2E, journalE]
|
strEs = [dateE, descE, acct1E, acct2E, journalE]
|
||||||
amtEs = [amt1E, amt2E]
|
amtEs = [amt1E, amt2E]
|
||||||
@ -893,13 +919,13 @@ postAddForm = do
|
|||||||
case tE of
|
case tE of
|
||||||
Left errs -> do
|
Left errs -> do
|
||||||
-- save current form values in session
|
-- save current form values in session
|
||||||
setMessage $ string $ intercalate "; " errs
|
setMessage $ toHtml $ intercalate "; " errs
|
||||||
redirect RedirectTemporary RegisterR
|
redirect RedirectTemporary RegisterR
|
||||||
|
|
||||||
Right t -> do
|
Right t -> do
|
||||||
let t' = txnTieKnot t -- XXX move into balanceTransaction
|
let t' = txnTieKnot t -- XXX move into balanceTransaction
|
||||||
liftIO $ appendToJournalFile journalpath $ showTransaction t'
|
liftIO $ appendToJournalFile journalpath $ showTransaction t'
|
||||||
setMessage $ string $ printf "Added transaction:\n%s" (show t')
|
setMessage $ toHtml $ (printf "Added transaction:\n%s" (show t') :: String)
|
||||||
redirect RedirectTemporary RegisterR
|
redirect RedirectTemporary RegisterR
|
||||||
|
|
||||||
-- | Handle a journal edit form post.
|
-- | Handle a journal edit form post.
|
||||||
@ -912,10 +938,11 @@ postEditForm = do
|
|||||||
$ (,)
|
$ (,)
|
||||||
<$> maybeStringInput "text"
|
<$> maybeStringInput "text"
|
||||||
<*> maybeStringInput "journal"
|
<*> maybeStringInput "journal"
|
||||||
let textE = maybe (Left "No value provided") Right textM
|
let textE = maybe (Left "No value provided") (Right . unpack) textM
|
||||||
journalE = maybe (Right $ journalFilePath j)
|
journalE = maybe (Right $ journalFilePath j)
|
||||||
(\f -> if f `elem` journalFilePaths j
|
(\f -> let f' = unpack f in
|
||||||
then Right f
|
if f' `elem` journalFilePaths j
|
||||||
|
then Right f'
|
||||||
else Left "unrecognised journal file path")
|
else Left "unrecognised journal file path")
|
||||||
journalM
|
journalM
|
||||||
strEs = [textE, journalE]
|
strEs = [textE, journalE]
|
||||||
@ -924,7 +951,7 @@ postEditForm = do
|
|||||||
-- display errors or perform edit
|
-- display errors or perform edit
|
||||||
if not $ null errs
|
if not $ null errs
|
||||||
then do
|
then do
|
||||||
setMessage $ string $ intercalate "; " errs
|
setMessage $ toHtml (intercalate "; " errs :: String)
|
||||||
redirect RedirectTemporary JournalR
|
redirect RedirectTemporary JournalR
|
||||||
|
|
||||||
else do
|
else do
|
||||||
@ -935,24 +962,24 @@ postEditForm = do
|
|||||||
changed = tnew /= told || filechanged'
|
changed = tnew /= told || filechanged'
|
||||||
if not changed
|
if not changed
|
||||||
then do
|
then do
|
||||||
setMessage $ string $ "No change"
|
setMessage "No change"
|
||||||
redirect RedirectTemporary JournalR
|
redirect RedirectTemporary JournalR
|
||||||
else do
|
else do
|
||||||
jE <- liftIO $ journalFromPathAndString Nothing journalpath tnew
|
jE <- liftIO $ journalFromPathAndString Nothing journalpath tnew
|
||||||
either
|
either
|
||||||
(\e -> do
|
(\e -> do
|
||||||
setMessage $ string e
|
setMessage $ toHtml e
|
||||||
redirect RedirectTemporary JournalR)
|
redirect RedirectTemporary JournalR)
|
||||||
(const $ do
|
(const $ do
|
||||||
liftIO $ writeFileWithBackup journalpath tnew
|
liftIO $ writeFileWithBackup journalpath tnew
|
||||||
setMessage $ string $ printf "Saved journal %s\n" (show journalpath)
|
setMessage $ toHtml (printf "Saved journal %s\n" (show journalpath) :: String)
|
||||||
redirect RedirectTemporary JournalR)
|
redirect RedirectTemporary JournalR)
|
||||||
jE
|
jE
|
||||||
|
|
||||||
-- | Handle an import page post.
|
-- | Handle an import page post.
|
||||||
postImportForm :: Handler RepPlain
|
postImportForm :: Handler RepPlain
|
||||||
postImportForm = do
|
postImportForm = do
|
||||||
setMessage $ string $ "can't handle file upload yet"
|
setMessage "can't handle file upload yet"
|
||||||
redirect RedirectTemporary JournalR
|
redirect RedirectTemporary JournalR
|
||||||
-- -- get form input values, or basic validation errors. E means an Either value.
|
-- -- get form input values, or basic validation errors. E means an Either value.
|
||||||
-- fileM <- runFormPost' $ maybeFileInput "file"
|
-- fileM <- runFormPost' $ maybeFileInput "file"
|
||||||
@ -960,11 +987,11 @@ postImportForm = do
|
|||||||
-- -- display errors or import transactions
|
-- -- display errors or import transactions
|
||||||
-- case fileE of
|
-- case fileE of
|
||||||
-- Left errs -> do
|
-- Left errs -> do
|
||||||
-- setMessage $ string errs
|
-- setMessage errs
|
||||||
-- redirect RedirectTemporary JournalR
|
-- redirect RedirectTemporary JournalR
|
||||||
|
|
||||||
-- Right s -> do
|
-- Right s -> do
|
||||||
-- setMessage $ string $ s
|
-- setMessage s
|
||||||
-- redirect RedirectTemporary JournalR
|
-- redirect RedirectTemporary JournalR
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
@ -980,41 +1007,41 @@ getRegisterOnlyR = do
|
|||||||
-- | Render a register report as HTML.
|
-- | Render a register report as HTML.
|
||||||
registerReportAsHtml :: [Opt] -> TemplateData -> RegisterReport -> Hamlet AppRoute
|
registerReportAsHtml :: [Opt] -> TemplateData -> RegisterReport -> Hamlet AppRoute
|
||||||
registerReportAsHtml _ td items = [$hamlet|
|
registerReportAsHtml _ td items = [$hamlet|
|
||||||
%table.registerreport
|
<table.registerreport
|
||||||
%tr.headings
|
<tr.headings
|
||||||
^headings^
|
^{headings}
|
||||||
$forall number.items i
|
$forall i <- number items
|
||||||
^itemAsHtml' i^
|
^{itemAsHtml' i}
|
||||||
|]
|
|]
|
||||||
where
|
where
|
||||||
number = zip [1..]
|
number = zip [1..]
|
||||||
headings = [$hamlet|
|
headings = [$hamlet|
|
||||||
%th.date!align=left Date
|
<th.date align=left Date
|
||||||
%th.description!align=left Description
|
<th.description align=left Description
|
||||||
%th.account!align=left Account
|
<th.account align=left Account
|
||||||
%th.amount!align=right Amount
|
<th.amount align=right Amount
|
||||||
%th.balance!align=right Balance
|
<th.balance align=right Balance
|
||||||
|]
|
|] :: Hamlet AppRoute
|
||||||
itemAsHtml' = itemAsHtml td
|
itemAsHtml' = itemAsHtml td
|
||||||
itemAsHtml :: TemplateData -> (Int, RegisterReportItem) -> Hamlet AppRoute
|
itemAsHtml :: TemplateData -> (Int, RegisterReportItem) -> Hamlet AppRoute
|
||||||
itemAsHtml TD{p=p} (n, (ds, posting, b)) = [$hamlet|
|
itemAsHtml TD{p=p} (n, (ds, posting, b)) = [$hamlet|
|
||||||
%tr.item.$evenodd$.$firstposting$
|
<tr.item.#{evenodd}.#{firstposting}
|
||||||
%td.date $date$
|
<td.date>#{date}
|
||||||
%td.description $desc$
|
<td.description>#{desc}
|
||||||
%td.account
|
<td.account
|
||||||
%a!href=$aurl$ $acct$
|
<a href=#{aurl}>#{acct}
|
||||||
%td.amount!align=right $mixedAmountAsHtml.pamount.posting$
|
<td.amount align=right>#{mixedAmountAsHtml $ pamount posting}
|
||||||
%td.balance!align=right $mixedAmountAsHtml.b$
|
<td.balance align=right>#{mixedAmountAsHtml b}
|
||||||
|] where
|
|] where
|
||||||
evenodd = if even n then "even" else "odd"
|
evenodd = if even n then "even" else "odd" :: String
|
||||||
(firstposting, date, desc) = case ds of Just (da, de) -> ("firstposting", show da, de)
|
(firstposting, date, desc) = case ds of Just (da, de) -> ("firstposting", show da, de)
|
||||||
Nothing -> ("", "", "")
|
Nothing -> ("", "", "") :: (String,String,String)
|
||||||
acct = paccount posting
|
acct = paccount posting
|
||||||
aurl = printf ".?a=%s%s" (accountNameToAccountRegex acct) p' :: String
|
aurl = printf ".?a=%s%s" (accountNameToAccountRegex acct) p' :: String
|
||||||
p' = if null p then "" else printf "&p=%s" p
|
p' = if null p then "" else printf "&p=%s" p :: String
|
||||||
|
|
||||||
mixedAmountAsHtml b = preEscapedString $ addclass $ intercalate "<br>" $ lines $ show b
|
mixedAmountAsHtml b = preEscapedString $ addclass $ intercalate "<br>" $ lines $ show b
|
||||||
where addclass = printf "<span class=\"%s\">%s</span>" c
|
where addclass = printf "<span class=\"%s\">%s</span>" (c :: String)
|
||||||
c = case isNegativeMixedAmount b of Just True -> "negative amount"
|
c = case isNegativeMixedAmount b of Just True -> "negative amount"
|
||||||
_ -> "positive amount"
|
_ -> "positive amount"
|
||||||
|
|
||||||
@ -1058,10 +1085,10 @@ getAddformRTR = do
|
|||||||
| n == 1 = ("To account"
|
| n == 1 = ("To account"
|
||||||
,"eg: expenses:food"
|
,"eg: expenses:food"
|
||||||
,[$hamlet|
|
,[$hamlet|
|
||||||
%td!style=padding-left:1em;
|
%td style=padding-left:1em;
|
||||||
Amount:
|
Amount:
|
||||||
%td
|
%td
|
||||||
%input.textinput!size=15!name=$amtvar$!value=""
|
%input.textinput size=15 name=$amtvar$ value=""
|
||||||
|]
|
|]
|
||||||
,"eg: $6"
|
,"eg: $6"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -8,12 +8,12 @@ Released under GPL version 3 or later.
|
|||||||
module Hledger.Web.Main where
|
module Hledger.Web.Main where
|
||||||
|
|
||||||
import Control.Concurrent (forkIO, threadDelay)
|
import Control.Concurrent (forkIO, threadDelay)
|
||||||
import Network.Wai.Handler.SimpleServer (run)
|
import Data.Text(pack)
|
||||||
import System.Exit (exitFailure)
|
import System.Exit (exitFailure)
|
||||||
import System.IO.Storage (withStore, putValue,)
|
import System.IO.Storage (withStore, putValue,)
|
||||||
import Yesod.Content (typeByExt)
|
|
||||||
import Yesod.Helpers.Static (fileLookupDir)
|
|
||||||
import System.Console.GetOpt
|
import System.Console.GetOpt
|
||||||
|
import Yesod
|
||||||
|
import Yesod.Helpers.Static
|
||||||
|
|
||||||
import Hledger.Cli.Options
|
import Hledger.Cli.Options
|
||||||
import Hledger.Cli.Utils (withJournalDo, openBrowserOn)
|
import Hledger.Cli.Utils (withJournalDo, openBrowserOn)
|
||||||
@ -21,7 +21,7 @@ import Hledger.Cli.Version (progversionstr, binaryfilename)
|
|||||||
import Hledger.Data
|
import Hledger.Data
|
||||||
import Prelude hiding (putStr, putStrLn)
|
import Prelude hiding (putStr, putStrLn)
|
||||||
import Hledger.Data.UTF8 (putStr, putStrLn)
|
import Hledger.Data.UTF8 (putStr, putStrLn)
|
||||||
import Hledger.Web.App (App(..), withApp)
|
import Hledger.Web.App (App(..))
|
||||||
import Hledger.Web.Files (createFilesIfMissing)
|
import Hledger.Web.Files (createFilesIfMissing)
|
||||||
import Hledger.Web.Settings (browserstartdelay, defhost, defport, datadir)
|
import Hledger.Web.Settings (browserstartdelay, defhost, defport, datadir)
|
||||||
|
|
||||||
@ -82,15 +82,15 @@ server baseurl port opts args j = do
|
|||||||
printf "Starting http server on port %d with base url %s\n" port baseurl
|
printf "Starting http server on port %d with base url %s\n" port baseurl
|
||||||
withStore "hledger" $ do
|
withStore "hledger" $ do
|
||||||
putValue "hledger" "journal" j
|
putValue "hledger" "journal" j
|
||||||
withApp App{
|
warpDebug port $ App{
|
||||||
-- appConnPool=Nothing
|
-- appConnPool=Nothing
|
||||||
appRoot=baseurl
|
appRoot=pack baseurl
|
||||||
,appDataDir=datadir
|
,appDataDir=datadir
|
||||||
,appStatic=fileLookupDir datadir $ typeByExt -- ++[("hamlet","text/plain")]
|
,appStaticSettings=static datadir
|
||||||
,appOpts=opts
|
,appOpts=opts
|
||||||
,appArgs=args
|
,appArgs=args
|
||||||
,appJournal=j
|
,appJournal=j
|
||||||
} $ run port
|
}
|
||||||
|
|
||||||
browser :: String -> IO ()
|
browser :: String -> IO ()
|
||||||
browser baseurl = do
|
browser baseurl = do
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{-# LANGUAGE CPP #-}
|
{-# LANGUAGE CPP, OverloadedStrings #-}
|
||||||
module Hledger.Web.Settings
|
module Hledger.Web.Settings
|
||||||
(
|
(
|
||||||
hamletFile
|
hamletFile
|
||||||
@ -42,17 +42,18 @@ browserstartdelay = 100000 -- microseconds
|
|||||||
-- urls
|
-- urls
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
hledgerorgurl, manualurl :: String
|
||||||
hledgerorgurl = "http://hledger.org"
|
hledgerorgurl = "http://hledger.org"
|
||||||
manualurl = hledgerorgurl++"/MANUAL.html"
|
manualurl = hledgerorgurl++"/MANUAL.html"
|
||||||
|
|
||||||
defhost = "localhost"
|
defhost = "localhost" :: String
|
||||||
defport = 5000
|
defport = 5000
|
||||||
|
|
||||||
approot :: String
|
approot :: String
|
||||||
#ifdef PRODUCTION
|
#ifdef PRODUCTION
|
||||||
approot = printf "http://%s:%d" defhost (defport :: Int)
|
approot = printf "http://%s:%d" defhost (defport :: Int) :: String
|
||||||
#else
|
#else
|
||||||
approot = printf "http://%s:%d" defhost (defport :: Int)
|
approot = printf "http://%s:%d" defhost (defport :: Int) :: String
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
staticroot :: String
|
staticroot :: String
|
||||||
|
|||||||
@ -68,13 +68,12 @@ executable hledger-web
|
|||||||
-- ,time
|
-- ,time
|
||||||
-- ,utf8-string >= 0.3.5 && < 0.4
|
-- ,utf8-string >= 0.3.5 && < 0.4
|
||||||
,io-storage >= 0.3 && < 0.4
|
,io-storage >= 0.3 && < 0.4
|
||||||
,yesod >= 0.6.1.2 && < 0.7
|
,yesod >= 0.8 && < 0.9
|
||||||
,hamlet >= 0.6.0.1 && < 0.7
|
|
||||||
-- ,convertible-text >= 0.3.0.1 && < 0.4
|
-- ,convertible-text >= 0.3.0.1 && < 0.4
|
||||||
-- ,data-object >= 0.3.1.2 && < 0.4
|
-- ,data-object >= 0.3.1.2 && < 0.4
|
||||||
,failure >= 0.1 && < 0.2
|
,failure >= 0.1 && < 0.2
|
||||||
-- ,persistent == 0.2.*
|
-- ,persistent == 0.2.*
|
||||||
-- ,persistent-sqlite == 0.2.*
|
-- ,persistent-sqlite == 0.2.*
|
||||||
,template-haskell >= 2.4 && < 2.6
|
,template-haskell >= 2.4 && < 2.6
|
||||||
,wai-extra == 0.2.*
|
,wai-extra == 0.4.*
|
||||||
,file-embed == 0.0.*
|
,file-embed == 0.0.*
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user