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 +48/−1
- Foundation.hs +84/−88
- Handler/AddForm.hs +9/−9
- Handler/Common.hs +62/−76
- Handler/JournalR.hs +30/−34
- Handler/RegisterR.hs +33/−26
- Hledger/Web/Main.hs +35/−24
- Hledger/Web/WebOptions.hs +1/−2
- README +1/−0
- app/main.hs +2/−2
- doc/hledger-web.1 +319/−0
- doc/hledger-web.1.info +199/−0
- doc/hledger-web.1.txt +237/−0
- hledger-web.cabal +113/−99
- static/hledger.css +185/−168
- static/hledger.js +63/−120
- templates/default-layout-wrapper.hamlet +19/−20
- templates/homepage.hamlet +0/−38
- templates/homepage.julius +0/−1
- templates/homepage.lucius +0/−6
- templates/normalize.lucius +0/−439
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">×- <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- - <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)) " "- 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> - <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> - <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:<N</tt></b>, <b><tt>amt:>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:<N</code>, <code>amt:>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;-}