packages feed

hledger-web 0.27 → 1.0

raw patch · 21 files changed

+1440/−1153 lines, 21 filesdep +ghc-primdep +megaparsecdep +mtldep ~hledgerdep ~hledger-libdep ~hledger-webPVP ok

version bump matches the API change (PVP)

Dependencies added: ghc-prim, megaparsec, mtl

Dependency ranges changed: hledger, hledger-lib, hledger-web, text

API changes (from Hackage documentation)

- Hledger.Web.Main: main :: IO ()
+ Hledger.Web.Main: hledgerWebMain :: IO ()
- Handler.RegisterR: registerChartHtml :: [(Commodity, (String, [TransactionsReportItem]))] -> HtmlUrl AppRoute
+ Handler.RegisterR: registerChartHtml :: [(CommoditySymbol, (String, [TransactionsReportItem]))] -> HtmlUrl AppRoute

Files

CHANGES view
@@ -1,7 +1,54 @@ User-visible changes in hledger-web. See also hledger's change log. -0.27 (unreleased)++# 1.0 (2016/10/26)++## ui++-   show the sidebar by default (#310)++-   fix the add link's tooltip++-   when the add form opens, focus the first field (#338)++-   leave the add form's date field blank, avoiding a problem with tab clearing it (#322)++-   use transaction id instead of date in transaction urls (#308) (Thomas R. Koll)++-   after following a link to a transaction, highlight it (Thomas R. Koll)++-   misc. HTML/CSS/file cleanups/fixes (Thomas R. Koll)++-   added .btn-default for consistent button styling across browsers (#418) (Dominik Süß)++## misc++-   startup is more robust (#226).++    Now we exit if something is already using the specified port,+    and we don't open a browser page before the app is ready.++-   termination is more robust, avoiding stray background threads.++    We terminate the server thread more carefully on exit, eg on control-C in GHCI.++-   more robust register dates and filtering in some situations (see hledger-ui notes)++-   reloading the journal preserves options, arguments in effect (#314).++    The initial query specified by command line arguments is now preserved+    when the journal is reloaded. This does not appear in the web UI, it's+    like an invisible extra filter.++-   show a proper not found page on 404++-   document the special \`inacct:\` query (#390)+++++0.27 (2015/10/30)  - Fix keyboard shortcut for adding a transaction (Carlos Lopez-Camey) 
Foundation.hs view
@@ -124,12 +124,11 @@     --     hamletToRepHtml $(hamletFile "templates/default-layout-wrapper.hamlet")          pc <- widgetToPageContent $ do-            $(widgetFile "normalize")             addStylesheet $ StaticR css_bootstrap_min_css              -- load these things early, in HEAD:             toWidgetHead [hamlet|-                          <script type="text/javascript" src="@{StaticR js_jquery_min_js}"></script>-                          <script type="text/javascript" src="@{StaticR js_typeahead_bundle_min_js}"></script>+                          <script type="text/javascript" src="@{StaticR js_jquery_min_js}">+                          <script type="text/javascript" src="@{StaticR js_typeahead_bundle_min_js}">                          |]             addScript $ StaticR js_bootstrap_min_js             -- addScript $ StaticR js_typeahead_bundle_min_js@@ -216,8 +215,8 @@ -- | Make a ViewData using the given date and request parameters, and defaults elsewhere. viewdataWithDateAndParams :: Day -> String -> String -> String -> ViewData viewdataWithDateAndParams d q a p =-    let (querymatcher,queryopts) = parseQuery d q-        (acctsmatcher,acctsopts) = parseQuery d a+    let (querymatcher,queryopts) = parseQuery d (pack q)+        (acctsmatcher,acctsopts) = parseQuery d (pack a)     in VD {            opts         = defwebopts           ,j            = nulljournal@@ -230,57 +229,62 @@           ,am           = acctsmatcher           ,aopts        = acctsopts           ,showpostings = p == "1"-          ,showsidebar  = False+          ,showsidebar  = True           }  -- | Gather data used by handlers and templates in the current request. getViewData :: Handler ViewData getViewData = do-  app        <- getYesod-  let opts@WebOpts{cliopts_=copts@CliOpts{reportopts_=ropts}} = appOpts app-  (j, merr)  <- getCurrentJournal app copts{reportopts_=ropts{no_elide_=True}}-  lastmsg    <- getLastMessage-  let msg = maybe lastmsg (Just . toHtml) merr-  Just here  <- getCurrentRoute-  today      <- liftIO getCurrentDay-  q          <- getParameterOrNull "q"-  a          <- getParameterOrNull "a"-  p          <- getParameterOrNull "p"--  -- a "sidebar" query parameter overrides the "showsidebar" cookie-  sidebarparam <- lookupGetParam (pack "sidebar")-  cookies <- reqCookies <$> getRequest-  let sidebarcookie = lookup "showsidebar" cookies-  let showsidebar = maybe (sidebarcookie == Just "1") (=="1") sidebarparam+  mhere      <- getCurrentRoute+  case mhere of+    Nothing -> return nullviewdata+    Just here -> do+      app        <- getYesod+      let opts@WebOpts{cliopts_=copts@CliOpts{reportopts_=ropts}} = appOpts app+      today      <- liftIO getCurrentDay+      (j, merr)  <- getCurrentJournal app copts{reportopts_=ropts{no_elide_=True}} today+      lastmsg    <- getLastMessage+      let msg = maybe lastmsg (Just . toHtml) merr+      q          <- getParameterOrNull "q"+      a          <- getParameterOrNull "a"+      p          <- getParameterOrNull "p"+      -- sidebar visibility: show it, unless there is a showsidebar cookie+      -- set to "0", or a ?sidebar=0 query parameter.+      msidebarparam <- lookupGetParam "sidebar"+      msidebarcookie <- reqCookies <$> getRequest >>= return . lookup "showsidebar"+      let showsidebar = maybe (msidebarcookie /= Just "0") (/="0") msidebarparam -  return (viewdataWithDateAndParams today q a p){-               opts=opts-              ,msg=msg-              ,here=here-              ,today=today-              ,j=j-              ,showsidebar=showsidebar-              }-    where-      -- | 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.-      getCurrentJournal :: App -> CliOpts -> Handler (Journal, Maybe String)-      getCurrentJournal app opts = do-        -- XXX put this inside atomicModifyIORef' for thread safety-        j <- liftIO $ readIORef $ appJournal app-        (jE, changed) <- liftIO $ journalReloadIfChanged opts j-        if not changed-         then return (j,Nothing)-         else case jE of-                Right j' -> do liftIO $ writeIORef (appJournal app) j'-                               return (j',Nothing)-                Left e   -> do setMessage $ "error while reading" {- ++ ": " ++ e-}-                               return (j, Just e)+      return (viewdataWithDateAndParams today q a p){+                   opts=opts+                  ,msg=msg+                  ,here=here+                  ,today=today+                  ,j=j+                  ,showsidebar=showsidebar+                  }+        where+          -- | 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.+          getCurrentJournal :: App -> CliOpts -> Day -> Handler (Journal, Maybe String)+          getCurrentJournal app opts d = do+            -- XXX put this inside atomicModifyIORef' for thread safety+            j <- liftIO $ readIORef $ appJournal app+            (ej, changed) <- liftIO $ journalReloadIfChanged opts d j+            -- re-apply any initial filter specified at startup+            let initq = queryFromOpts d $ reportopts_ opts+                ej' = filterJournalTransactions initq <$> ej+            if not changed+             then return (j,Nothing)+             else case ej' of+                    Right j' -> do liftIO $ writeIORef (appJournal app) j'+                                   return (j',Nothing)+                    Left e   -> do setMessage $ "error while reading" {- ++ ": " ++ e-}+                                   return (j, Just e) -      -- | Get the named request parameter, or the empty string if not present.-      getParameterOrNull :: String -> Handler String-      getParameterOrNull p = unpack `fmap` fromMaybe "" <$> lookupGetParam (pack p)+          -- | Get the named request parameter, or the empty string if not present.+          getParameterOrNull :: String -> Handler String+          getParameterOrNull p = unpack `fmap` fromMaybe "" <$> lookupGetParam (pack p)  -- | Get the message that was set by the last request, in a -- referentially transparent manner (allowing multiple reads).@@ -333,24 +337,31 @@    }); -<form#addform method=POST style="position:relative;">-  <table.form style="width:100%; white-space:nowrap;">-   <tr>-    <td colspan=4>-     <table style="width:100%;">-      <tr#descriptionrow>-       <td>-        <input #date        .typeahead .form-control .input-lg type=text size=15 name=date placeholder="Date" value=#{defdate}>-       <td>-        <input #description .typeahead .form-control .input-lg type=text size=40 name=description placeholder="Description">-   $forall n <- postingnums+<form#addform method=POST .form>+ <div .form-group>+  <div .row>+   <div .col-md-2 .col-xs-6 .col-sm-6>+    <input #date .typeahead .form-control .input-lg type=text size=15 name=date placeholder="Date" value="#{defdate}">+   <div .col-md-10 .col-xs-6 .col-sm-6>+    <input #description .typeahead .form-control .input-lg type=text size=40 name=description placeholder="Description">+ <div .account-postings>+  $forall n <- postingnums     ^{postingfields vd n}-  <span style="padding-left:2em;">-   <span .small>-     Tab in last field for <a .small href="#" onclick="addformAddPosting(); return false;">more</a> (or ctrl +, ctrl -)+ <div .col-md-8 .col-xs-8 .col-sm-8>+ <div .col-md-4 .col-xs-4 .col-sm-4>+  <button type=submit .btn .btn-default .btn-lg name=submit>add+ $if length filepaths > 1+  <br>+  <span class="input-lg">to:+   ^{journalselect filepaths}+ <span style="padding-left:2em;">+  <span .small>+    Enter a value in the last field for+    <a href="#" onclick="addformAddPosting(); return false;">more+    (or ctrl +, ctrl -) |]  where-  defdate = "today" :: String+  defdate = "" :: String -- #322 don't set a default, typeahead(?) clears it on tab. See also hledger.js   dates = ["today","yesterday","tomorrow"] :: [String]   descriptions = sort $ nub $ map tdescription $ jtxns j   accts = sort $ journalAccountNamesUsed j@@ -358,36 +369,21 @@   listToJsonValueObjArrayStr as  = preEscapedString $ escapeJSSpecialChars $ encode $ JSArray $ map (\a -> JSObject $ toJSObject [("value", showJSON a)]) as   numpostings = 4   postingnums = [1..numpostings]+  filepaths = map fst $ jfiles j   postingfields :: ViewData -> Int -> HtmlUrl AppRoute   postingfields _ n = [hamlet|-<tr .posting>- <td style="padding-left:2em;">-  <input ##{acctvar} .account-input .typeahead .form-control .input-lg style="width:100%;" type=text name=#{acctvar} placeholder="#{acctph}">- ^{amtfieldorsubmitbtn}+<div .form-group .row .account-group ##{grpvar}>+ <div .col-md-8 .col-xs-8 .col-sm-8>+  <input ##{acctvar} .account-input .typeahead .form-control .input-lg type=text name=#{acctvar} placeholder="#{acctph}">+ <div .col-md-4 .col-xs-4 .col-sm-4>+  <input ##{amtvar} .amount-input .form-control .input-lg type=text name=#{amtvar} placeholder="#{amtph}"> |]    where-    islast = n == numpostings     acctvar = "account" ++ show n     acctph = "Account " ++ show n-    amtfieldorsubmitbtn-       | not islast = [hamlet|-          <td>-           <input ##{amtvar} .amount-input .form-control .input-lg type=text size=10 name=#{amtvar} placeholder="#{amtph}">-         |]-       | otherwise = [hamlet|-          <td #addbtncell style="text-align:right;">-           <button type=submit .btn .btn-lg name=submit>add-           $if length filepaths > 1-            <br>-            <span class="input-lg">to:-            ^{journalselect filepaths}-         |]-       where-        amtvar = "amount" ++ show n-        amtph = "Amount " ++ show n-        filepaths = map fst $ files j--           -- <button .btn style="font-size:18px;" type=submit title="Add this transaction">Add+    amtvar = "amount" ++ show n+    amtph = "Amount " ++ show n+    grpvar = "grp" ++ show n  journalselect :: [FilePath] -> HtmlUrl AppRoute journalselect journalfilepaths = [hamlet|
Handler/AddForm.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP, FlexibleContexts, OverloadedStrings, QuasiQuotes, RecordWildCards #-}+{-# LANGUAGE CPP, FlexibleContexts, OverloadedStrings, QuasiQuotes, RecordWildCards, TypeFamilies #-} -- | Add form data & handler. (The layout and js are defined in -- Foundation so that the add form can be in the default layout for -- all views.)@@ -10,13 +10,14 @@ #if !MIN_VERSION_base(4,8,0) import Control.Applicative #endif+import Control.Monad.State.Strict (evalStateT) import Data.Either (lefts,rights) import Data.List (sort) import qualified Data.List as L (head) -- qualified keeps dev & prod builds warning-free import Data.Text (append, pack, unpack) import qualified Data.Text as T import Data.Time.Calendar-import Text.Parsec (digit, eof, many1, string, runParser)+import Text.Megaparsec (digitChar, eof, some, string, runParser, ParseError, Dec)  import Hledger.Utils import Hledger.Data hiding (num)@@ -43,7 +44,6 @@                     $forall e<-errs                      \#{e}<br>                    |]-                                   -- 1. process the fixed fields with yesod-form    VD{..} <- getViewData@@ -55,7 +55,7 @@        validateDate :: Text -> Handler (Either FormMessage Day)       validateDate s = return $-        case fixSmartDateStrEither' today $ strip $ unpack s of+        case fixSmartDateStrEither' today $ T.pack $ strip $ unpack s of           Right d  -> Right d           Left _   -> Left $ MsgInvalidEntry $ pack "could not parse date \"" `append` s `append` pack "\":" -- ++ show e) @@ -83,11 +83,11 @@       let numberedParams s =             reverse $ dropWhile (T.null . snd) $ reverse $ sort             [ (n,v) | (k,v) <- params-                    , let en = parsewith (paramnamep s) $ T.unpack k+                    , let en = parsewith (paramnamep s) k :: Either (ParseError Char Dec) Int                     , isRight en                     , let Right n = en                     ]-            where paramnamep s = do {string s; n <- many1 digit; eof; return (read n :: Int)}+            where paramnamep s = do {string s; n <- some digitChar; eof; return (read n :: Int)}           acctparams = numberedParams "account"           amtparams  = numberedParams "amount"           num = length acctparams@@ -95,8 +95,8 @@                     | map fst acctparams == [1..num] &&                       map fst amtparams `elem` [[1..num], [1..num-1]] = []                     | otherwise = ["the posting parameters are malformed"]-          eaccts = map (parsewith (accountnamep <* eof) . strip . T.unpack . snd) acctparams-          eamts  = map (runParser (amountp <* eof) nullctx "" . strip . T.unpack . snd) amtparams+          eaccts = map (runParser (accountnamep <* eof) "" . textstrip  . snd) acctparams+          eamts  = map (runParser (evalStateT (amountp <* eof) mempty) "" . textstrip . snd) amtparams           (accts, acctErrs) = (rights eaccts, map show $ lefts eaccts)           (amts', amtErrs)  = (rights eamts, map show $ lefts eamts)           amts | length amts' == num = amts'@@ -106,7 +106,7 @@                | otherwise = either (\e -> Left [L.head $ lines e]) Right                               (balanceTransaction Nothing $ nulltransaction {                                   tdate=date-                                 ,tdescription=desc+                                 ,tdescription=T.pack desc                                  ,tpostings=[nullposting{paccount=acct, pamount=Mixed [amt]} | (acct,amt) <- zip accts amts]                                  })       case etxn of
Handler/Common.hs view
@@ -6,8 +6,8 @@  import Import -import Data.List-import Data.Text(pack)+-- import Data.Text (Text)+import qualified Data.Text as T import Data.Time.Calendar import System.FilePath (takeFileName) #if BLAZE_HTML_0_4@@ -33,55 +33,49 @@   defaultLayout $ do       setTitle $ toHtml $ title ++ " - hledger-web"       toWidget [hamlet|-        <div#content>-         $if showsidebar vd-          <div#sidebar>-           <div#sidebar-spacer>-           <div#sidebar-body>-            ^{sidebar vd}-         $else-          <div#sidebar style="display:none;">-           <div#sidebar-spacer>-           <div#sidebar-body>-         <div#main>-          ^{topbar vd}-          <div#maincontent>-           ^{searchform vd}-           ^{content}+         ^{topbar vd}+         ^{sidebar vd}+         <div #main-content .col-xs-12 .#{showmd} .#{showsm}>+          ^{searchform vd}+          ^{content}       |]+  where+    showmd = if showsidebar vd then "col-md-8" else "col-md-12" :: String+    showsm = if showsidebar vd then "col-sm-8" else "col-sm-12" :: String  -- | Global toolbar/heading area. topbar :: ViewData -> HtmlUrl AppRoute topbar VD{..} = [hamlet|-<nav class="navbar" role="navigation">- <div#topbar>-  <h1>#{title}+<div#spacer .#{showsm} .#{showmd} .col-xs-2>+ <h1>+  <button .visible-xs .btn .btn-default type="button" data-toggle="offcanvas">+   <span .glyphicon .glyphicon-align-left .tgl-icon>+<div#topbar .col-md-8 .col-sm-8 .col-xs-10>+ <h1>#{title}+ |]   where     title = takeFileName $ journalFilePath j+    showmd = if showsidebar then "col-md-4" else "col-any-0" :: String+    showsm = if showsidebar then "col-sm-4" else "" :: String  -- | The sidebar used on most views. sidebar :: ViewData -> HtmlUrl AppRoute sidebar vd@VD{..} =  [hamlet|- <a href=@{JournalR} title="Go back to top">-  hledger-web-- <p>- <!--- <a#sidebartogglebtn role="button" style="cursor:pointer;" onclick="sidebarToggle()" title="Show/hide sidebar">-  <span class="glyphicon glyphicon-expand"></span>- -->- <br>- <div#sidebar-content>-  <p style="margin-top:1em;">-   <a href=@{JournalR} .#{journalcurrent} title="Show general journal entries, most recent first" style="white-space:nowrap;">Journal-  <div#accounts style="margin-top:1em;">+ <div #sidebar-menu .#{showmd} .#{showsm} .sidebar-offcanvas>+  <table .main-menu .table>+   <tr .#{journalcurrent}>+    <td .top .acct>+     <a href=@{JournalR} .#{journalcurrent} title="Show general journal entries, most recent first">Journal+    <td .top>    ^{accounts} |]  where-  journalcurrent = if here == JournalR then "current" else "" :: String+  journalcurrent = if here == JournalR then "inacct" else "" :: String   accounts = balanceReportAsHtml opts vd $ balanceReport (reportopts_ $ cliopts_ opts){empty_=True} am j+  showmd = if showsidebar then "col-md-4" else "col-any-0" :: String+  showsm = if showsidebar then "col-sm-4" else "" :: String  -- -- | Navigation link, preserving parameters and possibly highlighted. -- navlink :: ViewData -> String -> AppRoute -> String -> HtmlUrl AppRoute@@ -104,17 +98,18 @@ -- | Search form for entering custom queries to filter journal data. searchform :: ViewData -> HtmlUrl AppRoute searchform VD{..} = [hamlet|-<div#searchformdiv>- <form#searchform.form method=GET>-  <table width="100%">-   <tr>-    <td width="99%" style="position:relative;">-     $if filtering-      <a role=button .btn .close style="position:absolute; right:0; padding-right:.1em; padding-left:.1em; margin-right:.1em; margin-left:.1em; font-size:24px;" href="@{here}" title="Clear search terms">&times;-     <input .form-control style="font-size:18px; padding-bottom:2px;" name=q value=#{q} title="Enter hledger search patterns to filter the data below">-    <td width="1%" style="white-space:nowrap;">-     <button .btn style="font-size:18px;" type=submit title="Apply search terms">Search-     <button .btn style="font-size:18px;" type=button data-toggle="modal" data-target="#helpmodal" title="Show search and general help">?+<div#searchformdiv .row>+ <form#searchform .form-inline method=GET>+  <div .form-group .col-md-12 .col-sm-12 .col-xs-12>+    <div #searchbar .input-group>+     <input .form-control name=q value=#{q} title="Enter hledger search patterns to filter the data below" placeholder="Search">+     <div .input-group-btn>+      $if filtering+       <a href=@{here} .btn .btn-default title="Clear search terms">+        <span .glyphicon .glyphicon-remove-circle>+      <button .btn .btn-default type=submit title="Apply search terms">+       <span .glyphicon .glyphicon-search>+      <button .btn .btn-default type=button data-toggle="modal" data-target="#helpmodal" title="Show search and general help">? |]  where   filtering = not $ null q@@ -138,7 +133,7 @@ --   <tr#addbuttonrow> --    <td> --     <span.help>^{formathelp}---    <td align=right>+--    <td> --     <span.help> --      Are you sure ? This will overwrite the journal. # --     <input type=hidden name=action value=edit>@@ -183,55 +178,46 @@ balanceReportAsHtml :: WebOpts -> ViewData -> BalanceReport -> HtmlUrl AppRoute balanceReportAsHtml _ vd@VD{..} (items',total) =  [hamlet|- <table.balancereport>-  <tr>-   <td>Account-   <td style="padding-left:1em; text-align:right;">Balance   $forall i <- items    ^{itemAsHtml vd i}-  <tr.totalrule>-   <td colspan=2>-  <tr>+  <tr .total>    <td>-   <td.balance align=right>#{mixedAmountAsHtml total}+   <td>+    #{mixedAmountAsHtml total} |]  where    l = ledgerFromJournal Any j    inacctmatcher = inAccountQuery qopts    items = items' -- maybe items' (\m -> filter (matchesAccount m . \(a,_,_,_)->a) items') showacctmatcher    itemAsHtml :: ViewData -> BalanceReportItem -> HtmlUrl AppRoute-   itemAsHtml _ ((acct, adisplay, aindent), abal) = [hamlet|-<tr.item.#{inacctclass}>- <td.account.#{depthclass}>-  \#{indent}-   <a href="@?{acctquery}" title="Show transactions affecting this account and subaccounts">#{adisplay}-   <span.hoverlinks>-    $if hassubs-     &nbsp;-     <a href="@?{acctonlyquery}" title="Show transactions affecting this account but not subaccounts">only-- <td.balance align=right>#{mixedAmountAsHtml abal}+   itemAsHtml _ (acct, adisplay, aindent, abal) = [hamlet|+<tr .#{inacctclass}>+ <td .acct>+  <div .ff-wrapper>+   \#{indent}+   <a href="@?{acctquery}" .#{inacctclass} title="Show transactions affecting this account and subaccounts">#{adisplay}+   $if hassubs+    <a href="@?{acctonlyquery}" .only .hidden-sm .hidden-xs title="Show transactions affecting this account but not subaccounts">only+ <td>+  #{mixedAmountAsHtml abal} |]      where        hassubs = not $ maybe False (null.asubs) $ ledgerAccount l acct- -- <td.numpostings align=right title="#{numpostings} transactions in this account">(#{numpostings})-       -- numpostings = maybe 0 (length.apostings) $ ledgerAccount l acct-       depthclass = "depth"++show aindent        inacctclass = case inacctmatcher of-                       Just m' -> if m' `matchesAccount` acct then "inacct" else "notinacct"+                       Just m' -> if m' `matchesAccount` acct then "inacct" else ""                        Nothing -> "" :: String        indent = preEscapedString $ concat $ replicate (2 * (1+aindent)) "&nbsp;"-       acctquery = (RegisterR, [("q", pack $ accountQuery acct)])-       acctonlyquery = (RegisterR, [("q", pack $ accountOnlyQuery acct)])+       acctquery = (RegisterR, [("q", T.pack $ accountQuery acct)])+       acctonlyquery = (RegisterR, [("q", T.pack $ accountOnlyQuery acct)])  accountQuery :: AccountName -> String-accountQuery a = "inacct:" ++ quoteIfSpaced a -- (accountNameToAccountRegex a)+accountQuery a = "inacct:" ++ T.unpack (quoteIfSpaced a) -- (accountNameToAccountRegex a)  accountOnlyQuery :: AccountName -> String-accountOnlyQuery a = "inacctonly:" ++ quoteIfSpaced a -- (accountNameToAccountRegex a)+accountOnlyQuery a = "inacctonly:" ++ T.unpack (quoteIfSpaced a ) -- (accountNameToAccountRegex a)  accountUrl :: AppRoute -> AccountName -> (AppRoute, [(Text, Text)])-accountUrl r a = (r, [("q", pack $ accountQuery a)])+accountUrl r a = (r, [("q", T.pack $ accountQuery a)])  -- stringIfLongerThan :: Int -> String -> String -- stringIfLongerThan n s = if length s > n then s else ""@@ -251,8 +237,8 @@           (prevdy,prevdm,_) = toGregorian prevd  mixedAmountAsHtml :: MixedAmount -> Html-mixedAmountAsHtml b = preEscapedString $ addclass $ intercalate "<br>" $ lines $ showMixedAmountWithoutPrice b-    where addclass = printf "<span class=\"%s\">%s</span>" (c :: String)+mixedAmountAsHtml b = preEscapedString $ unlines $ map addclass $ lines $ showMixedAmountWithoutPrice b+    where addclass = printf "<span class=\"%s\">%s</span><br/>" (c :: String)           c = case isNegativeMixedAmount b of Just True -> "negative amount"                                               _         -> "positive amount" 
Handler/JournalR.hs view
@@ -3,7 +3,8 @@  module Handler.JournalR where -import Data.Text (pack)+-- import Data.Text (Text)+import qualified Data.Text as T import Import  import Handler.AddForm@@ -27,16 +28,18 @@       -- showlastcolumn = if injournal && not filtering then False else True       title = case inacct of                 Nothing       -> "General Journal"++s2-                Just (a,inclsubs) -> "Transactions in "++a++s1++s2+                Just (a,inclsubs) -> "Transactions in "++T.unpack a++s1++s2                                       where s1 = if inclsubs then "" else " (excluding subaccounts)"                 where                   s2 = if filtering then ", filtered" else ""       maincontent = journalTransactionsReportAsHtml opts vd $ journalTransactionsReport (reportopts_ $ cliopts_ opts) j m   hledgerLayout vd "journal" [hamlet|-       <h2#contenttitle>#{title}-       <!-- p>Journal entries record movements of commodities between accounts. -->-       <a#addformlink role="button" style="cursor:pointer;" onClick="addformReset(true);" data-toggle="modal" data-target="#addmodal"  title="Add a new transaction to the journal" style="margin-top:1em;">Add a transaction-       ^{maincontent}+       <div .row>+        <h2 #contenttitle>#{title}+        <!-- p>Journal entries record movements of commodities between accounts. -->+        <a #addformlink role="button" style="cursor:pointer; margin-top:1em;" data-toggle="modal" data-target="#addmodal" title="Add a new transaction to the journal" >Add a transaction+       <div .table-responsive>+        ^{maincontent}      |]  postJournalR :: Handler Html@@ -45,48 +48,41 @@ -- | Render a "TransactionsReport" as html for the formatted journal view. journalTransactionsReportAsHtml :: WebOpts -> ViewData -> TransactionsReport -> HtmlUrl AppRoute journalTransactionsReportAsHtml _ vd (_,items) = [hamlet|-<table.transactionsreport>- <tr.headings>-  <th.date style="text-align:left;">+<table .transactionsreport .table .table-condensed>+ <thead>+  <th .date style="text-align:left;">    Date-   <span .glyphicon .glyphicon-chevron-up>-  <th.description style="text-align:left;">Description-  <th.account style="text-align:left;">Account-  <th.amount style="text-align:right;">Amount+  <th .description style="text-align:left;">Description+  <th .account style="text-align:left;">Account+  <th .amount style="text-align:right;">Amount  $forall i <- numberTransactionsReportItems items   ^{itemAsHtml vd i}  |]  where -- .#{datetransition}    itemAsHtml :: ViewData -> (Int, Bool, Bool, Bool, TransactionsReportItem) -> HtmlUrl AppRoute-   itemAsHtml VD{..} (n, _, _, _, (t, _, split, _, amt, _)) = [hamlet|-<tr ##{date} .item.#{evenodd}.#{firstposting} style="vertical-align:top;" title="#{show t}">- <td.date>#{date}- <td.description colspan=2>#{elideRight 60 desc}- <td.amount style="text-align:right;">+   itemAsHtml VD{..} (_, _, _, _, (torig, _, split, _, amt, _)) = [hamlet|+<tr .title #transaction-#{tindex torig}>+ <td .date nowrap>#{date}+ <td .description colspan=2>#{textElideRight 60 desc}+ <td .amount style="text-align:right;">   $if showamt    \#{mixedAmountAsHtml amt}-$forall p' <- tpostings t- <tr .item.#{evenodd}.posting title="#{show t}">-  <td.date>-  <td.description>-  <td.account>+$forall p' <- tpostings torig+ <tr .item .posting title="#{show torig}">+  <td .nonhead>+  <td .nonhead>+  <td .nonhead>    &nbsp;-   <a href="@?{acctlink (paccount p')}##{date}" title="#{paccount p'}">#{elideAccountName 40 $ paccount p'}-  <td.amount style="text-align:right;">#{mixedAmountAsHtml $ pamount p'}-<tr.#{evenodd}>- <td>&nbsp;- <td>- <td>- <td>+   <a href="@?{acctlink (paccount p')}##{tindex torig}" title="#{paccount p'}">#{elideAccountName 40 $ paccount p'}+  <td .amount .nonhead style="text-align:right;">#{mixedAmountAsHtml $ pamount p'} |]      where-       acctlink a = (RegisterR, [("q", pack $ accountQuery a)])-       evenodd = if even n then "even" else "odd" :: String+       acctlink a = (RegisterR, [("q", T.pack $ accountQuery a)])        -- datetransition | newm = "newmonth"        --                | newd = "newday"        --                | otherwise = "" :: String-       (firstposting, date, desc) = (False, show $ tdate t, tdescription t)-       -- acctquery = (here, [("q", pack $ accountQuery acct)])+       (date, desc) = (show $ tdate torig, tdescription torig)+       -- acctquery = (here, [("q", T.pack $ accountQuery acct)])        showamt = not split || not (isZeroMixedAmount amt) 
Handler/RegisterR.hs view
@@ -7,6 +7,8 @@  import Data.List import Data.Maybe+-- import Data.Text (Text)+import qualified Data.Text as T import Safe  import Handler.AddForm@@ -28,14 +30,14 @@   let -- injournal = isNothing inacct       filtering = m /= Any       -- title = "Transactions in "++a++s1++s2-      title = a++s1++s2+      title = T.unpack a++s1++s2                where                  (a,inclsubs) = fromMaybe ("all accounts",True) $ inAccount qopts                  s1 = if inclsubs then "" else " (excluding subaccounts)"                  s2 = if filtering then ", filtered" else ""       maincontent = registerReportHtml opts vd $ accountTransactionsReport (reportopts_ $ cliopts_ opts) j m $ fromMaybe Any $ inAccountQuery qopts   hledgerLayout vd "register" [hamlet|-       <h2#contenttitle>#{title}+       <h2 #contenttitle>#{title}        <!-- p>Transactions affecting this account, with running balance. -->        ^{maincontent}      |]@@ -46,24 +48,27 @@ -- Generate html for an account register, including a balance chart and transaction list. registerReportHtml :: WebOpts -> ViewData -> TransactionsReport -> HtmlUrl AppRoute registerReportHtml opts vd r = [hamlet|- ^{registerChartHtml $ transactionsReportByCommodity r}+ <div .hidden-xs>+  ^{registerChartHtml $ transactionsReportByCommodity r}  ^{registerItemsHtml opts vd r} |]  -- Generate html for a transaction list from an "TransactionsReport". registerItemsHtml :: WebOpts -> ViewData -> TransactionsReport -> HtmlUrl AppRoute registerItemsHtml _ vd (balancelabel,items) = [hamlet|-<table.registerreport>- <tr.headings>-  <th.date style="text-align:left;">-   Date-   <span .glyphicon .glyphicon-chevron-up>-  <th.description style="text-align:left;">Description-  <th.account style="text-align:left;">To/From Account(s)-  <th.amount style="text-align:right; white-space:normal;">Amount Out/In-  <th.balance style="text-align:right; white-space:normal;">#{balancelabel'}- $forall i <- numberTransactionsReportItems items-  ^{itemAsHtml vd i}+<div .table-responsive>+ <table.registerreport .table .table-striped .table-condensed>+  <thead>+   <tr>+    <th style="text-align:left;">+     Date+     <span .glyphicon .glyphicon-chevron-up>+    <th style="text-align:left;">Description+    <th style="text-align:left;">To/From Account(s)+    <th style="text-align:right; white-space:normal;">Amount Out/In+    <th style="text-align:right; white-space:normal;">#{balancelabel'}+  $forall i <- numberTransactionsReportItems items+   ^{itemAsHtml vd i}  |]  where    insomeacct = isJust $ inAccount $ qopts vd@@ -73,14 +78,15 @@    itemAsHtml :: ViewData -> (Int, Bool, Bool, Bool, TransactionsReportItem) -> HtmlUrl AppRoute    itemAsHtml VD{..} (n, newd, newm, _, (torig, tacct, split, acct, amt, bal)) = [hamlet| -<tr ##{date} .item.#{evenodd}.#{firstposting}.#{datetransition} title="#{show torig}" style="vertical-align:top;">- <td.date><a href="@{JournalR}##{date}">#{date}- <td.description title="#{show torig}">#{elideRight 30 desc}- <td.account>#{elideRight 40 acct}- <td.amount style="text-align:right; white-space:nowrap;">+<tr ##{tindex torig} .item.#{evenodd}.#{firstposting}.#{datetransition} title="#{show torig}" style="vertical-align:top;">+ <td .date>+  <a href="@{JournalR}#transaction-#{tindex torig}">#{date}+ <td .description title="#{show torig}">#{textElideRight 30 desc}+ <td .account>#{elideRight 40 acct}+ <td .amount style="text-align:right; white-space:nowrap;">   $if showamt    \#{mixedAmountAsHtml amt}- <td.balance style="text-align:right;">#{mixedAmountAsHtml bal}+ <td .balance style="text-align:right;">#{mixedAmountAsHtml bal} |]       where@@ -98,13 +104,13 @@                --                      Data.Foldable.Foldable t1 =>                --                      t1 (Transaction, t2, t3, t4, t5, MixedAmount)                --                      -> t -> Text.Blaze.Internal.HtmlM ()-registerChartHtml :: [(Commodity, (String, [TransactionsReportItem]))] -> HtmlUrl AppRoute+registerChartHtml :: [(CommoditySymbol, (String, [TransactionsReportItem]))] -> HtmlUrl AppRoute registerChartHtml percommoditytxnreports =  -- have to make sure plot is not called when our container (maincontent)  -- is hidden, eg with add form toggled  [hamlet|-<label#register-chart-label style=""><br>-<div#register-chart style="width:85%; height:150px; margin-bottom:1em; display:block;">+<label #register-chart-label style=""><br>+<div #register-chart style="height:150px; margin-bottom:1em; display:block;"> <script type=text/javascript>  \$(document).ready(function() {    var $chartdiv = $('#register-chart');@@ -119,11 +125,11 @@           $forall i <- reverse items            [             #{dayToJsTimestamp $ triDate i},-            #{simpleMixedAmountQuantity $ triCommodityBalance c i},+            #{simpleMixedAmountQuantity $ triCommodityBalance c i}            ],           /* [] */         ],-        label: '#{shownull c}',+        label: '#{shownull $ T.unpack c}',         color: #{colorForCommodity c},         lines: {           show: true,@@ -145,6 +151,7 @@             '#{showMixedAmountWithZeroCommodity $ triCommodityAmount c i}',             '#{showMixedAmountWithZeroCommodity $ triCommodityBalance c i}',             '#{concat $ intersperse "\\n" $ lines  $ show $ triOrigTransaction i}',+            #{tindex $ triOrigTransaction i}            ],           /* [] */         ],@@ -169,6 +176,6 @@            of "" -> ""               s  -> s++":"    colorForCommodity = fromMaybe 0 . flip lookup commoditiesIndex-   commoditiesIndex = zip (map fst percommoditytxnreports) [0..] :: [(Commodity,Int)]+   commoditiesIndex = zip (map fst percommoditytxnreports) [0..] :: [(CommoditySymbol,Int)]    simpleMixedAmountQuantity = maybe 0 aquantity . headMay . amounts    shownull c = if null c then " " else c
Hledger/Web/Main.hs view
@@ -16,8 +16,8 @@ import Settings            --  (parseExtra) import Application          (makeApplication) import Data.String-import Network.Wai.Handler.Warp (runSettings, defaultSettings, setPort)-import Network.Wai.Handler.Launch (runUrlPort)+import Network.Wai.Handler.Warp (runSettings, defaultSettings, setHost, setPort)+import Network.Wai.Handler.Launch (runHostPortUrl) -- #if !MIN_VERSION_base(4,8,0) import Control.Applicative ((<$>))@@ -35,16 +35,19 @@ import Hledger.Web.WebOptions  -main :: IO ()-main = do+hledgerWebMain :: IO ()+hledgerWebMain = do   opts <- getHledgerWebOpts   when (debug_ (cliopts_ opts) > 0) $ printf "%s\n" prognameandversion >> printf "opts: %s\n" (show opts)   runWith opts  runWith :: WebOpts -> IO () runWith opts-  | "help" `inRawOpts` (rawopts_ $ cliopts_ opts)            = putStr (showModeHelp webmode) >> exitSuccess-  | "version" `inRawOpts` (rawopts_ $ cliopts_ opts)         = putStrLn prognameandversion >> exitSuccess+  | "h"               `inRawOpts` (rawopts_ $ cliopts_ opts) = putStr (showModeUsage webmode) >> exitSuccess+  | "help"            `inRawOpts` (rawopts_ $ cliopts_ opts) = printHelpForTopic (topicForMode webmode) >> exitSuccess+  | "man"             `inRawOpts` (rawopts_ $ cliopts_ opts) = runManForTopic (topicForMode webmode) >> exitSuccess+  | "info"            `inRawOpts` (rawopts_ $ cliopts_ opts) = runInfoForTopic (topicForMode webmode) >> exitSuccess+  | "version"         `inRawOpts` (rawopts_ $ cliopts_ opts) = putStrLn prognameandversion >> exitSuccess   | "binary-filename" `inRawOpts` (rawopts_ $ cliopts_ opts) = putStrLn (binaryfilename progname)   | otherwise = do     requireJournalFileExists =<< (head `fmap` journalFilePathFromOpts (cliopts_ opts)) -- XXX head should be safe for now@@ -57,7 +60,7 @@   -- https://github.com/simonmichael/hledger/issues/202   -- -f- gives [Error#yesod-core] <stdin>: hGetContents: illegal operation (handle is closed) for some reason   -- Also we may be writing to this file. Just disallow it.-  when (f == "-") $ error' "hledger-web doesn't support --f -, please specify a file path"+  when (f == "-") $ error' "hledger-web doesn't support -f -, please specify a file path"    readJournalFile Nothing Nothing True f >>=    either error' (cmd opts . journalApplyAliases (aliasesFromOpts $ cliopts_ opts))@@ -66,24 +69,32 @@ web :: WebOpts -> Journal -> IO () web opts j = do   d <- getCurrentDay-  let j' = filterJournalTransactions (queryFromOpts d $ reportopts_ $ cliopts_ opts) j+  let initq = queryFromOpts d $ reportopts_ $ cliopts_ opts+      j' = filterJournalTransactions initq j+      h = "127.0.0.1"       p = port_ opts       u = base_url_ opts       staticRoot = pack <$> file_url_ opts-  _ <- printf "Starting web app on port %d with base url %s\n" p u-  app <- makeApplication opts j' AppConfig{appEnv = Development-                                          ,appPort = p-                                          ,appRoot = pack u-                                          ,appHost = fromString "*4"-                                          ,appExtra = Extra "" Nothing staticRoot-                                          }+      appconfig = AppConfig{appEnv = Development+                           ,appHost = fromString h+                           ,appPort = p+                           ,appRoot = pack u+                           ,appExtra = Extra "" Nothing staticRoot+                           }+  app <- makeApplication opts j' appconfig+  _ <- printf "Starting web app on host %s port %d with base url %s\n" h p u   if server_ opts-   then do-    putStrLn "Press ctrl-c to quit"-    hFlush stdout-    Network.Wai.Handler.Warp.runSettings (setPort p defaultSettings) app-   else do-    putStrLn "Starting web browser if possible"-    putStrLn "Web app will auto-exit after a few minutes with no browsers (or press ctrl-c)"-    hFlush stdout-    Network.Wai.Handler.Launch.runUrlPort p "" app+    then do+      putStrLn "Press ctrl-c to quit"+      hFlush stdout+      let warpsettings =+            setHost (fromString h) $+            setPort p $+            defaultSettings+      Network.Wai.Handler.Warp.runSettings warpsettings app+    else do+      putStrLn "Starting web browser..."+      putStrLn "Web app will auto-exit after a few minutes with no browsers (or press ctrl-c)"+      hFlush stdout+      Network.Wai.Handler.Launch.runHostPortUrl h p "" app+
Hledger/Web/WebOptions.hs view
@@ -2,12 +2,11 @@ module Hledger.Web.WebOptions where import Prelude+import Data.Default #if !MIN_VERSION_base(4,8,0) import Data.Functor.Compat ((<$>)) #endif import Data.Maybe-import System.Console.CmdArgs-import System.Console.CmdArgs.Explicit  import Hledger.Cli hiding (progname,version,prognameandversion) import Settings
+ README view
@@ -0,0 +1,1 @@+A basic web UI for hledger data. Intended to be robust and somewhat useful.
app/main.hs view
@@ -1,9 +1,9 @@ import Prelude              (IO) -import qualified Hledger.Web.Main+import Hledger.Web.Main  main :: IO ()-main = Hledger.Web.Main.main+main = hledgerWebMain  -- more standard yesod main, for reloading experiments 
+ doc/hledger-web.1 view
@@ -0,0 +1,319 @@++.TH "hledger\-web" "1" "October 2016" "hledger\-web 1.0" "hledger User Manuals"++++.SH NAME+.PP+hledger\-web \- web interface for the hledger accounting tool+.SH SYNOPSIS+.PP+\f[C]hledger\-web\ [OPTIONS]\f[]+.PD 0+.P+.PD+\f[C]hledger\ web\ \-\-\ [OPTIONS]\f[]+.PP+.PP+.SH DESCRIPTION+.PP+hledger is a cross\-platform program for tracking money, time, or any+other commodity, using double\-entry accounting and a simple, editable+file format.+hledger is inspired by and largely compatible with ledger(1).+.PP+hledger\-web is hledger\[aq]s web interface.+It starts a simple web application for browsing and adding transactions,+and optionally opens it in a web browser window if possible.+It provides a more user\-friendly UI than the hledger CLI or hledger\-ui+interface, showing more at once (accounts, the current account register,+balance charts) and allowing history\-aware data entry, interactive+searching, and bookmarking.+.PP+hledger\-web also lets you share a ledger with multiple users, or even+the public web.+There is no access control, so if you need that you should put it behind+a suitable web proxy.+As a small protection against data loss when running an unprotected+instance, it writes a numbered backup of the main journal file (only ?)+on every edit.+.PP+Like hledger, it reads data from one or more files in hledger journal,+timeclock, timedot, or CSV format specified with \f[C]\-f\f[], or+\f[C]$LEDGER_FILE\f[], or \f[C]$HOME/.hledger.journal\f[] (on windows,+perhaps \f[C]C:/Users/USER/.hledger.journal\f[]).+For more about this see hledger(1), hledger_journal(5) etc.+.PP+By default, hledger\-web starts the web app in "transient mode" and also+opens it in your default web browser if possible.+In this mode the web app will keep running for as long as you have it+open in a browser window, and will exit after two minutes of inactivity+(no requests and no browser windows viewing it).+.IP+.nf+\f[C]+$\ hledger\ web+Starting\ web\ app\ on\ port\ 5000\ with\ base\ url\ http://localhost:5000+Starting\ web\ browser\ if\ possible+Web\ app\ will\ auto\-exit\ after\ a\ few\ minutes\ with\ no\ browsers\ (or\ press\ ctrl\-c)+\f[]+.fi+.PP+With \f[C]\-\-server\f[], it starts the web app in non\-transient mode+and logs requests to the console.+Typically when running hledger web as part of a website you\[aq]ll want+to use \f[C]\-\-base\-url\f[] to set the protocol/hostname/port/path to+be used in hyperlinks.+The \f[C]\-\-file\-url\f[] option allows static files to be served from+a different url, eg for better caching or cookie\-less serving.+.PP+You can use \f[C]\-\-port\f[] to listen on a different TCP port, eg if+you are running multiple hledger\-web instances.+This need not be the same as the PORT in the base url.+.PP+Note there is no built\-in access control, so you will need to hide+hledger\-web behind an authenticating proxy (such as apache or nginx) if+you want to restrict who can see and add entries to your journal.+.PP+Command\-line options and arguments may be used to set an initial filter+on the data.+This is not shown in the web UI, but it will be applied in addition to+any search query entered there.+.PP+With journal and timeclock files (but not CSV files, currently) the web+app detects changes made by other means and will show the new data on+the next request.+If a change makes the file unparseable, hledger\-web will show an error+until the file has been fixed.+.SH OPTIONS+.PP+Note: if invoking hledger\-web as a hledger subcommand, write+\f[C]\-\-\f[] before options as shown above.+.TP+.B \f[C]\-\-server\f[]+disable browser\-opening and auto\-exit\-on\-idle, and log all requests+to stdout+.RS+.RE+.TP+.B \f[C]\-\-port=PORT\f[]+set the TCP port to listen on (default: 5000)+.RS+.RE+.TP+.B \f[C]\-\-base\-url=URL\f[]+set the base url (default: http://localhost:PORT).+You would change this when sharing over the network, or integrating+within a larger website.+.RS+.RE+.TP+.B \f[C]\-\-file\-url=URL\f[]+set the static files url (default: BASEURL/static).+hledger\-web normally serves static files itself, but if you wanted to+serve them from another server for efficiency, you would set the url+with this.+.RS+.RE+.PP+hledger general options:+.TP+.B \f[C]\-h\f[]+show general usage (or after COMMAND, the command\[aq]s usage)+.RS+.RE+.TP+.B \f[C]\-\-help\f[]+show the current program\[aq]s manual as plain text (or after an add\-on+COMMAND, the add\-on\[aq]s manual)+.RS+.RE+.TP+.B \f[C]\-\-man\f[]+show the current program\[aq]s manual with man+.RS+.RE+.TP+.B \f[C]\-\-info\f[]+show the current program\[aq]s manual with info+.RS+.RE+.TP+.B \f[C]\-\-version\f[]+show version+.RS+.RE+.TP+.B \f[C]\-\-debug[=N]\f[]+show debug output (levels 1\-9, default: 1)+.RS+.RE+.TP+.B \f[C]\-f\ FILE\ \-\-file=FILE\f[]+use a different input file.+For stdin, use \-+.RS+.RE+.TP+.B \f[C]\-\-rules\-file=RULESFILE\f[]+Conversion rules file to use when reading CSV (default: FILE.rules)+.RS+.RE+.TP+.B \f[C]\-\-alias=OLD=NEW\f[]+display accounts named OLD as NEW+.RS+.RE+.TP+.B \f[C]\-I\ \-\-ignore\-assertions\f[]+ignore any failing balance assertions in the journal+.RS+.RE+.PP+hledger reporting options:+.TP+.B \f[C]\-b\ \-\-begin=DATE\f[]+include postings/txns on or after this date+.RS+.RE+.TP+.B \f[C]\-e\ \-\-end=DATE\f[]+include postings/txns before this date+.RS+.RE+.TP+.B \f[C]\-D\ \-\-daily\f[]+multiperiod/multicolumn report by day+.RS+.RE+.TP+.B \f[C]\-W\ \-\-weekly\f[]+multiperiod/multicolumn report by week+.RS+.RE+.TP+.B \f[C]\-M\ \-\-monthly\f[]+multiperiod/multicolumn report by month+.RS+.RE+.TP+.B \f[C]\-Q\ \-\-quarterly\f[]+multiperiod/multicolumn report by quarter+.RS+.RE+.TP+.B \f[C]\-Y\ \-\-yearly\f[]+multiperiod/multicolumn report by year+.RS+.RE+.TP+.B \f[C]\-p\ \-\-period=PERIODEXP\f[]+set start date, end date, and/or reporting interval all at once+(overrides the flags above)+.RS+.RE+.TP+.B \f[C]\-\-date2\f[]+show, and match with \-b/\-e/\-p/date:, secondary dates instead+.RS+.RE+.TP+.B \f[C]\-C\ \-\-cleared\f[]+include only cleared postings/txns+.RS+.RE+.TP+.B \f[C]\-\-pending\f[]+include only pending postings/txns+.RS+.RE+.TP+.B \f[C]\-U\ \-\-uncleared\f[]+include only uncleared (and pending) postings/txns+.RS+.RE+.TP+.B \f[C]\-R\ \-\-real\f[]+include only non\-virtual postings+.RS+.RE+.TP+.B \f[C]\-\-depth=N\f[]+hide accounts/postings deeper than N+.RS+.RE+.TP+.B \f[C]\-E\ \-\-empty\f[]+show items with zero amount, normally hidden+.RS+.RE+.TP+.B \f[C]\-B\ \-\-cost\f[]+show amounts in their cost price\[aq]s commodity+.RS+.RE+.TP+.B \f[C]\-\-pivot\ TAG\f[]+will transform the journal before any other processing by replacing the+account name of every posting having the tag TAG with content VALUE by+the account name "TAG:VALUE".+.RS+.RE+The TAG will only match if it is a full\-length match.+The pivot will only happen if the TAG is on a posting, not if it is on+the transaction.+If the tag value is a multi:level:account:name the new account name will+be "TAG:multi:level:account:name".+.RS+.RE+.TP+.B \f[C]\-\-anon\f[]+show anonymized accounts and payees+.RS+.RE+.SH ENVIRONMENT+.PP+\f[B]LEDGER_FILE\f[] The journal file path when not specified with+\f[C]\-f\f[].+Default: \f[C]~/.hledger.journal\f[] (on windows, perhaps+\f[C]C:/Users/USER/.hledger.journal\f[]).+.SH FILES+.PP+Reads data from one or more files in hledger journal, timeclock,+timedot, or CSV format specified with \f[C]\-f\f[], or+\f[C]$LEDGER_FILE\f[], or \f[C]$HOME/.hledger.journal\f[] (on windows,+perhaps \f[C]C:/Users/USER/.hledger.journal\f[]).+.SH BUGS+.PP+The need to precede options with \f[C]\-\-\f[] when invoked from hledger+is awkward.+.PP+\f[C]\-f\-\f[] doesn\[aq]t work (hledger\-web can\[aq]t read from+stdin).+.PP+Query arguments and some hledger options are ignored.+.PP+Does not work in text\-mode browsers.+.PP+Does not work well on small screens.+++.SH "REPORTING BUGS"+Report bugs at http://bugs.hledger.org+(or on the #hledger IRC channel or hledger mail list)++.SH AUTHORS+Simon Michael <simon@joyful.com> and contributors++.SH COPYRIGHT++Copyright (C) 2007-2016 Simon Michael.+.br+Released under GNU GPL v3 or later.++.SH SEE ALSO+hledger(1), hledger\-ui(1), hledger\-web(1), hledger\-api(1),+hledger_csv(5), hledger_journal(5), hledger_timeclock(5), hledger_timedot(5),+ledger(1)++http://hledger.org
+ doc/hledger-web.1.info view
@@ -0,0 +1,199 @@+This is hledger-web/doc/hledger-web.1.info, produced by makeinfo+version 4.8 from stdin.+++File: hledger-web.1.info,  Node: Top,  Up: (dir)++hledger-web(1) hledger-web 1.0+******************************++hledger-web is hledger's web interface. It starts a simple web+application for browsing and adding transactions, and optionally opens+it in a web browser window if possible. It provides a more user-friendly+UI than the hledger CLI or hledger-ui interface, showing more at once+(accounts, the current account register, balance charts) and allowing+history-aware data entry, interactive searching, and bookmarking.++   hledger-web also lets you share a ledger with multiple users, or even+the public web. There is no access control, so if you need that you+should put it behind a suitable web proxy. As a small protection against+data loss when running an unprotected instance, it writes a numbered+backup of the main journal file (only ?) on every edit.++   Like hledger, it reads data from one or more files in hledger+journal, timeclock, timedot, or CSV format specified with `-f', or+`$LEDGER_FILE', or `$HOME/.hledger.journal' (on windows, perhaps+`C:/Users/USER/.hledger.journal'). For more about this see hledger(1),+hledger_journal(5) etc.++   By default, hledger-web starts the web app in "transient mode" and+also opens it in your default web browser if possible. In this mode the+web app will keep running for as long as you have it open in a browser+window, and will exit after two minutes of inactivity (no requests and+no browser windows viewing it).+++$ hledger web+Starting web app on port 5000 with base url http://localhost:5000+Starting web browser if possible+Web app will auto-exit after a few minutes with no browsers (or press ctrl-c)++   With `--server', it starts the web app in non-transient mode and+logs requests to the console. Typically when running hledger web as part+of a website you'll want to use `--base-url' to set the+protocol/hostname/port/path to be used in hyperlinks. The `--file-url'+option allows static files to be served from a different url, eg for+better caching or cookie-less serving.++   You can use `--port' to listen on a different TCP port, eg if you+are running multiple hledger-web instances. This need not be the same as+the PORT in the base url.++   Note there is no built-in access control, so you will need to hide+hledger-web behind an authenticating proxy (such as apache or nginx) if+you want to restrict who can see and add entries to your journal.++   Command-line options and arguments may be used to set an initial+filter on the data. This is not shown in the web UI, but it will be+applied in addition to any search query entered there.++   With journal and timeclock files (but not CSV files, currently) the+web app detects changes made by other means and will show the new data+on the next request. If a change makes the file unparseable, hledger-web+will show an error until the file has been fixed.++* Menu:++* OPTIONS::+++File: hledger-web.1.info,  Node: OPTIONS,  Prev: Top,  Up: Top++1 OPTIONS+*********++Note: if invoking hledger-web as a hledger subcommand, write `--'+before options as shown above.++`--server'+     disable browser-opening and auto-exit-on-idle, and log all+     requests to stdout++`--port=PORT'+     set the TCP port to listen on (default: 5000)++`--base-url=URL'+     set the base url (default: http://localhost:PORT). You would+     change this when sharing over the network, or integrating within a+     larger website.++`--file-url=URL'+     set the static files url (default: BASEURL/static). hledger-web+     normally serves static files itself, but if you wanted to serve+     them from another server for efficiency, you would set the url+     with this.++   hledger general options:++`-h'+     show general usage (or after COMMAND, the command's usage)++`--help'+     show the current program's manual as plain text (or after an add-on+     COMMAND, the add-on's manual)++`--man'+     show the current program's manual with man++`--info'+     show the current program's manual with info++`--version'+     show version++`--debug[=N]'+     show debug output (levels 1-9, default: 1)++`-f FILE --file=FILE'+     use a different input file. For stdin, use -++`--rules-file=RULESFILE'+     Conversion rules file to use when reading CSV (default: FILE.rules)++`--alias=OLD=NEW'+     display accounts named OLD as NEW++`-I --ignore-assertions'+     ignore any failing balance assertions in the journal++   hledger reporting options:++`-b --begin=DATE'+     include postings/txns on or after this date++`-e --end=DATE'+     include postings/txns before this date++`-D --daily'+     multiperiod/multicolumn report by day++`-W --weekly'+     multiperiod/multicolumn report by week++`-M --monthly'+     multiperiod/multicolumn report by month++`-Q --quarterly'+     multiperiod/multicolumn report by quarter++`-Y --yearly'+     multiperiod/multicolumn report by year++`-p --period=PERIODEXP'+     set start date, end date, and/or reporting interval all at once+     (overrides the flags above)++`--date2'+     show, and match with -b/-e/-p/date:, secondary dates instead++`-C --cleared'+     include only cleared postings/txns++`--pending'+     include only pending postings/txns++`-U --uncleared'+     include only uncleared (and pending) postings/txns++`-R --real'+     include only non-virtual postings++`--depth=N'+     hide accounts/postings deeper than N++`-E --empty'+     show items with zero amount, normally hidden++`-B --cost'+     show amounts in their cost price's commodity++`--pivot TAG'+     will transform the journal before any other processing by+     replacing the account name of every posting having the tag TAG+     with content VALUE by the account name "TAG:VALUE".  The TAG will+     only match if it is a full-length match. The pivot will only+     happen if the TAG is on a posting, not if it is on the transaction.+     If the tag value is a multi:level:account:name the new account+     name will be "TAG:multi:level:account:name".++`--anon'+     show anonymized accounts and payees++++Tag Table:+Node: Top90+Node: OPTIONS2997+Ref: #options3084++End Tag Table
+ doc/hledger-web.1.txt view
@@ -0,0 +1,237 @@++hledger-web(1)               hledger User Manuals               hledger-web(1)++++NAME+       hledger-web - web interface for the hledger accounting tool++SYNOPSIS+       hledger-web [OPTIONS]+       hledger web -- [OPTIONS]++++DESCRIPTION+       hledger  is  a  cross-platform program for tracking money, time, or any+       other commodity, using double-entry accounting and a  simple,  editable+       file  format.   hledger  is  inspired  by  and  largely compatible with+       ledger(1).++       hledger-web is hledger's web interface.  It starts a simple web  appli-+       cation for browsing and adding transactions, and optionally opens it in+       a web browser window if possible.  It provides a more user-friendly  UI+       than  the  hledger  CLI  or  hledger-ui interface, showing more at once+       (accounts, the current account register, balance charts)  and  allowing+       history-aware data entry, interactive searching, and bookmarking.++       hledger-web  also  lets you share a ledger with multiple users, or even+       the public web.  There is no access control, so if you  need  that  you+       should  put  it  behind  a  suitable  web proxy.  As a small protection+       against data loss when running an unprotected  instance,  it  writes  a+       numbered backup of the main journal file (only ?)  on every edit.++       Like  hledger, it reads data from one or more files in hledger journal,+       timeclock, timedot, or CSV format specified with -f,  or  $LEDGER_FILE,+       or        $HOME/.hledger.journal       (on       windows,       perhaps+       C:/Users/USER/.hledger.journal).  For more about this  see  hledger(1),+       hledger_journal(5) etc.++       By default, hledger-web starts the web app in "transient mode" and also+       opens it in your default web browser if possible.  In this mode the web+       app will keep running for as long as you have it open in a browser win-+       dow, and will exit after two minutes of inactivity (no requests and  no+       browser windows viewing it).++              $ hledger web+              Starting web app on port 5000 with base url http://localhost:5000+              Starting web browser if possible+              Web app will auto-exit after a few minutes with no browsers (or press ctrl-c)++       With  --server,  it  starts  the web app in non-transient mode and logs+       requests to the console.  Typically when running hledger web as part of+       a  website  you'll  want  to  use  --base-url to set the protocol/host-+       name/port/path to be used in hyperlinks.  The --file-url option  allows+       static  files  to be served from a different url, eg for better caching+       or cookie-less serving.++       You can use --port to listen on a different TCP port,  eg  if  you  are+       running  multiple  hledger-web instances.  This need not be the same as+       the PORT in the base url.++       Note there is no built-in access control, so  you  will  need  to  hide+       hledger-web behind an authenticating proxy (such as apache or nginx) if+       you want to restrict who can see and add entries to your journal.++       Command-line options and arguments may be used to set an initial filter+       on  the  data.  This is not shown in the web UI, but it will be applied+       in addition to any search query entered there.++       With journal and timeclock files (but not CSV files, currently) the web+       app  detects  changes made by other means and will show the new data on+       the next request.  If a change makes the file unparseable,  hledger-web+       will show an error until the file has been fixed.++OPTIONS+       Note:  if invoking hledger-web as a hledger subcommand, write -- before+       options as shown above.++       --server+              disable  browser-opening  and  auto-exit-on-idle,  and  log  all+              requests to stdout++       --port=PORT+              set the TCP port to listen on (default: 5000)++       --base-url=URL+              set  the  base  url (default: http://localhost:PORT).  You would+              change this when sharing over the network, or integrating within+              a larger website.++       --file-url=URL+              set the static files url (default: BASEURL/static).  hledger-web+              normally serves static files itself, but if you wanted to  serve+              them  from  another server for efficiency, you would set the url+              with this.++       hledger general options:++       -h     show general usage (or after COMMAND, the command's usage)++       --help show the current program's manual as plain  text  (or  after  an+              add-on COMMAND, the add-on's manual)++       --man  show the current program's manual with man++       --info show the current program's manual with info++       --version+              show version++       --debug[=N]+              show debug output (levels 1-9, default: 1)++       -f FILE --file=FILE+              use a different input file.  For stdin, use -++       --rules-file=RULESFILE+              Conversion   rules  file  to  use  when  reading  CSV  (default:+              FILE.rules)++       --alias=OLD=NEW+              display accounts named OLD as NEW++       -I --ignore-assertions+              ignore any failing balance assertions in the journal++       hledger reporting options:++       -b --begin=DATE+              include postings/txns on or after this date++       -e --end=DATE+              include postings/txns before this date++       -D --daily+              multiperiod/multicolumn report by day++       -W --weekly+              multiperiod/multicolumn report by week++       -M --monthly+              multiperiod/multicolumn report by month++       -Q --quarterly+              multiperiod/multicolumn report by quarter++       -Y --yearly+              multiperiod/multicolumn report by year++       -p --period=PERIODEXP+              set start date, end date, and/or reporting interval all at  once+              (overrides the flags above)++       --date2+              show, and match with -b/-e/-p/date:, secondary dates instead++       -C --cleared+              include only cleared postings/txns++       --pending+              include only pending postings/txns++       -U --uncleared+              include only uncleared (and pending) postings/txns++       -R --real+              include only non-virtual postings++       --depth=N+              hide accounts/postings deeper than N++       -E --empty+              show items with zero amount, normally hidden++       -B --cost+              show amounts in their cost price's commodity++       --pivot TAG+              will  transform  the  journal  before  any  other  processing by+              replacing the account name of every posting having the  tag  TAG+              with content VALUE by the account name "TAG:VALUE".+       The  TAG  will only match if it is a full-length match.  The pivot will+       only happen if the TAG is on a posting, not if it is  on  the  transac-+       tion.   If  the tag value is a multi:level:account:name the new account+       name will be "TAG:multi:level:account:name".++       --anon show anonymized accounts and payees++ENVIRONMENT+       LEDGER_FILE The journal file path when not specified with -f.  Default:+       ~/.hledger.journal  (on  windows,  perhaps C:/Users/USER/.hledger.jour-+       nal).++FILES+       Reads data from one or more files in hledger journal, timeclock,  time-+       dot,   or   CSV   format   specified   with  -f,  or  $LEDGER_FILE,  or+       $HOME/.hledger.journal          (on          windows,           perhaps+       C:/Users/USER/.hledger.journal).++BUGS+       The  need  to precede options with -- when invoked from hledger is awk-+       ward.++       -f- doesn't work (hledger-web can't read from stdin).++       Query arguments and some hledger options are ignored.++       Does not work in text-mode browsers.++       Does not work well on small screens.++++REPORTING BUGS+       Report bugs at http://bugs.hledger.org (or on the #hledger IRC  channel+       or hledger mail list)+++AUTHORS+       Simon Michael <simon@joyful.com> and contributors+++COPYRIGHT+       Copyright (C) 2007-2016 Simon Michael.+       Released under GNU GPL v3 or later.+++SEE ALSO+       hledger(1),      hledger-ui(1),     hledger-web(1),     hledger-api(1),+       hledger_csv(5), hledger_journal(5), hledger_timeclock(5), hledger_time-+       dot(5), ledger(1)++       http://hledger.org++++hledger-web 1.0                  October 2016                   hledger-web(1)
hledger-web.cabal view
@@ -1,22 +1,22 @@--- This file has been generated from package.yaml by hpack version 0.5.4.+-- This file has been generated from package.yaml by hpack version 0.14.0. -- -- see: https://github.com/sol/hpack  name:           hledger-web-version:        0.27+version:        1.0 stability:      stable category:       Finance synopsis:       Web interface for the hledger accounting tool-description:-    This is hledger's web interface.-    It provides a more user-friendly and collaborative UI than the-    command-line or curses-style interfaces.-    hledger is a cross-platform program for tracking money, time, or-    any other commodity, using double-entry accounting and a simple,-    editable file format. It is inspired by and largely compatible-    with ledger(1).  hledger provides command-line, curses and web-    interfaces, and aims to be a reliable, practical tool for daily-    use.+description:    This is hledger's web interface.+                It provides a more user-friendly and collaborative UI than the+                command-line or curses-style interfaces.+                .+                hledger is a cross-platform program for tracking money, time, or+                any other commodity, using double-entry accounting and a simple,+                editable file format. It is inspired by and largely compatible+                with ledger(1).  hledger provides command-line, curses and web+                interfaces, and aims to be a reliable, practical tool for daily+                use. license:        GPL license-file:   LICENSE author:         Simon Michael <simon@joyful.com>@@ -25,7 +25,7 @@ bug-reports:    http://bugs.hledger.org cabal-version:  >= 1.10 build-type:     Simple-tested-with:    GHC==7.6.3, GHC==7.8.4, GHC==7.10.2+tested-with:    GHC==7.10.3, GHC==8.0  extra-source-files:       CHANGES@@ -35,6 +35,7 @@       config/routes       config/settings.yml       messages/en.msg+      README       static/css/bootstrap-theme.css       static/css/bootstrap-theme.css.map       static/css/bootstrap-theme.min.css@@ -92,52 +93,49 @@       static/js/typeahead.bundle.min.js       templates/default-layout-wrapper.hamlet       templates/default-layout.hamlet-      templates/homepage.hamlet-      templates/homepage.julius-      templates/homepage.lucius-      templates/normalize.lucius +data-files:+      doc/hledger-web.1+      doc/hledger-web.1.info+      doc/hledger-web.1.txt+ source-repository head     type: git     location: https://github.com/simonmichael/hledger -flag threaded-  default: True-  description:-    Build with support for multithreaded execution.- flag dev-  default: False-  description:-    Turn on development settings, like auto-reload templates.+    default: False+    description: Turn on development settings, like auto-reload templates.+    manual: False  flag library-only-  default: False-  description:-    Build for use with "yesod devel"+    default: False+    description: Build for use with "yesod devel"+    manual: False -flag old-locale-  default: False-  description:-    A compatibility flag, set automatically by cabal.-    If false then depend on time >= 1.5, -    if true then depend on time < 1.5 together with old-locale.+flag oldtime+    description: If building with time < 1.5, also depend on old-locale. Set automatically by cabal.+    manual: False+    default: False +flag threaded+    default: True+    description: Build with support for multithreaded execution.+    manual: False+ library     ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-orphans-    cpp-options: -DVERSION="0.27"-    if flag(dev) || flag(library-only)-        cpp-options: -DDEVELOPMENT+    cpp-options: -DVERSION="1.0"     build-depends:-          hledger-lib == 0.27-        , hledger == 0.27-        , base               >= 4 && < 5-        , base-compat        >= 0.8.1+          hledger-lib >= 1.0 && < 1.1+        , hledger >= 1.0 && < 1.1+        , base >=4 && <5+        , base-compat >=0.8.1         , blaze-html         , blaze-markup         , bytestring         , clientsession-        , cmdargs            >= 0.10 && < 0.11+        , cmdargs >=0.10 && <0.11         , data-default         , directory         , filepath@@ -145,29 +143,36 @@         , http-conduit         , http-client         , HUnit-        , conduit-extra      >= 1.1-        , parsec             >= 3-        , safe               >= 0.2-        , shakespeare        >= 2.0+        , conduit-extra >=1.1+        , safe >=0.2+        , shakespeare >=2.0         , template-haskell-        , text+        , text >=1.2 && <1.3         , transformers         , wai         , wai-extra-        , wai-handler-launch >= 1.3+        , wai-handler-launch >=1.3         , warp         , yaml-        , yesod              >= 1.4 && < 1.5+        , yesod >=1.4 && <1.5         , yesod-core         , yesod-form         , yesod-static         , json--    if flag(old-locale)-      build-depends: time < 1.5, old-locale+        , megaparsec >=5 && < 5.1+        , mtl+    if (flag(dev)) || (flag(library-only))+        cpp-options: -DDEVELOPMENT+    if flag(oldtime)+        build-depends:+              time <1.5+            , old-locale     else-      build-depends: time >= 1.5-+        build-depends:+              time >=1.5+    if impl(ghc <7.6)+        build-depends:+              ghc-prim     exposed-modules:           Application           Foundation@@ -185,32 +190,26 @@           Settings           Settings.Development           Settings.StaticFiles+    other-modules:+          Paths_hledger_web     default-language: Haskell2010  executable hledger-web-    if flag(library-only)-      buildable: False     main-is: main.hs     hs-source-dirs:           app     ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-orphans-    if flag(threaded)-      ghc-options: -threaded-    if flag(dev)-      ghc-options:   -O0-    cpp-options: -DVERSION="0.27"-    if flag(dev)-      cpp-options: -DDEVELOPMENT+    cpp-options: -DVERSION="1.0"     build-depends:-          hledger-lib == 0.27-        , hledger == 0.27-        , base               >= 4 && < 5-        , base-compat        >= 0.8.1+          hledger-lib >= 1.0 && < 1.1+        , hledger >= 1.0 && < 1.1+        , base >=4 && <5+        , base-compat >=0.8.1         , blaze-html         , blaze-markup         , bytestring         , clientsession-        , cmdargs            >= 0.10 && < 0.11+        , cmdargs >=0.10 && <0.11         , data-default         , directory         , filepath@@ -218,30 +217,42 @@         , http-conduit         , http-client         , HUnit-        , conduit-extra      >= 1.1-        , parsec             >= 3-        , safe               >= 0.2-        , shakespeare        >= 2.0+        , conduit-extra >=1.1+        , safe >=0.2+        , shakespeare >=2.0         , template-haskell-        , text+        , text >=1.2 && <1.3         , transformers         , wai         , wai-extra-        , wai-handler-launch >= 1.3+        , wai-handler-launch >=1.3         , warp         , yaml-        , yesod              >= 1.4 && < 1.5+        , yesod >=1.4 && <1.5         , yesod-core         , yesod-form         , yesod-static         , json-        , hledger-web == 0.27--    if flag(old-locale)-      build-depends: time < 1.5, old-locale+        , parsec >=3+        , hledger-web == 1.0+    if flag(library-only)+        buildable: False+    if flag(threaded)+        ghc-options: -threaded+    if flag(dev)+        ghc-options: -O0+    if flag(dev)+        cpp-options: -DDEVELOPMENT+    if flag(oldtime)+        build-depends:+              time <1.5+            , old-locale     else-      build-depends: time >= 1.5-+        build-depends:+              time >=1.5+    if impl(ghc <7.6)+        build-depends:+              ghc-prim     default-language: Haskell2010  test-suite test@@ -253,17 +264,17 @@     hs-source-dirs:           tests     ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-orphans-    cpp-options: -DVERSION="0.27"+    cpp-options: -DVERSION="1.0"     build-depends:-          hledger-lib == 0.27-        , hledger == 0.27-        , base               >= 4 && < 5-        , base-compat        >= 0.8.1+          hledger-lib >= 1.0 && < 1.1+        , hledger >= 1.0 && < 1.1+        , base >=4 && <5+        , base-compat >=0.8.1         , blaze-html         , blaze-markup         , bytestring         , clientsession-        , cmdargs            >= 0.10 && < 0.11+        , cmdargs >=0.10 && <0.11         , data-default         , directory         , filepath@@ -271,19 +282,18 @@         , http-conduit         , http-client         , HUnit-        , conduit-extra      >= 1.1-        , parsec             >= 3-        , safe               >= 0.2-        , shakespeare        >= 2.0+        , conduit-extra >=1.1+        , safe >=0.2+        , shakespeare >=2.0         , template-haskell-        , text+        , text >=1.2 && <1.3         , transformers         , wai         , wai-extra-        , wai-handler-launch >= 1.3+        , wai-handler-launch >=1.3         , warp         , yaml-        , yesod              >= 1.4 && < 1.5+        , yesod >=1.4 && <1.5         , yesod-core         , yesod-form         , yesod-static@@ -291,10 +301,14 @@         , hledger-web         , hspec         , yesod-test--    if flag(old-locale)-      build-depends: time < 1.5, old-locale+    if flag(oldtime)+        build-depends:+              time <1.5+            , old-locale     else-      build-depends: time >= 1.5-+        build-depends:+              time >=1.5+    if impl(ghc <7.6)+        build-depends:+              ghc-prim     default-language: Haskell2010
static/hledger.css view
@@ -4,174 +4,6 @@ /* 1. colours */  /* green */-body                                                                 { background-color:white; color:black; }-.registerreport .odd                                                 { background-color:#ded; }-/* .transactionsreport .odd                                             { background-color:#eee; } */-.filtering                                                           { background-color:#e0e0e0; }-a:link, a:visited { color:#00e; }-/* a:link:hover, a:visited:hover { color:red; } */-/* #main                                                                { border-color:#e0e0e0; } see below */-/* .journalreport td                                                    { border-color:thin solid #e0e0e0; } see below */--/* white */-/* body                                                                 { background-color:#fff; } */-/* .registerreport .odd                                                 { background-color:#eee; } */-/* .filtering                                                           { background-color:#ddd; } */-/* #main                                                                { border-color:#eee; } see below */-/* .journalreport td                                                    { border-color:thin solid #eee; } see below */--#message                                                             { color:red; background-color:#fee; }-/* #addform input.textinput, #addform .dhx_combo_input, .dhx_combo_list { /\*background-color:#eee;*\/ } */-#editform textarea                                                   { background-color:#eee; }-.negative                                                            { color:#800; }-.help                                                                {  }--#sidebar .hoverlinks { visibility:hidden; }-/* #sidebar .mouseover { background-color:rgba(208,208,208,0.5); } */-#sidebar .mouseover .hoverlinks { visibility:visible; }--#sidebar .balancereport .hoverlinks { margin-left:0em; font-weight:normal; /*font-size:smaller;*/ display:inline-block; text-align:right; }-#sidebar .balancereport .hoverlinks a { margin-left:0.5em; }-/* #sidebar .notinacct, .notinacct :link, .notinacct :visited { color:#888; } */-#sidebar .notinacct .negative { color:#b77; }-#sidebar .balancereport .inacct { font-weight:bold; }-/* #sidebar .balancereport .inacct { background-color:#e0e0e0; } */-#sidebar .balancereport .numpostings                                          { padding-left:1em; color:#aaa; }-#sidebar .current { font-weight:bold; }--/*------------------------------------------------------------------------------------------*/-/* 2. font families & sizes */-/* overspecified for cross-browser robustness */-body { font-size:16px; }-/*-body                                               { font-family:helvetica,arial,sans-serif; }-pre                                                { font-family:courier,"courier new",monospace; }-.dhx_combo_input, .dhx_combo_list { font-size:small; }-#editform textarea                                 { font-family:courier,"courier new",monospace; font-size:small; }-.nav2                                              { font-size:small; }-#searchform                                        { font-size:small; }-.topleftlink                                       { font-size:small; }-.toprightlink                                      { font-size:small; }-#journaldesc                                       { font-size:small; }-.togglelink                                        { font-size:smaller; white-space:nowrap }-.help                                              { font-size:smaller; }-.form                                              { font-size:small; }-.transactionsreport                                { font-size:small; }-.entriesreport                                     { font-size:small; }-.balancereport                                     { font-size:small; }-.registerreport                                    { font-size:small; }-.showall                                           { font-size:small; }-*/-/* #addformlink                                    { font-size:small; } */-/* #editformlink                                   { font-size:small; } */-/*-#contenttitle                                    { font-size:1.2em; }-*/--/*------------------------------------------------------------------------------------------*/-/* 3. layout */--body                        { margin:0; }---#topbar                     { padding:2px; }-.topleftlink                { float:left; margin-right:1em; padding:2px; }-.toprightlink               { float:right; margin-left:1em; padding:2px; }-#topbar h1                  { display:inline-block; vertical-align:top; margin:0; }-#journalinfo                { vertical-align:middle; margin:0; }-/* #topbar                  { padding:4px; border-bottom:2px solid #ddd; } */--#message                    { margin:0.5em;}-.help                       { font-style: italic; }-.helprow td                 { padding-bottom:8px; }--#outermain { overflow:auto; }-#main { overflow:auto; padding-left:1em; }--#sidebar {-  float:left;-  padding-right:1em; -  border-right:thin solid #e0e0e0;-  margin-bottom:5em;-}-/* #sidebar.affix { */-/*     position: fixed; */-/*     top: 20px; */-/* } */--.balancereport .item { border-top:thin solid #e0e0e0; }--#navlinks                   { margin-bottom:1em; }-.navlink                    { }-.navlinkcurrent             { font-weight:bold; }-.form                       { margin:0em; }--#searchformdiv              { margin:0 0 1em 0; }-#searchform                 { margin:0; }-#searchform span            { padding:4px; }-#stopfilterlink             { font-weight:bold; }-.filtering                  { font-weight:bold; }--#main .journal { }-#main .register { }-.current                    { font-weight:bold; }-.date                       { padding-left:0em; }-.description                { padding-left:1em; white-space:normal; }-.account                    { padding-left:1em; white-space:normal; }-.amount                     { padding-left:1em; white-space:nowrap; }-.balance                    { padding-left:1em; padding-right:0.3em; white-space:nowrap; }-.amount, .balance { width:2em; }  /* minimise width */-.positive                   { }--/* table.transactionsreport         { border-spacing: 0; } */-/* .transactionsreport td           {  } */-/* .transactionsreport pre          { margin-top:0; } */--/* table.entriesreport         { border-spacing: 0; } */-/* .entriesreport td           {  } */-/* .entriesreport pre          { margin-top:0; } */--.balancereport              { border-spacing:0; }-.balancereport tr           { vertical-align:bottom; border-spacing:0; }-.balancereport .title       { white-space:nowrap; }-.balancereport .item        { }-/* .balancereport .depth0      { padding-top:1em; } */-.balancereport td           { padding:0; }-.totalrule td               { border-top:thin solid black; }-.balancereport .account     { white-space:nowrap; }--.hidden { display:none; }-table.registerreport        { border-spacing:0; }-table.registerreport tr     { vertical-align:top; }-table.registerreport td     { padding-bottom:0.2em; }-table.registerreport .date  { white-space:nowrap; }-table.registerreport tr.posting { font-size:smaller; }-table.registerreport tr.posting .account  { padding-left:1.5em; }-table.registerreport tr.posting .amount  { padding-right:0.5em; }-tr.firstposting td          { }-/* tr.newday td                { border-top: 1px solid #797; } */-/* tr.newday .date             { font-weight:bold; } */-/* tr.newmonth td              { border-top: 2px solid #464; } */-/* tr.newyear td               { border-top: 3px solid black; } */-#accountsheading            { white-space:nowrap; }---#addform {-  /* margin:0 0 2em; */-  /* padding:.5em 0; */-  /* border-top:thin solid #e0e0e0; */-  /* border-bottom:thin solid #e0e0e0; */-}-#addform tr {-  vertical-align:top;-}-/* #addform input.textinput, #addform .dhx_combo_input, .dhx_combo_list   { padding:4px; } */-/* #addform table              { } */-/* #addform #addbuttonrow      { text-align:right; } */-/* #editform                   { width:95%; } */-#editform textarea          { width:100%; padding:4px; }-/* #searchform table           { border-spacing:0; padding-left:0em; } */- ::-moz-placeholder {   font-style:italic; }@@ -259,4 +91,189 @@  .twitter-typeahead {   width:100%;+}++code {+  font-weight: bold;+  color: black;+}++ul {+    list-style-type: none;+	  padding: 0;+}++#main-content {+    padding-left: 30px;+}++#sidebar-menu {+    overflow:hidden;+    border-right: 1px solid #ebebeb;+}+#sidebar-menu .main-menu {+    table-layout: fixed;+    word-wrap: break-word;+}++#sidebar-menu .main-menu td {+    padding: 1px !important;+    border-top: 1px solid #ebebeb;+    overflow: hidden;+    white-space:nowrap;+    text-overflow:ellipsis;+    font-size: 16px;+}++#sidebar-menu .main-menu .ff-wrapper { /* This wrapper is needed because firefox won't apply overflow to a td-tag */+    overflow:hidden;+    text-overflow:ellipsis;+}++#sidebar-menu .main-menu .top {+    border: none !important;+}++#sidebar-menu .main-menu a {+    display: inline;+	  font-size: 16px;+	  font-weight: 500;+	  color: #2F2F2F;+	  padding: 4px 20px;+}++#sidebar-menu .main-menu a:hover {+	  color: #11427D;+	  text-decoration: none;+	  background-color: transparent;+}++#sidebar-menu .main-menu .only{+    visibility: hidden;+    padding: 1px;+}++#sidebar-menu .main-menu tr:hover > td > div > .only {+    visibility: visible;+}++#sidebar-menu .main-menu .only:hover{+    border-left: none;+}+#sidebar-menu .main-menu .balance {+    float: right;+}++#sidebar-menu .main-menu .total {+    border-left: none;+    border-right: none;+    border-bottom: none;+    border-top: 1px solid black;+}++#sidebar-menu .main-menu .inacct {+    font-weight: bold;+	  color: #11427D;+    background-color: #f9f9f9;+}++#sidebar-menu .main-menu .amount {+    float: right;+    overflow-x:auto;+	  font-weight: 500 !important;+}++#sidebar-menu .main-menu .acct {+    width:60%;+    vertical-align:bottom;+}++.transactionsreport .nonhead {+    border: none !important;+}++.negative {+    color: #a94442;+}++.date {+    whitespace: nowrap;+}++#main-content {+   /* +    -webkit-transition: width 0.3s ease, margin 0.3s ease;+    -moz-transition: width 0.3s ease, margin 0.3s ease;+    -o-transition: width 0.3s ease, margin 0.3s ease;+    transition: width 0.3s ease, margin 0.3s ease;+*/+}++#sidebar-menu {+    /*+    -webkit-transition: width 0.3s ease, margin 0.3s ease,opacity 0.3s ease,height 1s ease 1s;+    -moz-transition: width 0.3s ease, margin 0.3s ease,opacity 0.3s ease,height 1s ease 1s;+    -o-transition: width 0.3s ease, margin 0.3s ease,opacity 0.3s ease,height 1s ease 1s;+    transition: width 0.3s ease, margin 0.3s ease,opacity 0.3s ease,height 1s ease 1s;+*/+}++.col-any-0 {+    width:0 !important;+    height:0 !important;+    float: left;+    opacity:0;+}++.tgl-icon {+    font-size:large;+}++#searchbar {+    width: 100% !important;+}++@media screen and (max-width: 768px) {+    .row-offcanvas {+        position: relative;+        -webkit-transition: all .25s ease-out;+        -o-transition: all .25s ease-out;+        transition: all .25s ease-out;+    }++    .row-offcanvas-right {+        right: 0;+    }++    .row-offcanvas-left {+        left: 0;+    }++    .row-offcanvas-right+    .sidebar-offcanvas {+        right: -90%;+    }++    .row-offcanvas-left+    .sidebar-offcanvas {+        left: -90%;+    }++    .row-offcanvas-right.active {+        right: 90%;+    }++    .row-offcanvas-left.active {+        left: 90%;+    }++    .sidebar-offcanvas {+        position: absolute;+        top: 0;+        width: 90%;+    }++    #sidebar-menu .main-menu td {+        max-width: 200px;+    } }
static/hledger.js view
@@ -5,6 +5,12 @@  $(document).ready(function() { +  // ensure add form always focusses its first field+  $('#addmodal')+    .on('shown.bs.modal', function (e) {+      addformFocus();+    })+   // show add form if ?add=1   if ($.url.param('add')) { addformShow(true); } @@ -24,8 +30,20 @@   $('body, #addform input, #addform select').bind('keydown', 'ctrl+shift+=', addformAddPosting);   $('body, #addform input, #addform select').bind('keydown', 'ctrl+=',       addformAddPosting);   $('body, #addform input, #addform select').bind('keydown', 'ctrl+-',       addformDeletePosting);-  $('#addform tr.posting:last > td:first input').bind('keydown', 'tab', addformAddPostingWithTab);+  $('.amount-input:last').keypress(addformAddPosting); ++  // highlight the entry from the url hash+  if (window.location.hash && $(window.location.hash)[0]) {+    $(window.location.hash).addClass('highlighted');+  }+  $(window).on('hashchange', function(event) {+    $('.highlighted').removeClass('highlighted');+    $(window.location.hash).addClass('highlighted');+  });+  $('[data-toggle="offcanvas"]').click(function () {+      $('.row-offcanvas').toggleClass('active');+  }); });  //----------------------------------------------------------------------@@ -36,58 +54,39 @@   return $container.plot(     series,     { /* general chart options */-      // series: {-      // },-      // yaxis: {-      //   /* ticks: 6, */-      // },       xaxis: {         mode: "time",         timeformat: "%Y/%m/%d"-        /* ticks: 6, */       },+      legend: {+        position: 'sw'+      },       grid: {         markings:          function (axes) {           var now = Date.now();           var markings = [-            // {-            //   xaxis: { to: now },        // past-            //   yaxis: { from: 0, to: 0 }, // =0-            //  color: '#d88',-            //  lineWidth:1-            // },             {               xaxis: { to: now }, // past               yaxis: { to: 0 },   // <0               color: '#ffdddd',             },--            // {-            //   xaxis: { from: now, to: now }, // now-            //  color: '#bbb',-            // },-             {               xaxis: { from: now }, // future               yaxis: { from: 0 },   // >0-              // color: '#dddddd',               color: '#e0e0e0',             },             {               xaxis: { from: now }, // future               yaxis: { to: 0 },     // <0-              // color: '#ddbbbb',               color: '#e8c8c8',             },             {-              // xaxis: { from: now },      // future               yaxis: { from: 0, to: 0 }, // =0               color: '#bb0000',               lineWidth:1             },           ];-          // console.log(markings);           return markings;         },         hoverable: true,@@ -113,13 +112,14 @@  function registerChartClick(ev, pos, item) {   if (item) {-    var date = $.plot.dateGenerator(item.datapoint[0], {});-    var dateid = $.plot.formatDate(date, '%Y-%m-%d');-    $target = $('#'+dateid);-    if ($target.length)+    targetselector = '#'+item.series.data[item.dataIndex][5];+    $target = $(targetselector);+    if ($target.length) {+      window.location.hash = targetselector;       $('html, body').animate({         scrollTop: $target.offset().top       }, 1000);+    }   } } @@ -129,11 +129,7 @@ function addformShow(showmsg) {   showmsg = typeof showmsg !== 'undefined' ? showmsg : false;   addformReset(showmsg);-  $('#addmodal')-    .on('shown.bs.modal', function (e) {-      addformFocus();-    })-    .modal('show');+  $('#addmodal').modal('show'); }  // Make sure the add form is empty and clean for display.@@ -145,7 +141,7 @@     // reset typehead state (though not fetched completions)     $('.typeahead').typeahead('val', '');     $('.tt-dropdown-menu').hide();-    $('input#date').val('today');+    $('input#date').val(''); // #322 don't set a default, typeahead(?) clears it on tab. See also Foundation.hs   } } @@ -163,44 +159,21 @@  // Insert another posting row in the add form. function addformAddPosting() {+  $('.amount-input:last').off('keypress');   // do nothing if it's not currently visible   if (!$('#addform').is(':visible')) return;   // save a copy of last row-  var lastrow = $('#addform tr.posting:last').clone();+  var lastrow = $('#addform .form-group:last').clone();    // replace the submit button with an amount field, clear and renumber it, add the keybindings-  $('#addform tr.posting:last > td:last')-    .html( $('#addform tr.posting:first > td:last').html() );-  var num = $('#addform tr.posting').length;-  $('#addform tr.posting:last > td:last input:last') // input:last here and elsewhere is to avoid autocomplete's extra input-    .val('')-    .prop('id','amount'+num)-    .prop('name','amount'+num)-    .prop('placeholder','Amount '+num)-    .bind('keydown', 'ctrl+shift+=', addformAddPosting)-    .bind('keydown', 'ctrl+=', addformAddPosting)-    .bind('keydown', 'ctrl+-', addformDeletePosting);--  // set up the new last row's account field.-  // First typehead, it's hard to enable on new DOM elements-  var $acctinput = lastrow.find('.account-input:last');-  // XXX nothing works-  // $acctinput.typeahead('destroy'); //,'NoCached');-  // lastrow.on("DOMNodeInserted", function () {-  //   //$(this).find(".typeahead").typeahead();-  //   console.log('DOMNodeInserted');-  //  // infinite loop-  //  console.log($(this).find('.typeahead'));-  //   //enableTypeahead($(this).find('.typeahead'), accountsSuggester);-  // });-  // setTimeout(function (){-  //   $('#addform tr.posting:last input.account-input').typeahead('destroy');-  //   enableTypeahead($('#addform tr.posting:last input.account-input:last'), accountsSuggester);-  // }, 1000);+  var num = $('#addform .account-group').length;    // insert the new last row-  $('#addform > table > tbody').append(lastrow);+  $('#addform .account-postings').append(lastrow);+  // TODO: Enable typehead on dynamically created inputs +  var $acctinput = $('.account-input:last');+  var $amntinput = $('.amount-input:last');   // clear and renumber the field, add keybindings   $acctinput     .val('')@@ -211,77 +184,47 @@   $acctinput     .bind('keydown', 'ctrl+shift+=', addformAddPosting)     .bind('keydown', 'ctrl+=', addformAddPosting)-    .bind('keydown', 'ctrl+-', addformDeletePosting)-    .bind('keydown', 'tab', addformAddPostingWithTab);-}+    .bind('keydown', 'ctrl+-', addformDeletePosting); -// Insert another posting row by tabbing within the last field, also advancing the focus.-function addformAddPostingWithTab(ev) {-  // do nothing if called from a non-last row (don't know how to remove keybindings)-  if ($(ev.target).is('#addform input.account-input:last')) {-    addformAddPosting();-    focus($('#addform input.amount-input:last')); // help FF-    return false;-  }-  else-    return true;+  $amntinput+    .val('')+    .prop('id','amount'+(num+1))+    .prop('name','amount'+(num+1))+    .prop('placeholder','Amount '+(num+1))+    .keypress(addformAddPosting);++  $acctinput+    .bind('keydown', 'ctrl+shift+=', addformAddPosting)+    .bind('keydown', 'ctrl+=', addformAddPosting)+    .bind('keydown', 'ctrl+-', addformDeletePosting);+ }  // Remove the add form's last posting row, if empty, keeping at least two. function addformDeletePosting() {-  var num = $('#addform tr.posting').length;-  if (num <= 2-      || $('#addform tr.posting:last > td:first input:last').val() != ''-     ) return;-  // copy submit button-  var btn = $('#addform tr.posting:last > td:last').html();+  var num = $('#addform .account-group').length;+  if (num <= 2) return;   // remember if the last row's field or button had focus   var focuslost =-    $('#addform tr.posting:last > td:first input:last').is(':focus')-    || $('#addform tr.posting:last button').is(':focus');+    $('.account-input:last').is(':focus')+    || $('.amount-input:last').is(':focus');   // delete last row-  $('#addform tr.posting:last').remove();-  // remember if the last amount field had focus-  focuslost = focuslost || -    $('#addform tr.posting:last > td:last input:last').is(':focus');-  // replace new last row's amount field with the button-  $('#addform tr.posting:last > td:last').css('text-align','right').html(btn);-  // if deleted row had focus, focus the new last row-  if (focuslost) $('#addform tr.posting:last > td:first input:last').focus();+  $('#addform .account-group:last').remove();+  if(focuslost){+    focus($('account-input:last'));+  }+  // Rebind keypress+  $('.amount-input:last').keypress(addformAddPosting); }  //---------------------------------------------------------------------- // SIDEBAR  function sidebarToggle() {-  //console.log('sidebarToggle');-  var visible = $('#sidebar').is(':visible');-  //console.log('sidebar visibility was',visible);-  // if opening sidebar, start an ajax fetch of its content-  if (!visible) {-    //console.log('getting sidebar content');-    $.get("sidebar"-         ,null-         ,function(data) {-            //console.log( "success" );-            $("#sidebar-body" ).html(data);-          })-          .done(function() {-            //console.log( "success 2" );-          })-          .fail(function() {-            //console.log( "error" );-          });-  }-  // localStorage.setItem('sidebarVisible', !visible);-  // set a cookie to communicate the new sidebar state to the server-  $.cookie('showsidebar', visible ? '0' : '1');-  // horizontally slide the sidebar in or out-  // how to make it smooth, without delayed content pop-in ?-  //$('#sidebar').animate({'width': 'toggle'});-  //$('#sidebar').animate({'width': visible ? 'hide' : '+=20m'});-  //$('#sidebar-spacer').width(200);-  $('#sidebar').animate({'width': visible ? 'hide' : 'show'});+  $('#sidebar-menu').toggleClass('col-md-4 col-sm-4 col-any-0');+  $('#main-content').toggleClass('col-md-8 col-sm-8 col-md-12 col-sm-12');+  $('#spacer').toggleClass('col-md-4 col-sm-4 col-any-0');+  $.cookie('showsidebar', $('#sidebar-menu').hasClass('col-any-0') ? '0' : '1'); }  //----------------------------------------------------------------------
templates/default-layout-wrapper.hamlet view
@@ -25,9 +25,8 @@           document.hledgerWebBaseurl = '#{base_url_ opts}'; // save for binding keys later      <body>-        <div class="container">-            <header>-            <div id="outermain" role="main">+        <div .container-fluid>+            <div .row .row-offcanvas .row-offcanvas-left>               ^{pageBody pc}             <footer>                 #{extraCopyright $ appExtra $ settings master}@@ -61,11 +60,11 @@                     <p>                       <b>Keyboard shortcuts                       <ul>-                        <li> <b><tt>h</tt></b> or maybe <b><tt>?</tt></b> - view this help (escape or click to exit)-                        <li> <b><tt>j</tt></b> - go to the Journal view (home)-                        <li> <b><tt>a</tt></b> - add a transaction (escape to cancel)-                        <li> <b><tt>s</tt></b> - toggle sidebar-                        <li> <b><tt>f</tt></b> - focus search form ("find")+                        <li> <code>h</code> or maybe <code>?</code> - view this help (escape or click to exit)+                        <li> <code>j</code> - go to the Journal view (home)+                        <li> <code>a</code> - add a transaction (escape to cancel)+                        <li> <code>s</code> - toggle sidebar+                        <li> <code>f</code> - focus search form ("find")                     <p>                       <b>General                       <ul>@@ -79,19 +78,19 @@                     <p>                       <b>Search                       <ul>-                        <li> <b><tt>acct:REGEXP</tt></b> - filter on to/from account-                        <li> <b><tt>desc:REGEXP</tt></b> - filter on description-                        <li> <b><tt>date:PERIODEXP</tt></b>, <b><tt>date2:PERIODEXP</tt></b> - filter on date or secondary date-                        <li> <b><tt>code:REGEXP</tt></b> - filter on transaction's code (eg check number)-                        <li> <b><tt>status:*</tt></b>, <b><tt>status:!</tt></b>, <b><tt>status:</tt></b> - filter on transaction's cleared status (cleared, pending, uncleared)-                        <!-- <li> <b><tt>empty:BOOL</tt></b> - filter on whether amount is zero -->-                        <li> <b><tt>amt:N</tt></b>, <b><tt>amt:&lt;N</tt></b>, <b><tt>amt:&gt;N</tt></b> - filter on the unsigned amount magnitude. Or with a sign before N, filter on the signed value. (Single-commodity amounts only.)-                        <li> <b><tt>cur:REGEXP</tt></b> - filter on the currency/commodity symbol (must match all of it). Dollar sign must be written as <tt>\$</tt>-                        <li> <b><tt>tag:NAME</tt></b>, <b><tt>tag:NAME=REGEX</tt></b> - filter on tag name, or tag name and value-                        <!-- <li> <b><tt>depth:N</tt></b> - filter out accounts below this depth -->-                        <li> <b><tt>real:BOOL</tt></b> - filter on postings' real/virtual-ness+                        <li> <code>acct:REGEXP</code> - filter on to/from account+                        <li> <code>desc:REGEXP</code> - filter on description+                        <li> <code>date:PERIODEXP</code>, <code>date2:PERIODEXP</code> - filter on date or secondary date+                        <li> <code>code:REGEXP</code> - filter on transaction's code (eg check number)+                        <li> <code>status:*</code>, <code>status:!</code>, <code>status:</code> - filter on transaction's cleared status (cleared, pending, uncleared)+                        <!-- <li> <code>empty:BOOL</code> - filter on whether amount is zero -->+                        <li> <code>amt:N</code>, <code>amt:&lt;N</code>, <code>amt:&gt;N</code> - filter on the unsigned amount magnitude. Or with a sign before N, filter on the signed value. (Single-commodity amounts only.)+                        <li> <code>cur:REGEXP</code> - filter on the currency/commodity symbol (must match all of it). Dollar sign must be written as <code>\$</code>+                        <li> <code>tag:NAME</code>, <code>tag:NAME=REGEX</code> - filter on tag name, or tag name and value+                        <!-- <li> <code>depth:N</code> - filter out accounts below this depth -->+                        <li> <code>real:BOOL</code> - filter on postings' real/virtual-ness                         <li> Enclose search patterns containing spaces in single or double quotes-                        <li> Prepend <b><tt>not:</tt></b> to negate a search term+                        <li> Prepend <code>not:</code> to negate a search term                         <li> Multiple search terms on different fields are AND'ed, multiple search terms on the same field are OR'ed                         <li> These search terms also work with command-line hledger 
− templates/homepage.hamlet
@@ -1,38 +0,0 @@-<h1>_{MsgHello}--<ol>-  <li>Now that you have a working project you should use the #-    \<a href="http://www.yesodweb.com/book/">Yesod book</a> to learn more. #-    You can also use this scaffolded site to explore some basic concepts.--  <li> This page was generated by the #{handlerName} handler in #-    \<em>Handler/Home.hs</em>.--  <li> The #{handlerName} handler is set to generate your site's home screen in Routes file #-    <em>config/routes--  <li> The HTML you are seeing now is actually composed by a number of <em>widgets</em>, #-    most of them are brought together by the <em>defaultLayout</em> function which #-    is defined in the <em>Foundation.hs</em> module, and used by <em>#{handlerName}</em>. #-    All the files for templates and wigdets are in <em>templates</em>.--  <li>-    A Widget's Html, Css and Javascript are separated in three files with the #-    \<em>.hamlet</em>, <em>.lucius</em> and <em>.julius</em> extensions. --  <li ##{aDomId}>If you had javascript enabled then you wouldn't be seeing this.-    -  <li #form>-    This is an example trivial Form. Read the #-    \<a href="http://www.yesodweb.com/book/forms">Forms chapter</a> #-    on the yesod book to learn more about them.-    $maybe (info,con) <- submission-      <div .message>-        Your file's type was <em>#{fileContentType info}</em>. You say it has: <em>#{con}</em>-    <form method=post action=@{HomeR}#form enctype=#{formEnctype}>-      ^{formWidget}-      <input type="submit" value="Send it!">--  <li> And last but not least, Testing. In <em>tests/main.hs</em> you will find a #-    test suite that performs tests on this page. #-    You can run your tests by doing: <pre>yesod test</pre>
− templates/homepage.julius
@@ -1,1 +0,0 @@-document.getElementById("#{aDomId}").innerHTML = "This text was added by the Javascript part of the homepage widget.";
− templates/homepage.lucius
@@ -1,6 +0,0 @@-h1 {-    text-align: center-}-h2##{aDomId} {-    color: #990-}
− templates/normalize.lucius
@@ -1,439 +0,0 @@-/*! normalize.css 2011-08-12T17:28 UTC · http://github.com/necolas/normalize.css */--/* =============================================================================-   HTML5 display definitions-   ========================================================================== */--/*- * Corrects block display not defined in IE6/7/8/9 & FF3- */--article,-aside,-details,-figcaption,-figure,-footer,-header,-hgroup,-nav,-section {-    display: block;-}--/*- * Corrects inline-block display not defined in IE6/7/8/9 & FF3- */--audio,-canvas,-video {-    display: inline-block;-    *display: inline;-    *zoom: 1;-}--/*- * Prevents modern browsers from displaying 'audio' without controls- */--audio:not([controls]) {-    display: none;-}--/*- * Addresses styling for 'hidden' attribute not present in IE7/8/9, FF3, S4- * Known issue: no IE6 support- */--[hidden] {-    display: none;-}---/* =============================================================================-   Base-   ========================================================================== */--/*- * 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units- *    http://clagnut.com/blog/348/#c790- * 2. Keeps page centred in all browsers regardless of content height- * 3. Prevents iOS text size adjust after orientation change, without disabling user zoom- *    www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/- */--html {-    font-size: 100%; /* 1 */-    overflow-y: scroll; /* 2 */-    -webkit-text-size-adjust: 100%; /* 3 */-    -ms-text-size-adjust: 100%; /* 3 */-}--/*- * Addresses margins handled incorrectly in IE6/7- */--body {-    margin: 0;-}--/* - * Addresses font-family inconsistency between 'textarea' and other form elements.- */--body,-button,-input,-select,-textarea {-    font-family: sans-serif;-}---/* =============================================================================-   Links-   ========================================================================== */--a {-    color: #00e;-}--a:visited {-    color: #551a8b;-}--/*- * Addresses outline displayed oddly in Chrome- */--a:focus {-    outline: thin dotted;-}--/*- * Improves readability when focused and also mouse hovered in all browsers- * people.opera.com/patrickl/experiments/keyboard/test- */--a:hover,-a:active {-    outline: 0;-}---/* =============================================================================-   Typography-   ========================================================================== */--/*- * Addresses styling not present in IE7/8/9, S5, Chrome- */--abbr[title] {-    border-bottom: 1px dotted;-}--/*- * Addresses style set to 'bolder' in FF3/4, S4/5, Chrome-*/--b, -strong { -    font-weight: bold; -}--blockquote {-    margin: 1em 40px;-}--/*- * Addresses styling not present in S5, Chrome- */--dfn {-    font-style: italic;-}--/*- * Addresses styling not present in IE6/7/8/9- */--mark {-    background: #ff0;-    color: #000;-}--/*- * Corrects font family set oddly in IE6, S4/5, Chrome- * en.wikipedia.org/wiki/User:Davidgothberg/Test59- */--pre,-code,-kbd,-samp {-    font-family: monospace, serif;-    _font-family: 'courier new', monospace;-    font-size: 1em;-}--/*- * Improves readability of pre-formatted text in all browsers- */--pre {-    white-space: pre;-    white-space: pre-wrap;-    word-wrap: break-word;-}--/*- * 1. Addresses CSS quotes not supported in IE6/7- * 2. Addresses quote property not supported in S4- */--/* 1 */--q {-    quotes: none;-}--/* 2 */--q:before,-q:after {-    content: '';-    content: none;-}--small {-    font-size: 75%;-}--/*- * Prevents sub and sup affecting line-height in all browsers- * gist.github.com/413930- */--sub,-sup {-    font-size: 75%;-    line-height: 0;-    position: relative;-    vertical-align: baseline;-}--sup {-    top: -0.5em;-}--sub {-    bottom: -0.25em;-}---/* =============================================================================-   Lists-   ========================================================================== */--ul,-ol {-    margin: 1em 0;-    padding: 0 0 0 40px;-}--dd {-    margin: 0 0 0 40px;-}--nav ul,-nav ol {-    list-style: none;-    list-style-image: none;-}---/* =============================================================================-   Embedded content-   ========================================================================== */--/*- * 1. Removes border when inside 'a' element in IE6/7/8/9- * 2. Improves image quality when scaled in IE7- *    code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/- */--img {-    border: 0; /* 1 */-    -ms-interpolation-mode: bicubic; /* 2 */-}--/*- * Corrects overflow displayed oddly in IE9 - */--svg:not(:root) {-    overflow: hidden;-}---/* =============================================================================-   Figures-   ========================================================================== */--/*- * Addresses margin not present in IE6/7/8/9, S5, O11- */--figure {-    margin: 0;-}---/* =============================================================================-   Forms-   ========================================================================== */--/*- * Corrects margin displayed oddly in IE6/7- */--form {-    margin: 0;-}--/*- * Define consistent margin and padding- */--fieldset {-    margin: 0 2px;-    padding: 0.35em 0.625em 0.75em;-}--/*- * 1. Corrects color not being inherited in IE6/7/8/9- * 2. Corrects alignment displayed oddly in IE6/7- */--legend {-    border: 0; /* 1 */-    *margin-left: -7px; /* 2 */-}--/*- * 1. Corrects font size not being inherited in all browsers- * 2. Addresses margins set differently in IE6/7, F3/4, S5, Chrome- * 3. Improves appearance and consistency in all browsers- */--button,-input,-select,-textarea {-    font-size: 100%; /* 1 */-    margin: 0; /* 2 */-    vertical-align: baseline; /* 3 */-    *vertical-align: middle; /* 3 */-}--/*- * 1. Addresses FF3/4 setting line-height using !important in the UA stylesheet- * 2. Corrects inner spacing displayed oddly in IE6/7- */--button,-input {-    line-height: normal; /* 1 */-    *overflow: visible;  /* 2 */-}--/*- * Corrects overlap and whitespace issue for buttons and inputs in IE6/7- * Known issue: reintroduces inner spacing- */--table button,-table input {-    *overflow: auto;-}--/*- * 1. Improves usability and consistency of cursor style between image-type 'input' and others- * 2. Corrects inability to style clickable 'input' types in iOS- */--button,-html input[type="button"], -input[type="reset"], -input[type="submit"] {-    cursor: pointer; /* 1 */-    -webkit-appearance: button; /* 2 */-}--/*- * 1. Addresses box sizing set to content-box in IE8/9- * 2. Addresses excess padding in IE8/9- */--input[type="checkbox"],-input[type="radio"] {-    box-sizing: border-box; /* 1 */-    padding: 0; /* 2 */-}--/*- * 1. Addresses appearance set to searchfield in S5, Chrome- * 2. Addresses box sizing set to border-box in S5, Chrome (include -moz to future-proof)- */--input[type="search"] {-    -webkit-appearance: textfield; /* 1 */-    -moz-box-sizing: content-box;-    -webkit-box-sizing: content-box; /* 2 */-    box-sizing: content-box;-}--/*- * Corrects inner padding displayed oddly in S5, Chrome on OSX- */--input[type="search"]::-webkit-search-decoration {-    -webkit-appearance: none;-}--/*- * Corrects inner padding and border displayed oddly in FF3/4- * www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/- */--button::-moz-focus-inner,-input::-moz-focus-inner {-    border: 0;-    padding: 0;-}--/*- * 1. Removes default vertical scrollbar in IE6/7/8/9- * 2. Improves readability and alignment in all browsers- */--textarea {-    overflow: auto; /* 1 */-    vertical-align: top; /* 2 */-}---/* =============================================================================-   Tables-   ========================================================================== */--/* - * Remove most spacing between table cells- */--table {-    border-collapse: collapse;-    border-spacing: 0;-}