web: ui cleanups, replace balance/register with combo view
This commit is contained in:
		
							parent
							
								
									4467af1aa8
								
							
						
					
					
						commit
						0773dde872
					
				| @ -44,7 +44,7 @@ add opts args j | |||||||
| getAndAddTransactions :: Journal -> [Opt] -> [String] -> Day -> IO () | getAndAddTransactions :: Journal -> [Opt] -> [String] -> Day -> IO () | ||||||
| getAndAddTransactions j opts args defaultDate = do | getAndAddTransactions j opts args defaultDate = do | ||||||
|   (t, d) <- getTransaction j opts args defaultDate |   (t, d) <- getTransaction j opts args defaultDate | ||||||
|   j <- journalAddTransaction j t |   j <- journalAddTransaction j opts t | ||||||
|   getAndAddTransactions j opts args d |   getAndAddTransactions j opts args d | ||||||
| 
 | 
 | ||||||
| -- | Read a transaction from the command line, with history-aware prompting. | -- | Read a transaction from the command line, with history-aware prompting. | ||||||
| @ -134,11 +134,12 @@ askFor prompt def validator = do | |||||||
| -- | Append this transaction to the journal's file. Also, to the journal's | -- | Append this transaction to the journal's file. Also, to the journal's | ||||||
| -- transaction list, but we don't bother updating the other fields - this | -- transaction list, but we don't bother updating the other fields - this | ||||||
| -- is enough to include new transactions in the history matching. | -- is enough to include new transactions in the history matching. | ||||||
| journalAddTransaction :: Journal -> Transaction -> IO Journal | journalAddTransaction :: Journal -> [Opt] -> Transaction -> IO Journal | ||||||
| journalAddTransaction j@Journal{jtxns=ts} t = do | journalAddTransaction j@Journal{jtxns=ts} opts t = do | ||||||
|   appendToJournalFile j $ showTransaction t |   appendToJournalFile j $ showTransaction t | ||||||
|   putStrLn $ printf "\nAdded transaction to %s:" (filepath j) |   when (Debug `elem` opts) $ do | ||||||
|   putStrLn =<< registerFromString (show t) |     putStrLn $ printf "\nAdded transaction to %s:" (filepath j) | ||||||
|  |     putStrLn =<< registerFromString (show t) | ||||||
|   return j{jtxns=ts++[t]} |   return j{jtxns=ts++[t]} | ||||||
| 
 | 
 | ||||||
| -- | Append data to the journal's file, ensuring proper separation from | -- | Append data to the journal's file, ensuring proper separation from | ||||||
|  | |||||||
| @ -96,9 +96,9 @@ balance report: | |||||||
| -} | -} | ||||||
| 
 | 
 | ||||||
| module Hledger.Cli.Commands.Balance ( | module Hledger.Cli.Commands.Balance ( | ||||||
|   balance |   BalanceReport | ||||||
|  ,BalanceReport |  | ||||||
|  ,BalanceReportItem |  ,BalanceReportItem | ||||||
|  |  ,balance | ||||||
|  ,balanceReport |  ,balanceReport | ||||||
|  ,balanceReportAsText |  ,balanceReportAsText | ||||||
|  -- ,tests_Balance |  -- ,tests_Balance | ||||||
|  | |||||||
| @ -5,8 +5,13 @@ A ledger-compatible @print@ command. | |||||||
| 
 | 
 | ||||||
| -} | -} | ||||||
| 
 | 
 | ||||||
| module Hledger.Cli.Commands.Print | module Hledger.Cli.Commands.Print ( | ||||||
| where |   JournalReport | ||||||
|  |  ,JournalReportItem | ||||||
|  |  ,print' | ||||||
|  |  ,journalReport | ||||||
|  |  ,showTransactions | ||||||
|  | ) where | ||||||
| import Hledger.Data | import Hledger.Data | ||||||
| import Hledger.Cli.Options | import Hledger.Cli.Options | ||||||
| #if __GLASGOW_HASKELL__ <= 610 | #if __GLASGOW_HASKELL__ <= 610 | ||||||
| @ -15,6 +20,12 @@ import System.IO.UTF8 | |||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | -- | A "journal report" is just a list of transactions. | ||||||
|  | type JournalReport = [JournalReportItem] | ||||||
|  | 
 | ||||||
|  | -- | The data for a single journal report item, representing one transaction. | ||||||
|  | type JournalReportItem = Transaction | ||||||
|  | 
 | ||||||
| -- | Print journal transactions in standard format. | -- | Print journal transactions in standard format. | ||||||
| print' :: [Opt] -> [String] -> Journal -> IO () | print' :: [Opt] -> [String] -> Journal -> IO () | ||||||
| print' opts args j = do | print' opts args j = do | ||||||
| @ -22,8 +33,11 @@ print' opts args j = do | |||||||
|   putStr $ showTransactions (optsToFilterSpec opts args t) j |   putStr $ showTransactions (optsToFilterSpec opts args t) j | ||||||
| 
 | 
 | ||||||
| showTransactions :: FilterSpec -> Journal -> String | showTransactions :: FilterSpec -> Journal -> String | ||||||
| showTransactions filterspec j = | showTransactions fspec j = journalReportAsText [] fspec $ journalReport [] fspec j | ||||||
|     concatMap (showTransactionForPrint effective) $ sortBy (comparing tdate) txns | 
 | ||||||
|         where | journalReportAsText :: [Opt] -> FilterSpec -> JournalReport -> String -- XXX unlike the others, this one needs fspec not opts | ||||||
|           effective = EffectiveDate == whichdate filterspec | journalReportAsText _ fspec items = concatMap (showTransactionForPrint effective) items | ||||||
|           txns = jtxns $ filterJournalTransactions filterspec j |     where effective = EffectiveDate == whichdate fspec | ||||||
|  | 
 | ||||||
|  | journalReport :: [Opt] -> FilterSpec -> Journal -> JournalReport | ||||||
|  | journalReport _ fspec j = sortBy (comparing tdate) $ jtxns $ filterJournalTransactions fspec j | ||||||
| @ -6,9 +6,9 @@ A ledger-compatible @register@ command. | |||||||
| -} | -} | ||||||
| 
 | 
 | ||||||
| module Hledger.Cli.Commands.Register ( | module Hledger.Cli.Commands.Register ( | ||||||
|   register |   RegisterReport | ||||||
|  ,RegisterReport |  | ||||||
|  ,RegisterReportItem |  ,RegisterReportItem | ||||||
|  |  ,register | ||||||
|  ,registerReport |  ,registerReport | ||||||
|  ,registerReportAsText |  ,registerReportAsText | ||||||
|  ,showPostingWithBalanceForVty |  ,showPostingWithBalanceForVty | ||||||
|  | |||||||
| @ -19,6 +19,7 @@ import Hledger.Cli.Commands.Print | |||||||
| import Hledger.Cli.Commands.Register | import Hledger.Cli.Commands.Register | ||||||
| import Hledger.Cli.Options hiding (value) | import Hledger.Cli.Options hiding (value) | ||||||
| import Hledger.Cli.Utils | import Hledger.Cli.Utils | ||||||
|  | import Hledger.Cli.Version (version) | ||||||
| import Hledger.Data | import Hledger.Data | ||||||
| import Hledger.Read (journalFromPathAndString) | import Hledger.Read (journalFromPathAndString) | ||||||
| import Hledger.Read.Journal (someamount) | import Hledger.Read.Journal (someamount) | ||||||
| @ -47,14 +48,17 @@ data HledgerWebApp = HledgerWebApp { | |||||||
| mkYesod "HledgerWebApp" [$parseRoutes| | mkYesod "HledgerWebApp" [$parseRoutes| | ||||||
| /             IndexPage        GET | /             IndexPage        GET | ||||||
| /journal      JournalPage      GET POST | /journal      JournalPage      GET POST | ||||||
| /edit         EditPage         GET POST |  | ||||||
| /register     RegisterPage     GET | /register     RegisterPage     GET | ||||||
| /balance      BalancePage      GET | /balance      BalancePage      GET | ||||||
|  | /ledger       LedgerPage       GET | ||||||
| /style.css    StyleCss         GET | /style.css    StyleCss         GET | ||||||
| |] | |] | ||||||
| 
 | 
 | ||||||
| instance Yesod HledgerWebApp where approot = appRoot | instance Yesod HledgerWebApp where approot = appRoot | ||||||
| 
 | 
 | ||||||
|  | -- defaultroute = LedgerPage | ||||||
|  | defaultroute = JournalPage | ||||||
|  | 
 | ||||||
| -- | A bundle of useful data passed to templates. | -- | A bundle of useful data passed to templates. | ||||||
| data TemplateData = TD { | data TemplateData = TD { | ||||||
|      here         :: HledgerWebAppRoute -- ^ the current page's route |      here         :: HledgerWebAppRoute -- ^ the current page's route | ||||||
| @ -62,18 +66,14 @@ data TemplateData = TD { | |||||||
|     ,msg          :: Maybe (Html ())     -- ^ transient message |     ,msg          :: Maybe (Html ())     -- ^ transient message | ||||||
|     ,a            :: String             -- ^ a (filter pattern) parameter |     ,a            :: String             -- ^ a (filter pattern) parameter | ||||||
|     ,p            :: String             -- ^ p (period expression) parameter |     ,p            :: String             -- ^ p (period expression) parameter | ||||||
|     ,content      :: Html ()             -- ^ html for the content area |  | ||||||
|     ,contentplain :: String             -- ^ or plain text content |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| td = TD { | mktd = TD { | ||||||
|       here = IndexPage |       here = IndexPage | ||||||
|      ,title = "hledger" |      ,title = "hledger" | ||||||
|      ,msg = Nothing |      ,msg = Nothing | ||||||
|      ,a = "" |      ,a = "" | ||||||
|      ,p = "" |      ,p = "" | ||||||
|      ,content = nulltemplate id |  | ||||||
|      ,contentplain = "" |  | ||||||
|      } |      } | ||||||
| 
 | 
 | ||||||
| -- | The web command. | -- | The web command. | ||||||
| @ -104,9 +104,10 @@ server baseurl port opts args j = do | |||||||
|               } |               } | ||||||
|     withStore "hledger" $ do |     withStore "hledger" $ do | ||||||
|      putValue "hledger" "journal" j |      putValue "hledger" "journal" j | ||||||
|      basicHandler port app |      basicHandler' port Nothing app | ||||||
| 
 | 
 | ||||||
| -- handlers | ---------------------------------------------------------------------- | ||||||
|  | -- handlers & templates | ||||||
| 
 | 
 | ||||||
| getStyleCss :: Handler HledgerWebApp () | getStyleCss :: Handler HledgerWebApp () | ||||||
| getStyleCss = do | getStyleCss = do | ||||||
| @ -115,158 +116,107 @@ getStyleCss = do | |||||||
|     sendFile "text/css" $ dir </> "style.css" |     sendFile "text/css" $ dir </> "style.css" | ||||||
| 
 | 
 | ||||||
| getIndexPage :: Handler HledgerWebApp () | getIndexPage :: Handler HledgerWebApp () | ||||||
| getIndexPage = redirect RedirectTemporary BalancePage | getIndexPage = redirect RedirectTemporary defaultroute | ||||||
| 
 | 
 | ||||||
| -- | Gather all the stuff we want for a typical hledger web request handler. | ---------------------------------------------------------------------- | ||||||
| getHandlerParameters :: Handler HledgerWebApp |  | ||||||
|                        (String, String, [Opt], FilterSpec, Journal, Maybe (Html ()), HledgerWebAppRoute) |  | ||||||
| getHandlerParameters = do |  | ||||||
|   Just here <- getCurrentRoute |  | ||||||
|   (a, p, opts, fspec) <- getReportParameters |  | ||||||
|   (j, err) <- getLatestJournal opts |  | ||||||
|   msg <- getMessage' err |  | ||||||
|   return (a, p, opts, fspec, j, msg, here) |  | ||||||
|     where |  | ||||||
|       -- | Get current report parameters for this request. |  | ||||||
|       getReportParameters :: Handler HledgerWebApp (String, String, [Opt], FilterSpec) |  | ||||||
|       getReportParameters = do |  | ||||||
|           app <- getYesod |  | ||||||
|           t <- liftIO $ getCurrentLocalTime |  | ||||||
|           a <- fromMaybe "" <$> lookupGetParam "a" |  | ||||||
|           p <- fromMaybe "" <$> lookupGetParam "p" |  | ||||||
|           let opts = appOpts app ++ [Period p] |  | ||||||
|               args = appArgs app ++ [a] |  | ||||||
|               fspec = optsToFilterSpec opts args t |  | ||||||
|           return (a, p, opts, fspec) |  | ||||||
| 
 |  | ||||||
|       -- | Update our copy of the journal if the file changed. If there is an |  | ||||||
|       -- error while reloading, keep the old one and return the error, and set a |  | ||||||
|       -- ui message. |  | ||||||
|       getLatestJournal :: [Opt] -> Handler HledgerWebApp (Journal, Maybe String) |  | ||||||
|       getLatestJournal opts = do |  | ||||||
|         j <- liftIO $ fromJust `fmap` getValue "hledger" "journal" |  | ||||||
|         (jE, changed) <- liftIO $ journalReloadIfChanged opts j |  | ||||||
|         if not changed |  | ||||||
|          then return (j,Nothing) |  | ||||||
|          else case jE of |  | ||||||
|                 Right j' -> do liftIO $ putValue "hledger" "journal" j' |  | ||||||
|                                return (j',Nothing) |  | ||||||
|                 Left e  -> do setMessage $ string "error while reading" {- ++ ": " ++ e-} |  | ||||||
|                               return (j, Just e) |  | ||||||
| 
 |  | ||||||
|       -- | Helper to work around a yesod feature (can't set and get a message in the same request.) |  | ||||||
|       getMessage' :: Maybe String -> Handler HledgerWebApp (Maybe (Html ())) |  | ||||||
|       getMessage' newmsgstr = do |  | ||||||
|         oldmsg <- getMessage |  | ||||||
|         return $ maybe oldmsg (Just . string) newmsgstr |  | ||||||
| 
 |  | ||||||
| -- renderLatestJournalWith :: ([Opt] -> FilterSpec -> Journal -> Html ()) -> Handler HledgerWebApp RepHtml |  | ||||||
| -- renderLatestJournalWith reportHtml = do |  | ||||||
| --   (a, p, opts, fspec, j, msg, here) <- getHandlerParameters |  | ||||||
| --   let td' = td{here=here, title="hledger", msg=msg, a=a, p=p, content=reportHtml opts fspec j} |  | ||||||
| --   hamletToRepHtml $ pageLayout td' |  | ||||||
| 
 | 
 | ||||||
|  | -- | A basic journal view, like hledger print, with editing. | ||||||
| getJournalPage :: Handler HledgerWebApp RepHtml | getJournalPage :: Handler HledgerWebApp RepHtml | ||||||
| getJournalPage = do | getJournalPage = do | ||||||
|   (a, p, _, fspec, j, msg, here) <- getHandlerParameters |  | ||||||
|   let td' = td{here=here, title="hledger", msg=msg, a=a, p=p, content= |  | ||||||
|                      stringToPre $ showTransactions fspec j |  | ||||||
|               } |  | ||||||
|   hamletToRepHtml $ pageLayout td' |  | ||||||
| 
 |  | ||||||
| getBalancePage :: Handler HledgerWebApp RepHtml |  | ||||||
| getBalancePage = do |  | ||||||
|   (a, p, opts, fspec, j, msg, here) <- getHandlerParameters |   (a, p, opts, fspec, j, msg, here) <- getHandlerParameters | ||||||
|   let td' = td{here=here, title="hledger", msg=msg, a=a, p=p, content= |   let td = mktd{here=here, title="hledger", msg=msg, a=a, p=p} | ||||||
|                      balanceReportAsHtml opts td' $ balanceReport opts fspec j |       editform' = editform td $ jtext j | ||||||
|               } |       txns = journalReportAsHtml opts td $ journalReport opts fspec j | ||||||
|   hamletToRepHtml $ pageLayout td' |   hamletToRepHtml $ pageLayout td [$hamlet| | ||||||
|  | %div.journal | ||||||
|  |  ^journalScripts^ | ||||||
|  |  %div.nav2 | ||||||
|  |   %a#addformlink!href!onclick="return addformToggle()" add one transaction | ||||||
|  |   \ | $ | ||||||
|  |   %a#editformlink!href!onclick="return editformToggle()" edit the whole journal | ||||||
|  |  ^addform^ | ||||||
|  |  ^editform'^ | ||||||
|  |  #transactions ^txns^ | ||||||
|  | |] | ||||||
| 
 | 
 | ||||||
| -- | Render a balance report as HTML. | -- | Render a journal report as HTML. | ||||||
| balanceReportAsHtml :: [Opt] -> TemplateData -> BalanceReport -> Html () | journalReportAsHtml :: [Opt] -> TemplateData -> JournalReport -> Hamlet HledgerWebAppRoute | ||||||
| balanceReportAsHtml _ td (items,total) = [$hamlet| | journalReportAsHtml _ td items = [$hamlet| | ||||||
| %table.balancereport | %table.journalreport | ||||||
|  $forall items i |  $forall number.items i | ||||||
|   %tr.itemrule |  | ||||||
|    %td!colspan=2 |  | ||||||
|   ^itemAsHtml' i^ |   ^itemAsHtml' i^ | ||||||
|  %tr.totalrule | |] | ||||||
|   %td!colspan=2 |  | ||||||
|  %tr |  | ||||||
|   %td |  | ||||||
|   %td!align=right $mixedAmountAsHtml.total$ |  | ||||||
| |] id |  | ||||||
|  where |  where | ||||||
|  |    number = zip [1..] | ||||||
|    itemAsHtml' = itemAsHtml td |    itemAsHtml' = itemAsHtml td | ||||||
|    itemAsHtml :: TemplateData -> BalanceReportItem -> Hamlet String |    itemAsHtml :: TemplateData -> (Int, JournalReportItem) -> Hamlet HledgerWebAppRoute | ||||||
|    itemAsHtml TD{p=p} (a, adisplay, adepth, abal) = [$hamlet| |    itemAsHtml _ (n, t) = [$hamlet| | ||||||
|      %tr.item |      %tr.item.$evenodd$ | ||||||
|       %td.account |       %td.transaction | ||||||
|        $indent$ |        %pre $txn$ | ||||||
|        %a!href=$aurl$ $adisplay$ |  | ||||||
|       %td.balance!align=right $mixedAmountAsHtml.abal$ |  | ||||||
|      |] where |      |] where | ||||||
|        indent = preEscapedString $ concat $ replicate (2 * adepth) " " |        evenodd = if even n then "even" else "odd" | ||||||
|        aurl = printf "../register?a=^%s%s" a p' :: String |        txn = trimnl $ showTransaction t where trimnl = reverse . dropWhile (=='\n') . reverse | ||||||
|        p' = if null p then "" else printf "&p=%s" p |  | ||||||
| 
 | 
 | ||||||
| --mixedAmountAsHtml = intercalate ", " . lines . show | journalScripts = [$hamlet| | ||||||
| mixedAmountAsHtml = preEscapedString . intercalate "<br>" . lines . show | <script type="text/javascript"> | ||||||
| 
 | 
 | ||||||
| getRegisterPage :: Handler HledgerWebApp RepHtml |  function addformToggle() { | ||||||
| getRegisterPage = do |   a = document.getElementById('addform'); | ||||||
|   (a, p, opts, fspec, j, msg, here) <- getHandlerParameters |   e = document.getElementById('editform'); | ||||||
|   let td' = td{here=here, title="hledger", msg=msg, a=a, p=p, content= |   t = document.getElementById('transactions'); | ||||||
|                      registerReportAsHtml opts td' $ registerReport opts fspec j |   alink = document.getElementById('addformlink'); | ||||||
|               } |   elink = document.getElementById('editformlink'); | ||||||
|   hamletToRepHtml $ pageLayout td' |   if (a.style.display == 'none') { | ||||||
|  |    alink.style['font-weight'] = 'bold'; | ||||||
|  |    elink.style['font-weight'] = 'normal'; | ||||||
|  |    a.style.display = 'block'; | ||||||
|  |    e.style.display = 'none'; | ||||||
|  |    t.style.display = 'block'; | ||||||
|  |   } else { | ||||||
|  |    alink.style['font-weight'] = 'normal'; | ||||||
|  |    elink.style['font-weight'] = 'normal'; | ||||||
|  |    a.style.display = 'none'; | ||||||
|  |    e.style.display = 'none'; | ||||||
|  |    t.style.display = 'block'; | ||||||
|  |   } | ||||||
|  |   return false; | ||||||
|  |  } | ||||||
| 
 | 
 | ||||||
| -- | Render a register report as HTML. |  function editformToggle() { | ||||||
| registerReportAsHtml :: [Opt] -> TemplateData -> RegisterReport -> Html () |   a = document.getElementById('addform'); | ||||||
| registerReportAsHtml _ td items = [$hamlet| |   e = document.getElementById('editform'); | ||||||
| %table.registerreport |   t = document.getElementById('transactions'); | ||||||
|  $forall items i |   alink = document.getElementById('addformlink'); | ||||||
|   %tr.itemrule |   elink = document.getElementById('editformlink'); | ||||||
|    %td!colspan=5 |   if (e.style.display == 'none') { | ||||||
|   ^itemAsHtml' i^ |    alink.style['font-weight'] = 'normal'; | ||||||
| |] id |    elink.style['font-weight'] = 'bold'; | ||||||
|  where |    a.style.display = 'none'; | ||||||
|    itemAsHtml' = itemAsHtml td |    e.style.display = 'block'; | ||||||
|    itemAsHtml :: TemplateData -> RegisterReportItem -> Hamlet String |    t.style.display = 'none'; | ||||||
|    itemAsHtml TD{p=p} (ds, posting, b) = [$hamlet| |   } else { | ||||||
|      %tr.item |    alink.style['font-weight'] = 'normal'; | ||||||
|       %td.date $date$ |    elink.style['font-weight'] = 'normal'; | ||||||
|       %td.description $desc$ |    a.style.display = 'none'; | ||||||
|       %td.account |    e.style.display = 'none'; | ||||||
|        %a!href=$aurl$ $acct$ |    t.style.display = 'block'; | ||||||
|       %td.amount!align=right $mixedAmountAsHtml.pamount.posting$ |   } | ||||||
|       %td.balance!align=right $mixedAmountAsHtml.b$ |   return false; | ||||||
|      |] where |  } | ||||||
|        (date, desc) = case ds of Just (da, de) -> (show da, de) |  | ||||||
|                                  Nothing -> ("", "") |  | ||||||
|        acct = paccount posting |  | ||||||
|        aurl = printf "../register?a=^%s%s" acct p' :: String |  | ||||||
|        p' = if null p then "" else printf "&p=%s" p |  | ||||||
| 
 | 
 | ||||||
| queryStringFromAP a p = if null ap then "" else "?" ++ ap | </script> | ||||||
|     where | |] | ||||||
|       ap = intercalate "&" [a',p'] |  | ||||||
|       a' = if null a then "" else printf "&a=%s" a |  | ||||||
|       p' = if null p then "" else printf "&p=%s" p |  | ||||||
| 
 |  | ||||||
| getEditPage :: Handler HledgerWebApp RepHtml |  | ||||||
| getEditPage = do |  | ||||||
|   (a, p, _, _, _, msg, here) <- getHandlerParameters |  | ||||||
|   -- reload journal's text without parsing, if changed |  | ||||||
|   j <- liftIO $ fromJust `fmap` getValue "hledger" "journal" |  | ||||||
|   changed <- liftIO $ journalFileIsNewer j |  | ||||||
|   s <- liftIO $ if changed then readFile (filepath j) else return (jtext j) -- XXX readFile may throw an error |  | ||||||
|   let td' = td{here=here, title="hledger", msg=msg, a=a, p=p,  |  | ||||||
|                      content=(editform td') show, contentplain=s} -- XXX provide both to squeeze editform into pageLayout |  | ||||||
|   hamletToRepHtml $ pageLayout td' |  | ||||||
| 
 | 
 | ||||||
| postJournalPage :: Handler HledgerWebApp RepPlain | postJournalPage :: Handler HledgerWebApp RepPlain | ||||||
| postJournalPage = do | postJournalPage = do | ||||||
|  |   edit <- runFormPost' $ maybeStringInput "edit" | ||||||
|  |   if isJust edit then postEditForm else postAddForm | ||||||
|  | 
 | ||||||
|  | -- | Handle a journal add form post. | ||||||
|  | postAddForm :: Handler HledgerWebApp RepPlain | ||||||
|  | postAddForm = do | ||||||
|  |   (_, _, opts, _, _, _, _) <- getHandlerParameters | ||||||
|   today <- liftIO getCurrentDay |   today <- liftIO getCurrentDay | ||||||
|   -- get form input values. M means a Maybe value. |   -- get form input values. M means a Maybe value. | ||||||
|   (dateM, descM, acct1M, amt1M, acct2M, amt2M) <- runFormPost' |   (dateM, descM, acct1M, amt1M, acct2M, amt2M) <- runFormPost' | ||||||
| @ -315,12 +265,13 @@ postJournalPage = do | |||||||
|    Right t -> do |    Right t -> do | ||||||
|     let t' = txnTieKnot t -- XXX move into balanceTransaction |     let t' = txnTieKnot t -- XXX move into balanceTransaction | ||||||
|     j <- liftIO $ fromJust `fmap` getValue "hledger" "journal" |     j <- liftIO $ fromJust `fmap` getValue "hledger" "journal" | ||||||
|     liftIO $ journalAddTransaction j t' |     liftIO $ journalAddTransaction j opts t' | ||||||
|     setMessage $ string $ printf "Added transaction:\n%s" (show t') |     setMessage $ string $ printf "Added transaction:\n%s" (show t') | ||||||
|     redirect RedirectTemporary JournalPage |     redirect RedirectTemporary JournalPage | ||||||
| 
 | 
 | ||||||
| postEditPage :: Handler HledgerWebApp RepPlain | -- | Handle a journal edit form post. | ||||||
| postEditPage = do | postEditForm :: Handler HledgerWebApp RepPlain | ||||||
|  | postEditForm = do | ||||||
|   -- 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. | ||||||
|   textM  <- runFormPost' $ maybeStringInput "text" |   textM  <- runFormPost' $ maybeStringInput "text" | ||||||
|   let textE = maybe (Left "No value provided") Right textM |   let textE = maybe (Left "No value provided") Right textM | ||||||
| @ -343,134 +294,23 @@ postEditPage = do | |||||||
|     if not changed |     if not changed | ||||||
|      then do |      then do | ||||||
|        setMessage $ string $ "No change" |        setMessage $ string $ "No change" | ||||||
|        redirect RedirectTemporary EditPage |        redirect RedirectTemporary JournalPage | ||||||
|      else do |      else do | ||||||
|       jE <- liftIO $ journalFromPathAndString Nothing f tnew |       jE <- liftIO $ journalFromPathAndString Nothing f tnew | ||||||
|       either |       either | ||||||
|        (\e -> do |        (\e -> do | ||||||
|           setMessage $ string e |           setMessage $ string e | ||||||
|           redirect RedirectTemporary EditPage) |           redirect RedirectTemporary JournalPage) | ||||||
|        (const $ do |        (const $ do | ||||||
|           liftIO $ writeFileWithBackup f tnew |           liftIO $ writeFileWithBackup f tnew | ||||||
|           setMessage $ string $ printf "Saved journal %s\n" (show f) |           setMessage $ string $ printf "Saved journal %s\n" (show f) | ||||||
|           redirect RedirectTemporary JournalPage) |           redirect RedirectTemporary JournalPage) | ||||||
|        jE |        jE | ||||||
| 
 | 
 | ||||||
| -- templates |  | ||||||
| 
 |  | ||||||
| nulltemplate = [$hamlet||] |  | ||||||
| 
 |  | ||||||
| stringToPre :: String -> Html () |  | ||||||
| stringToPre s = [$hamlet|%pre $s$|] id |  | ||||||
| 
 |  | ||||||
| pageLayout :: TemplateData -> Hamlet HledgerWebAppRoute |  | ||||||
| pageLayout td@TD{here=here, title=title, msg=msg, content=content} = [$hamlet| |  | ||||||
| !!! |  | ||||||
| %html |  | ||||||
|  %head |  | ||||||
|   %title $title$ |  | ||||||
|   %meta!http-equiv=Content-Type!content=$metacontent$ |  | ||||||
|   %link!rel=stylesheet!type=text/css!href=@stylesheet@!media=all |  | ||||||
|  %body |  | ||||||
|   ^navbar.td^ |  | ||||||
|   #messages $m$ |  | ||||||
|   ^addform'.here^ |  | ||||||
|   #content |  | ||||||
|    $content$ |  | ||||||
| |] |  | ||||||
|  where m = fromMaybe (string "") msg |  | ||||||
|        addform' JournalPage = addform |  | ||||||
|        addform' _           = nulltemplate |  | ||||||
|        stylesheet = StyleCss |  | ||||||
|        metacontent = "text/html; charset=utf-8" |  | ||||||
| 
 |  | ||||||
| navbar :: TemplateData -> Hamlet HledgerWebAppRoute |  | ||||||
| navbar td = [$hamlet| |  | ||||||
|  #navbar |  | ||||||
|   %a.toprightlink!href=$hledgerurl$ hledger.org |  | ||||||
|   \ $ |  | ||||||
|   %a.toprightlink!href=$manualurl$ manual |  | ||||||
|   \ $ |  | ||||||
|   ^navlinks.td^ |  | ||||||
|   ^searchform.td^ |  | ||||||
| |] |  | ||||||
| 
 |  | ||||||
| navlinks :: TemplateData -> Hamlet HledgerWebAppRoute |  | ||||||
| navlinks TD{here=here,a=a,p=p} = [$hamlet| |  | ||||||
|  #navlinks |  | ||||||
|   ^journallink^ $ |  | ||||||
|   (^editlink^) $ |  | ||||||
|   | ^balancelink^ $ |  | ||||||
|   | ^registerlink^ $ |  | ||||||
| |] |  | ||||||
|  where |  | ||||||
|   journallink = navlink here "journal" JournalPage |  | ||||||
|   editlink = navlink here "edit" EditPage |  | ||||||
|   registerlink = navlink here "register" RegisterPage |  | ||||||
|   balancelink = navlink here "balance" BalancePage |  | ||||||
|   navlink here s dest = [$hamlet|%a.$style$!href=@?u@ $s$|] |  | ||||||
|    where u = (dest, concat [(if null a then [] else [("a", a)]) |  | ||||||
|                            ,(if null p then [] else [("p", p)])]) |  | ||||||
|          style | here == dest = "navlinkcurrent" |  | ||||||
|                | otherwise    = "navlink" |  | ||||||
| 
 |  | ||||||
| searchform :: TemplateData -> Hamlet HledgerWebAppRoute |  | ||||||
| searchform TD{here=here,a=a,p=p} = [$hamlet| |  | ||||||
|  %form#searchform!method=GET |  | ||||||
|   ^resetlink^ $ |  | ||||||
|   %span!style=white-space:nowrap; |  | ||||||
|    filter by: $ |  | ||||||
|    %input!name=a!size=30!value=$a$ |  | ||||||
|    ^ahelp^ $ |  | ||||||
|    in period: $ |  | ||||||
|    %input!name=p!size=30!value=$p$ |  | ||||||
|    ^phelp^ $ |  | ||||||
|    %input!type=submit!value=filter |  | ||||||
| |] |  | ||||||
|  where |  | ||||||
|   ahelp = helplink "filter-patterns" "?" |  | ||||||
|   phelp = helplink "period-expressions" "?" |  | ||||||
|   resetlink |  | ||||||
|    | null a && null p = nulltemplate |  | ||||||
|    | otherwise        = [$hamlet|%span#resetlink!style=font-weight:bold; $ |  | ||||||
|                                   %a!href=@here@ stop filtering|] |  | ||||||
| 
 |  | ||||||
| helplink topic label = [$hamlet|%a!href=$u$ $label$|] |  | ||||||
|     where u = manualurl ++ if null topic then "" else '#':topic |  | ||||||
| 
 |  | ||||||
| editform :: TemplateData -> Hamlet HledgerWebAppRoute |  | ||||||
| editform TD{contentplain=t} = [$hamlet| |  | ||||||
|  %form!method=POST |  | ||||||
|   %table.form#editform!cellpadding=0!cellspacing=0!border=0 |  | ||||||
|    %tr.formheading |  | ||||||
|     %td!colspan=2 |  | ||||||
|      %span!style=float:right; ^formhelp^ |  | ||||||
|      %span#formheading Edit journal: |  | ||||||
|    %tr |  | ||||||
|     %td!colspan=2 |  | ||||||
|      %textarea!name=text!rows=30!cols=80 |  | ||||||
|       $t$ |  | ||||||
|    %tr#addbuttonrow |  | ||||||
|     %td |  | ||||||
|      %a!href=@JournalPage@ cancel |  | ||||||
|     %td!align=right |  | ||||||
|      %input!type=submit!value=$submitlabel$ |  | ||||||
|    %tr.helprow |  | ||||||
|     %td |  | ||||||
|     %td!align=right |  | ||||||
|      #help Are you sure ? All previous data will be replaced |  | ||||||
| |] |  | ||||||
|  where |  | ||||||
|   submitlabel = "save journal" |  | ||||||
|   formhelp = helplink "file-format" "file format help" |  | ||||||
| 
 |  | ||||||
| addform :: Hamlet HledgerWebAppRoute | addform :: Hamlet HledgerWebAppRoute | ||||||
| addform = [$hamlet| | addform = [$hamlet| | ||||||
|  %form!method=POST |  %form#addform!method=POST!style=display:none; | ||||||
|   %table.form#addform!cellpadding=0!cellspacing=0!border=0 |   %table.form!cellpadding=0!cellspacing=0!border=0 | ||||||
|    %tr.formheading |  | ||||||
|     %td!colspan=4 |  | ||||||
|      %span#formheading Add a transaction: |  | ||||||
|    %tr |    %tr | ||||||
|     %td!colspan=4 |     %td!colspan=4 | ||||||
|      %table!cellpadding=0!cellspacing=0!border=0 |      %table!cellpadding=0!cellspacing=0!border=0 | ||||||
| @ -486,21 +326,21 @@ addform = [$hamlet| | |||||||
|       %tr.helprow |       %tr.helprow | ||||||
|        %td |        %td | ||||||
|        %td |        %td | ||||||
|         #help $datehelp$ ^datehelplink^ $ |         .help $datehelp$ ^datehelplink^ $ | ||||||
|        %td |        %td | ||||||
|        %td |        %td | ||||||
|         #help $deschelp$ |         .help $deschelp$ | ||||||
|    ^transactionfields1^ |    ^transactionfields1^ | ||||||
|    ^transactionfields2^ |    ^transactionfields2^ | ||||||
|    %tr#addbuttonrow |    %tr#addbuttonrow | ||||||
|     %td!colspan=4 |     %td!colspan=4 | ||||||
|      %input!type=submit!value=$addlabel$ |      %input!type=hidden!name=add!value=1 | ||||||
|  |      %input!type=submit!name=submit!value="add transaction" | ||||||
| |] | |] | ||||||
|  where |  where | ||||||
|   datehelplink = helplink "dates" "..." |   datehelplink = helplink "dates" "..." | ||||||
|   datehelp = "eg: 7/20, 2010/1/1, " |   datehelp = "eg: 7/20, 2010/1/1, " | ||||||
|   deschelp = "eg: supermarket (optional)" |   deschelp = "eg: supermarket (optional)" | ||||||
|   addlabel = "add transaction" |  | ||||||
|   date = "today" |   date = "today" | ||||||
|   desc = "" |   desc = "" | ||||||
|   transactionfields1 = transactionfields 1 |   transactionfields1 = transactionfields 1 | ||||||
| @ -517,10 +357,10 @@ transactionfields n = [$hamlet| | |||||||
|  %tr.helprow |  %tr.helprow | ||||||
|   %td |   %td | ||||||
|   %td |   %td | ||||||
|    #help $accthelp$ |    .help $accthelp$ | ||||||
|   %td |   %td | ||||||
|   %td |   %td | ||||||
|    #help $amthelp$ |    .help $amthelp$ | ||||||
| |] | |] | ||||||
|  where |  where | ||||||
|   label | n == 1    = "To account" |   label | n == 1    = "To account" | ||||||
| @ -542,3 +382,255 @@ transactionfields n = [$hamlet| | |||||||
|   acctvar = numbered "accountname" |   acctvar = numbered "accountname" | ||||||
|   amtvar = numbered "amount" |   amtvar = numbered "amount" | ||||||
| 
 | 
 | ||||||
|  | editform :: TemplateData -> String -> Hamlet HledgerWebAppRoute | ||||||
|  | editform _ content = [$hamlet| | ||||||
|  |  %form#editform!method=POST!style=display:none; | ||||||
|  |   %table.form#editform!cellpadding=0!cellspacing=0!border=0 | ||||||
|  |    %tr | ||||||
|  |     %td!colspan=2 | ||||||
|  |      %textarea!name=text!rows=30!cols=80 | ||||||
|  |       $content$ | ||||||
|  |    %tr#addbuttonrow | ||||||
|  |     %td | ||||||
|  |      %span.help ^formathelp^ | ||||||
|  |     %td!align=right | ||||||
|  |      %span.help Are you sure ? Your journal will be overwritten. $ | ||||||
|  |      %input!type=hidden!name=edit!value=1 | ||||||
|  |      %input!type=submit!name=submit!value="save journal" | ||||||
|  |      \ or $ | ||||||
|  |      %a!href!onclick="return editformToggle()" cancel | ||||||
|  | |] | ||||||
|  |   where | ||||||
|  |     formathelp = helplink "file-format" "file format help" | ||||||
|  | 
 | ||||||
|  | ---------------------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | -- | A combined accounts and postings view, like hledger balance + hledger register. | ||||||
|  | getLedgerPage :: Handler HledgerWebApp RepHtml | ||||||
|  | getLedgerPage = do | ||||||
|  |   (a, p, opts, fspec, j, msg, here) <- getHandlerParameters | ||||||
|  |   -- in this view, balance report is filtered only by period, not account/description filters | ||||||
|  |   app <- getYesod | ||||||
|  |   t <- liftIO $ getCurrentLocalTime | ||||||
|  |   let args = appArgs app | ||||||
|  |       fspec' = optsToFilterSpec opts args t | ||||||
|  |       br = balanceReportAsHtml opts td $ balanceReport opts fspec' j | ||||||
|  |       rr = registerReportAsHtml opts td $ registerReport opts fspec j | ||||||
|  |       td = mktd{here=here, title="hledger", msg=msg, a=a, p=p} | ||||||
|  |   hamletToRepHtml $ pageLayout td [$hamlet| | ||||||
|  | %div.ledger | ||||||
|  |  %div.accounts!style=float:left;  ^br^ | ||||||
|  |  %div.register ^rr^ | ||||||
|  | |] | ||||||
|  | 
 | ||||||
|  | ---------------------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | -- | An accounts and balances view, like hledger balance. | ||||||
|  | getBalancePage :: Handler HledgerWebApp RepHtml | ||||||
|  | getBalancePage = do | ||||||
|  |   (a, p, opts, fspec, j, msg, here) <- getHandlerParameters | ||||||
|  |   let td = mktd{here=here, title="hledger", msg=msg, a=a, p=p} | ||||||
|  |   hamletToRepHtml $ pageLayout td $ balanceReportAsHtml opts td $ balanceReport opts fspec j | ||||||
|  | 
 | ||||||
|  | -- | Render a balance report as HTML. | ||||||
|  | balanceReportAsHtml :: [Opt] -> TemplateData -> BalanceReport -> Hamlet HledgerWebAppRoute | ||||||
|  | balanceReportAsHtml _ td (items,total) = [$hamlet| | ||||||
|  | %table.balancereport | ||||||
|  |  $forall items i | ||||||
|  |   ^itemAsHtml' i^ | ||||||
|  |  %tr.totalrule | ||||||
|  |   %td!colspan=2 | ||||||
|  |  %tr | ||||||
|  |   %td | ||||||
|  |   %td!align=right $mixedAmountAsHtml.total$ | ||||||
|  | |] | ||||||
|  |  where | ||||||
|  |    itemAsHtml' = itemAsHtml td | ||||||
|  |    itemAsHtml :: TemplateData -> BalanceReportItem -> Hamlet HledgerWebAppRoute | ||||||
|  |    itemAsHtml TD{a=a,p=p} (acct, adisplay, adepth, abal) = [$hamlet| | ||||||
|  |      %tr.item.$current$ | ||||||
|  |       %td.account | ||||||
|  |        $indent$ | ||||||
|  |        %a!href=$aurl$ $adisplay$ | ||||||
|  |       %td.balance!align=right $mixedAmountAsHtml.abal$ | ||||||
|  |      |] where | ||||||
|  |        current = if not (null a) && containsRegex a acct then "current" else "" | ||||||
|  |        indent = preEscapedString $ concat $ replicate (2 * adepth) " " | ||||||
|  |        aurl = printf "../ledger?a=^%s%s" acct p' :: String | ||||||
|  |        p' = if null p then "" else printf "&p=%s" p | ||||||
|  | 
 | ||||||
|  | ---------------------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | -- | A postings view, like hledger register. | ||||||
|  | getRegisterPage :: Handler HledgerWebApp RepHtml | ||||||
|  | getRegisterPage = do | ||||||
|  |   (a, p, opts, fspec, j, msg, here) <- getHandlerParameters | ||||||
|  |   let td = mktd{here=here, title="hledger", msg=msg, a=a, p=p} | ||||||
|  |   hamletToRepHtml $ pageLayout td $ registerReportAsHtml opts td $ registerReport opts fspec j | ||||||
|  | 
 | ||||||
|  | -- | Render a register report as HTML. | ||||||
|  | registerReportAsHtml :: [Opt] -> TemplateData -> RegisterReport -> Hamlet HledgerWebAppRoute | ||||||
|  | registerReportAsHtml _ td items = [$hamlet| | ||||||
|  | %table.registerreport | ||||||
|  |  $forall number.items i | ||||||
|  |   ^itemAsHtml' i^ | ||||||
|  | |] | ||||||
|  |  where | ||||||
|  |    number = zip [1..] | ||||||
|  |    itemAsHtml' = itemAsHtml td | ||||||
|  |    itemAsHtml :: TemplateData -> (Int, RegisterReportItem) -> Hamlet HledgerWebAppRoute | ||||||
|  |    itemAsHtml TD{p=p} (n, (ds, posting, b)) = [$hamlet| | ||||||
|  |      %tr.item.$evenodd$.$firstposting$ | ||||||
|  |       %td.date $date$ | ||||||
|  |       %td.description $desc$ | ||||||
|  |       %td.account | ||||||
|  |        %a!href=$aurl$ $acct$ | ||||||
|  |       %td.amount!align=right $mixedAmountAsHtml.pamount.posting$ | ||||||
|  |       %td.balance!align=right $mixedAmountAsHtml.b$ | ||||||
|  |      |] where | ||||||
|  |        evenodd = if even n then "even" else "odd" | ||||||
|  |        (firstposting, date, desc) = case ds of Just (da, de) -> ("firstposting", show da, de) | ||||||
|  |                                                Nothing -> ("", "", "") | ||||||
|  |        acct = paccount posting | ||||||
|  |        aurl = printf "../ledger?a=^%s%s" acct p' :: String | ||||||
|  |        p' = if null p then "" else printf "&p=%s" p | ||||||
|  | 
 | ||||||
|  | --mixedAmountAsHtml = intercalate ", " . lines . show | ||||||
|  | mixedAmountAsHtml = preEscapedString . intercalate "<br>" . lines . show | ||||||
|  | 
 | ||||||
|  | ---------------------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | -- | A standalone journal edit form page. | ||||||
|  | getEditPage :: Handler HledgerWebApp RepHtml | ||||||
|  | getEditPage = do | ||||||
|  |   (a, p, _, _, _, msg, here) <- getHandlerParameters | ||||||
|  |   -- reload journal's text without parsing, if changed | ||||||
|  |   j <- liftIO $ fromJust `fmap` getValue "hledger" "journal" | ||||||
|  |   changed <- liftIO $ journalFileIsNewer j | ||||||
|  |   s <- liftIO $ if changed then readFile (filepath j) else return (jtext j) -- XXX readFile may throw an error | ||||||
|  |   let td = mktd{here=here, title="hledger", msg=msg, a=a, p=p} | ||||||
|  |   hamletToRepHtml $ pageLayout td $ editform td s | ||||||
|  | 
 | ||||||
|  | ---------------------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | -- | Gather all the stuff we want for a typical hledger web request handler. | ||||||
|  | getHandlerParameters :: Handler HledgerWebApp | ||||||
|  |                        (String, String, [Opt], FilterSpec, Journal, Maybe (Html ()), HledgerWebAppRoute) | ||||||
|  | getHandlerParameters = do | ||||||
|  |   Just here <- getCurrentRoute | ||||||
|  |   (a, p, opts, fspec) <- getReportParameters | ||||||
|  |   (j, err) <- getLatestJournal opts | ||||||
|  |   msg <- getMessage' err | ||||||
|  |   return (a, p, opts, fspec, j, msg, here) | ||||||
|  |     where | ||||||
|  |       -- | Get current report parameters for this request. | ||||||
|  |       getReportParameters :: Handler HledgerWebApp (String, String, [Opt], FilterSpec) | ||||||
|  |       getReportParameters = do | ||||||
|  |           app <- getYesod | ||||||
|  |           t <- liftIO $ getCurrentLocalTime | ||||||
|  |           a <- fromMaybe "" <$> lookupGetParam "a" | ||||||
|  |           p <- fromMaybe "" <$> lookupGetParam "p" | ||||||
|  |           let opts = appOpts app ++ [Period p] | ||||||
|  |               args = appArgs app ++ [a] | ||||||
|  |               fspec = optsToFilterSpec opts args t | ||||||
|  |           return (a, p, opts, fspec) | ||||||
|  | 
 | ||||||
|  |       -- | Update our copy of the journal if the file changed. If there is an | ||||||
|  |       -- error while reloading, keep the old one and return the error, and set a | ||||||
|  |       -- ui message. | ||||||
|  |       getLatestJournal :: [Opt] -> Handler HledgerWebApp (Journal, Maybe String) | ||||||
|  |       getLatestJournal opts = do | ||||||
|  |         j <- liftIO $ fromJust `fmap` getValue "hledger" "journal" | ||||||
|  |         (jE, changed) <- liftIO $ journalReloadIfChanged opts j | ||||||
|  |         if not changed | ||||||
|  |          then return (j,Nothing) | ||||||
|  |          else case jE of | ||||||
|  |                 Right j' -> do liftIO $ putValue "hledger" "journal" j' | ||||||
|  |                                return (j',Nothing) | ||||||
|  |                 Left e  -> do setMessage $ string "error while reading" {- ++ ": " ++ e-} | ||||||
|  |                               return (j, Just e) | ||||||
|  | 
 | ||||||
|  |       -- | Helper to work around a yesod feature (can't set and get a message in the same request.) | ||||||
|  |       getMessage' :: Maybe String -> Handler HledgerWebApp (Maybe (Html ())) | ||||||
|  |       getMessage' newmsgstr = do | ||||||
|  |         oldmsg <- getMessage | ||||||
|  |         return $ maybe oldmsg (Just . string) newmsgstr | ||||||
|  | 
 | ||||||
|  | pageLayout :: TemplateData -> Hamlet HledgerWebAppRoute -> Hamlet HledgerWebAppRoute | ||||||
|  | pageLayout td@TD{title=title, msg=msg} content = [$hamlet| | ||||||
|  | !!! | ||||||
|  | %html | ||||||
|  |  %head | ||||||
|  |   %title $title$ | ||||||
|  |   %meta!http-equiv=Content-Type!content=$metacontent$ | ||||||
|  |   %link!rel=stylesheet!type=text/css!href=@StyleCss@!media=all | ||||||
|  |  %body | ||||||
|  |   ^navbar.td^ | ||||||
|  |   #messages $m$ | ||||||
|  |   #content | ||||||
|  |    ^content^ | ||||||
|  | |] | ||||||
|  |  where m = fromMaybe (string "") msg | ||||||
|  |        metacontent = "text/html; charset=utf-8" | ||||||
|  | 
 | ||||||
|  | navbar :: TemplateData -> Hamlet HledgerWebAppRoute | ||||||
|  | navbar td = [$hamlet| | ||||||
|  |  #navbar | ||||||
|  |   %a.toprightlink!href=$hledgerurl$ hledger $version$ | ||||||
|  |   \ $ | ||||||
|  |   %a.toprightlink!href=$manualurl$ manual | ||||||
|  |   \ $ | ||||||
|  |   ^navlinks.td^ | ||||||
|  |   ^filterform.td^ | ||||||
|  | |] | ||||||
|  | 
 | ||||||
|  | navlinks :: TemplateData -> Hamlet HledgerWebAppRoute | ||||||
|  | navlinks td = [$hamlet| | ||||||
|  |  #navlinks | ||||||
|  |   ^journallink^ $ | ||||||
|  |   | ^ledgerlink^ $ | ||||||
|  | |] | ||||||
|  |  where | ||||||
|  |   journallink  = navlink td "journal" JournalPage | ||||||
|  |   ledgerlink   = navlink td "ledger" LedgerPage | ||||||
|  |   -- | ^balancelink^ $ | ||||||
|  |   -- | ^registerlink^ $ | ||||||
|  |   -- balancelink  = navlink td "balance" BalancePage | ||||||
|  |   -- registerlink = navlink td "register" RegisterPage | ||||||
|  | 
 | ||||||
|  | navlink :: TemplateData -> String -> HledgerWebAppRoute -> Hamlet HledgerWebAppRoute | ||||||
|  | navlink TD{here=here,a=a,p=p} s dest = [$hamlet|%a.$style$!href=@?u@ $s$|] | ||||||
|  |  where u = (dest, concat [(if null a then [] else [("a", a)]) | ||||||
|  |                          ,(if null p then [] else [("p", p)])]) | ||||||
|  |        style | dest == here = "navlinkcurrent" | ||||||
|  |              | otherwise    = "navlink" | ||||||
|  | 
 | ||||||
|  | filterform :: TemplateData -> Hamlet HledgerWebAppRoute | ||||||
|  | filterform TD{here=here,a=a,p=p} = [$hamlet| | ||||||
|  |  %form#filterform.$filtering$!method=GET | ||||||
|  |   %span!style=white-space:nowrap; | ||||||
|  |    ^filterformlabel^ $ | ||||||
|  |    %input!name=a!size=30!value=$a$ | ||||||
|  |    ^ahelp^ $ | ||||||
|  |    in period: $ | ||||||
|  |    %input!name=p!size=30!value=$p$ | ||||||
|  |    ^phelp^ $ | ||||||
|  |    %input!type=submit!value=filter | ||||||
|  | |] | ||||||
|  |  where | ||||||
|  |   ahelp = helplink "filter-patterns" "?" | ||||||
|  |   phelp = helplink "period-expressions" "?" | ||||||
|  |   (filtering, filterformlabel) | ||||||
|  |    | null a && null p = ("", [$hamlet|filter by: $|]) | ||||||
|  |    | otherwise        = ("filtering", [$hamlet| | ||||||
|  | %a#stopfilterlink!href=@here@ stop filtering | ||||||
|  | \ $ | ||||||
|  | by $ | ||||||
|  | |]) | ||||||
|  | 
 | ||||||
|  | helplink :: String -> String -> Hamlet HledgerWebAppRoute | ||||||
|  | helplink topic label = [$hamlet|%a!href=$u$!target=hledgerhelp $label$|] | ||||||
|  |     where u = manualurl ++ if null topic then "" else '#':topic | ||||||
|  | 
 | ||||||
|  | nulltemplate = [$hamlet||] | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -1,12 +1,19 @@ | |||||||
| /* hledger web ui stylesheet */ | /* hledger web ui stylesheet */ | ||||||
| 
 | 
 | ||||||
| body { font-family: "helvetica","arial", "sans serif"; margin:0; } | /* font families */ | ||||||
| #navbar { background-color:#eeeeee; border-bottom:2px solid #dddddd; padding:4px 4px 6px 4px; } | body { font-family:helvetica,arial,"sans serif"; } | ||||||
|  | /* pre { font-family:monospace,courier,"courier new"; } */ | ||||||
|  | #editform textarea { font-family:courier,"courier new",monospace; } | ||||||
|  | 
 | ||||||
|  | body { margin:0; } | ||||||
|  | #navbar { /* background-color:#eeeeee; */ /* border-bottom:2px solid #dddddd; */ padding:4px 4px 6px 4px; } | ||||||
| #navlinks { display:inline; } | #navlinks { display:inline; } | ||||||
| .navlink { } | .navlink { } | ||||||
| .navlinkcurrent { font-weight:bold; } | .navlinkcurrent { font-weight:bold; } | ||||||
| #searchform { font-size:small; display:inline; margin-left:1em; } | .nav2 { font-size:small; } | ||||||
| #resetlink { font-size:small; } | #filterform { font-size:small; display:inline; margin-left:1em; } | ||||||
|  | .filtering { background-color:#eee; font-weight:bold; } | ||||||
|  | #stopfilterlink { font-size:small; } | ||||||
| .toprightlink { font-size:small; margin-left:1em; float:right; } | .toprightlink { font-size:small; margin-left:1em; float:right; } | ||||||
| #messages { color:red; background-color:#ffeeee; margin:0.5em;} | #messages { color:red; background-color:#ffeeee; margin:0.5em;} | ||||||
| .form { margin:1em; font-size:small; } | .form { margin:1em; font-size:small; } | ||||||
| @ -16,25 +23,50 @@ body { font-family: "helvetica","arial", "sans serif"; margin:0; } | |||||||
| #addform #postingrow { } | #addform #postingrow { } | ||||||
| #addform #addbuttonrow { text-align:right; } | #addform #addbuttonrow { text-align:right; } | ||||||
| #editform { width:95%; } | #editform { width:95%; } | ||||||
| #editform textarea { background-color:#eeeeee; font-family:monospace; font-size:medium; width:100%; } | #editform textarea { /* background-color:#eeeeee; */ width:100%; } | ||||||
| #content { margin:1em; } | #content { margin:1em; } | ||||||
| .formheading td { padding-bottom:8px; } | .formheading td { padding-bottom:8px; } | ||||||
| #formheading { font-size:medium; font-weight:bold; } | #formheading { font-size:medium; font-weight:bold; } | ||||||
| .helprow td { padding-bottom:8px; } | .helprow td { padding-bottom:8px; } | ||||||
| #help {font-style: italic; font-size:smaller; } | .help {font-style: italic; font-size:smaller; } | ||||||
| 
 | 
 | ||||||
| /* for -fweb610 */ | /* for -fweb610 */ | ||||||
| #hledgerorglink, #helplink { float:right; margin-left:1em; } | /* #hledgerorglink, #helplink { float:right; margin-left:1em; } */ | ||||||
| 
 | 
 | ||||||
| /* .balancereport { font-size:small; } */ | .current { font-weight:bold; background-color:#eee; } | ||||||
|  | .description { padding-left:1em; } | ||||||
|  | .account { white-space:nowrap; padding-left:1em; } | ||||||
|  | .amount { white-space:nowrap; padding-left:1em; } | ||||||
|  | .balance { white-space:nowrap; padding-left:1em; } | ||||||
|  |  /* don't let fields get too small in emptyish reports */ | ||||||
|  | .description { width:4em; } | ||||||
|  | .account, .amount, .balance { width:2em; } | ||||||
|  | /* .odd { background-color:#e8e8e8; } */ | ||||||
|  | /* .even { background-color:#e8f8e8; } */ | ||||||
|  | /* .even { background-color:#f0fff0; } */ | ||||||
|  | 
 | ||||||
|  | .journalreport { font-size:small; } | ||||||
|  | table.journalreport { margin-top:1em; } | ||||||
|  | .journalreport td { border-top:thin solid #ddd; } | ||||||
|  | .journalreport pre { margin-top:0; } | ||||||
|  | 
 | ||||||
|  | .ledger .accounts {padding-right:1em; margin-right:1em; border-right:thin solid #ddd;} | ||||||
|  | .ledger .register {} | ||||||
|  | 
 | ||||||
|  | .balancereport { font-size:small; } | ||||||
| .balancereport tr { vertical-align:top; } | .balancereport tr { vertical-align:top; } | ||||||
|  | table.balancereport { border-spacing:0; } | ||||||
|  | .ledger .balancereport td { padding:0; } | ||||||
| /* .itemrule td { border-top:thin solid #ddd; } */ | /* .itemrule td { border-top:thin solid #ddd; } */ | ||||||
| .totalrule td { border-top:thin solid black; } | .totalrule td { border-top:thin solid black; } | ||||||
| 
 | 
 | ||||||
|  | table.registerreport { border-spacing:0; } | ||||||
| .registerreport { font-size:small; } | .registerreport { font-size:small; } | ||||||
| .registerreport tr { vertical-align:top; } | .registerreport tr { vertical-align:top; } | ||||||
|  | .registerreport td { padding-bottom:0.2em; } | ||||||
|  | /* .registerreport td { margin-left:0em; margin-right:0; } */ | ||||||
| .registerreport .date { white-space:nowrap; } | .registerreport .date { white-space:nowrap; } | ||||||
| /* .registerreport .description { font-size:small; } */ | /* .registerreport .description { font-size:small; } */ | ||||||
| .registerreport .account { white-space:nowrap; } | /* .firstposting { background-color:#eee; } */ | ||||||
| .registerreport .amount { white-space:nowrap; } | .registerreport .even { background-color:#f0f0f0; } | ||||||
| .registerreport .balance { white-space:nowrap; } | 
 | ||||||
|  | |||||||
| @ -104,7 +104,7 @@ ensureJournalFile f = do | |||||||
| emptyJournal :: IO String | emptyJournal :: IO String | ||||||
| emptyJournal = do | emptyJournal = do | ||||||
|   d <- getCurrentDay |   d <- getCurrentDay | ||||||
|   return $ printf "; journal created %s; see http://hledger.org/MANUAL.html#journal-file\n\n" (show d) |   return $ printf "; journal created %s by hledger\n\n" (show d) | ||||||
| 
 | 
 | ||||||
| -- | Read a Journal from this string, using the specified data format or | -- | Read a Journal from this string, using the specified data format or | ||||||
| -- trying all known formats, or give an error string. | -- trying all known formats, or give an error string. | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user