packages feed

hledger-web 1.17.1 → 1.18

raw patch · 17 files changed

+638/−173 lines, 17 filesdep +unordered-containersdep ~aesondep ~hledgerdep ~hledger-libPVP ok

version bump matches the API change (PVP)

Dependencies added: unordered-containers

Dependency ranges changed: aeson, hledger, hledger-lib

API changes (from Hackage documentation)

+ Hledger.Web.Import: setDescription :: MonadWidget m => Text -> m ()
+ Hledger.Web.Import: setDescriptionI :: (MonadWidget m, RenderMessage (HandlerSite m) msg) => msg -> m ()
+ Hledger.Web.Import: setOGImage :: MonadWidget m => Text -> m ()
+ Hledger.Web.Import: setOGType :: MonadWidget m => Text -> m ()
+ Hledger.Web.Widget.Common: removeInacct :: Text -> [Text]
+ Hledger.Web.Widget.Common: replaceInacct :: Text -> Text -> Text
+ Hledger.Web.Widget.Common: transactionFragment :: Journal -> Transaction -> String
- Hledger.Web.Widget.Common: balanceReportAsHtml :: Eq r => (r, r) -> r -> Bool -> Journal -> [QueryOpt] -> BalanceReport -> HtmlUrl r
+ Hledger.Web.Widget.Common: balanceReportAsHtml :: Eq r => (r, r) -> r -> Bool -> Journal -> Text -> [QueryOpt] -> BalanceReport -> HtmlUrl r

Files

CHANGES.md view
@@ -1,15 +1,27 @@ User-visible changes in hledger-web. See also the hledger changelog. +# 1.18 2020-06-07 +- Hyperlinks are now more robust when there are multiple journal+  files, eg links from register to journal now work properly. (#1041)++## add form++- Fixed a 2016 regression causing too many rows to be added by+  keypresses in the last amount field or CTRL-plus (#422, #1059).++- Always start with four rows when opened.++- Drop unneeded C-minus/C-plus keys & related help text.++ # 1.17.1 2020-03-19 -- require newer Decimal, math-functions, fixing inconsistent rounding-  Decimal 0.5.1+ changed to banker's rounding (round to nearest even-  number), and math-functions 0.3.3.0 (used by roi) fixed various-  precision-related issues. Now we require the latest versions of these.-  This was causing some functional test failures when building with old-  GHCs/snapshots.+- require newer Decimal, math-functions libs to ensure consistent+  rounding behaviour, even when built with old GHCs/snapshots. +  hledger uses banker's rounding (rounds to nearest even number, eg+  0.5 displayed with zero decimal places is "0").  # 1.17 2020-03-01 
Hledger/Web/Foundation.hs view
@@ -122,7 +122,7 @@         -- flip the default for items with zero amounts, show them by default         ropts' = ropts { empty_ = not (empty_ ropts) }         accounts =-          balanceReportAsHtml (JournalR, RegisterR) here hideEmptyAccts j qopts $+          balanceReportAsHtml (JournalR, RegisterR) here hideEmptyAccts j q qopts $           balanceReport ropts' m j          topShowmd = if showSidebar then "col-md-4" else "col-any-0" :: Text
Hledger/Web/Handler/JournalR.hs view
@@ -12,20 +12,23 @@ import Hledger.Web.Import import Hledger.Web.WebOptions import Hledger.Web.Widget.AddForm (addModal)-import Hledger.Web.Widget.Common (accountQuery, mixedAmountAsHtml)+import Hledger.Web.Widget.Common+            (accountQuery, mixedAmountAsHtml,+             transactionFragment, replaceInacct)  -- | The formatted journal view, with sidebar. getJournalR :: Handler Html getJournalR = do   checkServerSideUiEnabled-  VD{caps, j, m, opts, qopts, today} <- getViewData+  VD{caps, j, m, opts, q, qopts, today} <- getViewData   when (CapView `notElem` caps) (permissionDenied "Missing the 'view' capability")   let title = case inAccount qopts of         Nothing -> "General Journal"         Just (a, inclsubs) -> "Transactions in " <> a <> if inclsubs then "" else " (excluding subaccounts)"       title' = title <> if m /= Any then ", filtered" else ""-      acctlink a = (RegisterR, [("q", accountQuery a)])+      acctlink a = (RegisterR, [("q", replaceInacct q $ accountQuery a)])       (_, items) = transactionsReport (reportopts_ $ cliopts_ opts) j m+      transactionFrag = transactionFragment j    defaultLayout $ do     setTitle "journal - hledger-web"
Hledger/Web/Handler/RegisterR.hs view
@@ -17,13 +17,15 @@ import Hledger.Web.Import import Hledger.Web.WebOptions import Hledger.Web.Widget.AddForm (addModal)-import Hledger.Web.Widget.Common (accountQuery, mixedAmountAsHtml)+import Hledger.Web.Widget.Common+             (accountQuery, mixedAmountAsHtml,+              transactionFragment, removeInacct, replaceInacct)  -- | The main journal/account register view, with accounts sidebar. getRegisterR :: Handler Html getRegisterR = do   checkServerSideUiEnabled-  VD{caps, j, m, opts, qopts, today} <- getViewData+  VD{caps, j, m, opts, q, qopts, today} <- getViewData   when (CapView `notElem` caps) (permissionDenied "Missing the 'view' capability")    let (a,inclsubs) = fromMaybe ("all accounts",True) $ inAccount qopts@@ -33,7 +35,7 @@    let ropts = reportopts_ (cliopts_ opts)       acctQuery = fromMaybe Any (inAccountQuery qopts)-      acctlink acc = (RegisterR, [("q", accountQuery acc)])+      acctlink acc = (RegisterR, [("q", replaceInacct q $ accountQuery acc)])       otherTransAccounts =           map (\(acct,(name,comma)) -> (acct, (T.pack name, T.pack comma))) .           undecorateLinks . elideRightDecorated 40 . decorateLinks .@@ -44,6 +46,7 @@           tail $ (", "<$xs) ++ [""]       r@(balancelabel,items) = accountTransactionsReport ropts j m acctQuery       balancelabel' = if isJust (inAccount qopts) then balancelabel else "Total"+      transactionFrag = transactionFragment j   defaultLayout $ do     setTitle "register - hledger-web"     $(widgetFile "register")
Hledger/Web/Widget/Common.hs view
@@ -14,6 +14,9 @@   , fromFormSuccess   , writeJournalTextIfValidAndChanged   , journalFile404+  , transactionFragment+  , removeInacct+  , replaceInacct   ) where  import Data.Default (def)@@ -23,17 +26,20 @@ #endif import Data.Text (Text) import qualified Data.Text as T+import qualified Data.HashMap.Strict as HashMap import System.FilePath (takeFileName) import Text.Blaze ((!), textValue) import qualified Text.Blaze.Html5 as H import qualified Text.Blaze.Html5.Attributes as A import Text.Blaze.Internal (preEscapedString) import Text.Hamlet (hamletFile)+import Text.Printf (printf) import Yesod  import Hledger import Hledger.Cli.Utils (writeFileWithBackupIfChanged) import Hledger.Web.Settings (manualurl)+import qualified Hledger.Query as Query  #if MIN_VERSION_yesod(1,6,0) journalFile404 :: FilePath -> Journal -> HandlerFor m (FilePath, Text)@@ -79,8 +85,8 @@   where u = textValue $ manualurl <> if T.null topic then "" else T.cons '#' topic  -- | Render a "BalanceReport" as html.-balanceReportAsHtml :: Eq r => (r, r) -> r -> Bool -> Journal -> [QueryOpt] -> BalanceReport -> HtmlUrl r-balanceReportAsHtml (journalR, registerR) here hideEmpty j qopts (items, total) =+balanceReportAsHtml :: Eq r => (r, r) -> r -> Bool -> Journal -> Text -> [QueryOpt] -> BalanceReport -> HtmlUrl r+balanceReportAsHtml (journalR, registerR) here hideEmpty j q qopts (items, total) =   $(hamletFile "templates/balance-report.hamlet")   where     l = ledgerFromJournal Any j@@ -103,3 +109,20 @@     c = case isNegativeMixedAmount b of       Just True -> "negative amount"       _ -> "positive amount"++transactionFragment :: Journal -> Transaction -> String+transactionFragment j =+    let hm = HashMap.fromList $ zip (map fst $ jfiles j) [(1::Integer) ..]+    in  \t ->+            printf "transaction-%d-%d"+                (hm HashMap.! sourceFilePath (tsourcepos t)) (tindex t)++removeInacct :: Text -> [Text]+removeInacct =+    map quoteIfSpaced .+    filter (\term ->+        not $ T.isPrefixOf "inacct:" term || T.isPrefixOf "inacctonly:" term) .+    Query.words'' Query.prefixes++replaceInacct :: Text -> Text -> Text+replaceInacct q acct = T.unwords $ acct : removeInacct q
− README
@@ -1,1 +0,0 @@-A basic web UI for hledger data. Intended to be robust and somewhat useful.
+ README.md view
@@ -0,0 +1,10 @@+# hledger-web++A simple web-based user interface for the hledger accounting system,+providing a more modern UI than the command-line or terminal interfaces.+It can be used as a local single-user UI, or as a multi-user UI for+viewing/adding/editing on the web.++See also:+the [project README](https://hledger.org/README.html)+and [home page](https://hledger.org).
app/main.hs view
@@ -1,5 +1,3 @@-import Prelude              (IO)- import Hledger.Web.Main  main :: IO ()
hledger-web.1 view
@@ -1,5 +1,5 @@ -.TH "hledger-web" "1" "March 2020" "hledger-web 1.17.1" "hledger User Manuals"+.TH "hledger-web" "1" "June 2020" "hledger-web 1.18" "hledger User Manuals"   @@ -316,60 +316,215 @@ that both machine clocks are roughly in step.) .SH JSON API .PP-In addition to the web UI, hledger-web provides some API routes that-serve JSON in response to GET requests.-(And when started with \f[C]--serve-api\f[R], it provides only these-routes.):+In addition to the web UI, hledger-web also serves a JSON API that can+be used to get data or add new transactions.+If you want the JSON API only, you can use the \f[C]--serve-api\f[R]+flag.+Eg: .IP .nf \f[C]+$ hledger-web -f examples/sample.journal --serve-api+\&...+\f[R]+.fi+.PP+You can get JSON data from these routes:+.IP+.nf+\f[C] /accountnames /transactions /prices /commodities /accounts-/accounttransactions/#AccountName+/accounttransactions/ACCOUNTNAME \f[R] .fi .PP-Also, you can append a new transaction to the journal by sending a PUT-request to \f[C]/add\f[R] (hledger-web only).-As with the web UI\[aq]s add form, hledger-web must be started with the-\f[C]add\f[R] capability for this (enabled by default).+Eg, all account names in the journal (similar to the accounts command).+(hledger-web\[aq]s JSON does not include newlines, here we use python to+prettify it):+.IP+.nf+\f[C]+$ curl -s http://127.0.0.1:5000/accountnames | python -m json.tool+[+    \[dq]assets\[dq],+    \[dq]assets:bank\[dq],+    \[dq]assets:bank:checking\[dq],+    \[dq]assets:bank:saving\[dq],+    \[dq]assets:cash\[dq],+    \[dq]expenses\[dq],+    \[dq]expenses:food\[dq],+    \[dq]expenses:supplies\[dq],+    \[dq]income\[dq],+    \[dq]income:gifts\[dq],+    \[dq]income:salary\[dq],+    \[dq]liabilities\[dq],+    \[dq]liabilities:debts\[dq]+]+\f[R]+.fi .PP-The payload should be a valid hledger transaction as JSON, similar to-what you get from \f[C]/transactions\f[R] or-\f[C]/accounttransactions\f[R].+Or all transactions:+.IP+.nf+\f[C]+$ curl -s http://127.0.0.1:5000/transactions | python -m json.tool+[+    {+        \[dq]tcode\[dq]: \[dq]\[dq],+        \[dq]tcomment\[dq]: \[dq]\[dq],+        \[dq]tdate\[dq]: \[dq]2008-01-01\[dq],+        \[dq]tdate2\[dq]: null,+        \[dq]tdescription\[dq]: \[dq]income\[dq],+        \[dq]tindex\[dq]: 1,+        \[dq]tpostings\[dq]: [+            {+                \[dq]paccount\[dq]: \[dq]assets:bank:checking\[dq],+                \[dq]pamount\[dq]: [+                    {+                        \[dq]acommodity\[dq]: \[dq]$\[dq],+                        \[dq]aismultiplier\[dq]: false,+                        \[dq]aprice\[dq]: null,+\&...+\f[R]+.fi .PP-Another way to generate test data is with the-\f[C]readJsonFile\f[R]/\f[C]writeJsonFile\f[R] helpers in-Hledger.Web.Json, which can write or read most of hledger\[aq]s data-types to or from a file.-Eg here we write the first transaction of a sample journal:+Most of the JSON corresponds to hledger\[aq]s data types; for details of+what the fields mean, see the Hledger.Data.Json haddock docs and click+on the various data types, eg Transaction.+And for a higher level understanding, see the journal manual.+.PP+In some cases there is outer JSON corresponding to a \[dq]Report\[dq]+type.+To understand that, go to the Hledger.Web.Handler.MiscR haddock and look+at the source for the appropriate handler to see what it returns.+Eg for \f[C]/accounttransactions\f[R] it\[aq]s getAccounttransactionsR,+returning a \[dq]\f[C]accountTransactionsReport ...\f[R]\[dq].+Looking up the haddock for that we can see that /accounttransactions+returns an AccountTransactionsReport, which consists of a report title+and a list of AccountTransactionsReportItem (etc).+.PP+You can add a new transaction to the journal with a PUT request to+\f[C]/add\f[R], if hledger-web was started with the \f[C]add\f[R]+capability (enabled by default).+The payload must be the full, exact JSON representation of a hledger+transaction (partial data won\[aq]t do).+You can get sample JSON from hledger-web\[aq]s \f[C]/transactions\f[R]+or \f[C]/accounttransactions\f[R], or you can export it with+hledger-lib, eg like so: .IP .nf \f[C]-$ make ghci-web->>> :m +*Hledger.Web.Json+\&.../hledger$ stack ghci hledger-lib >>> writeJsonFile \[dq]txn.json\[dq] (head $ jtxns samplejournal) >>> :q-$ python -m json.tool <txn.json >txn.pretty.json  # optional: make human-readable \f[R] .fi .PP-(sample output & discussion)-.PP-And here\[aq]s how to test adding that with curl:+Here\[aq]s how it looks as of hledger-1.17 (remember, this JSON+corresponds to hledger\[aq]s Transaction and related data types): .IP .nf \f[C]-$ curl -s http://127.0.0.1:5000/add -X PUT -H \[aq]Content-Type: application/json\[aq] --data-binary \[at]txn.pretty.json; echo+{+    \[dq]tcomment\[dq]: \[dq]\[dq],+    \[dq]tpostings\[dq]: [+        {+            \[dq]pbalanceassertion\[dq]: null,+            \[dq]pstatus\[dq]: \[dq]Unmarked\[dq],+            \[dq]pamount\[dq]: [+                {+                    \[dq]aprice\[dq]: null,+                    \[dq]acommodity\[dq]: \[dq]$\[dq],+                    \[dq]aquantity\[dq]: {+                        \[dq]floatingPoint\[dq]: 1,+                        \[dq]decimalPlaces\[dq]: 10,+                        \[dq]decimalMantissa\[dq]: 10000000000+                    },+                    \[dq]aismultiplier\[dq]: false,+                    \[dq]astyle\[dq]: {+                        \[dq]ascommodityside\[dq]: \[dq]L\[dq],+                        \[dq]asdigitgroups\[dq]: null,+                        \[dq]ascommodityspaced\[dq]: false,+                        \[dq]asprecision\[dq]: 2,+                        \[dq]asdecimalpoint\[dq]: \[dq].\[dq]+                    }+                }+            ],+            \[dq]ptransaction_\[dq]: \[dq]1\[dq],+            \[dq]paccount\[dq]: \[dq]assets:bank:checking\[dq],+            \[dq]pdate\[dq]: null,+            \[dq]ptype\[dq]: \[dq]RegularPosting\[dq],+            \[dq]pcomment\[dq]: \[dq]\[dq],+            \[dq]pdate2\[dq]: null,+            \[dq]ptags\[dq]: [],+            \[dq]poriginal\[dq]: null+        },+        {+            \[dq]pbalanceassertion\[dq]: null,+            \[dq]pstatus\[dq]: \[dq]Unmarked\[dq],+            \[dq]pamount\[dq]: [+                {+                    \[dq]aprice\[dq]: null,+                    \[dq]acommodity\[dq]: \[dq]$\[dq],+                    \[dq]aquantity\[dq]: {+                        \[dq]floatingPoint\[dq]: -1,+                        \[dq]decimalPlaces\[dq]: 10,+                        \[dq]decimalMantissa\[dq]: -10000000000+                    },+                    \[dq]aismultiplier\[dq]: false,+                    \[dq]astyle\[dq]: {+                        \[dq]ascommodityside\[dq]: \[dq]L\[dq],+                        \[dq]asdigitgroups\[dq]: null,+                        \[dq]ascommodityspaced\[dq]: false,+                        \[dq]asprecision\[dq]: 2,+                        \[dq]asdecimalpoint\[dq]: \[dq].\[dq]+                    }+                }+            ],+            \[dq]ptransaction_\[dq]: \[dq]1\[dq],+            \[dq]paccount\[dq]: \[dq]income:salary\[dq],+            \[dq]pdate\[dq]: null,+            \[dq]ptype\[dq]: \[dq]RegularPosting\[dq],+            \[dq]pcomment\[dq]: \[dq]\[dq],+            \[dq]pdate2\[dq]: null,+            \[dq]ptags\[dq]: [],+            \[dq]poriginal\[dq]: null+        }+    ],+    \[dq]ttags\[dq]: [],+    \[dq]tsourcepos\[dq]: {+        \[dq]tag\[dq]: \[dq]JournalSourcePos\[dq],+        \[dq]contents\[dq]: [+            \[dq]\[dq],+            [+                1,+                1+            ]+        ]+    },+    \[dq]tdate\[dq]: \[dq]2008-01-01\[dq],+    \[dq]tcode\[dq]: \[dq]\[dq],+    \[dq]tindex\[dq]: 1,+    \[dq]tprecedingcomment\[dq]: \[dq]\[dq],+    \[dq]tdate2\[dq]: null,+    \[dq]tdescription\[dq]: \[dq]income\[dq],+    \[dq]tstatus\[dq]: \[dq]Unmarked\[dq]+} \f[R] .fi .PP-By default, both the server-side HTML UI and the JSON API are served.-Running with \f[C]--serve-api\f[R] disables the former, useful if you-only want to serve the API.+And here\[aq]s how to test adding it with curl.+This should add a new entry to your journal:+.IP+.nf+\f[C]+$ curl http://127.0.0.1:5000/add -X PUT -H \[aq]Content-Type: application/json\[aq] --data-binary \[at]txn.json+\f[R]+.fi .SH ENVIRONMENT .PP \f[B]LEDGER_FILE\f[R] The journal file path when not specified with
hledger-web.cabal view
@@ -1,24 +1,25 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.31.2.+-- This file has been generated from package.yaml by hpack version 0.33.0. -- -- see: https://github.com/sol/hpack ----- hash: 9a8cb3ef7223d8bdfbc8e2c5a8caa8d9c80b645289d749fdec200b98094ecd60+-- hash: daab2dd5e0ca3ed383252d6577500975576694432c856b386d9cbc2c5f7b75c0  name:           hledger-web-version:        1.17.1-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 terminal interfaces.+version:        1.18+synopsis:       Web-based user interface for the hledger accounting system+description:    A simple web-based user interface for the hledger accounting system,+                providing a more modern UI than the command-line or terminal interfaces.+                It can be used as a local single-user UI, or as a multi-user UI for+                viewing/adding/editing on the web.                 .-                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, terminal and web-                interfaces, and aims to be a reliable, practical tool for daily-                use.+                hledger is a robust, cross-platform set of tools for tracking money,+                time, or any other commodity, using double-entry accounting and a+                simple, editable file format, with command-line, terminal and web+                interfaces. It is a Haskell rewrite of Ledger, and one of the leading+                implementations of Plain Text Accounting. Read more at:+                <https://hledger.org> category:       Finance stability:      stable homepage:       http://hledger.org@@ -27,11 +28,11 @@ maintainer:     Simon Michael <simon@joyful.com> license:        GPL-3 license-file:   LICENSE-tested-with:    GHC==8.2.2, GHC==8.4.3, GHC==8.6.5, GHC==8.8.2, GHC==8.10+tested-with:    GHC==8.2.2, GHC==8.4.4, GHC==8.6.5, GHC==8.8.3, GHC==8.10.0.20200123 build-type:     Simple extra-source-files:     CHANGES.md-    README+    README.md     config/favicon.ico     config/keter.yaml     config/robots.txt@@ -150,7 +151,7 @@   hs-source-dirs:       ./.   ghc-options: -Wall -fwarn-tabs -Wcompat -Wincomplete-uni-patterns -Wincomplete-record-updates -Wredundant-constraints-  cpp-options: -DVERSION="1.17.1"+  cpp-options: -DVERSION="1.18"   build-depends:       Decimal >=0.5.1     , aeson@@ -169,8 +170,8 @@     , extra >=1.6.3     , filepath     , hjsmin-    , hledger >=1.17.1 && <1.18-    , hledger-lib >=1.17.1 && <1.18+    , hledger >=1.18 && <1.19+    , hledger-lib >=1.18 && <1.19     , http-client     , http-conduit     , http-types@@ -184,6 +185,7 @@     , time >=1.5     , transformers     , unix-compat+    , unordered-containers     , utf8-string     , wai     , wai-cors@@ -208,7 +210,7 @@   hs-source-dirs:       app   ghc-options: -Wall -fwarn-tabs -Wcompat -Wincomplete-uni-patterns -Wincomplete-record-updates -Wredundant-constraints-  cpp-options: -DVERSION="1.17.1"+  cpp-options: -DVERSION="1.18"   build-depends:       base     , hledger-web
hledger-web.info view
@@ -3,8 +3,8 @@  File: hledger-web.info,  Node: Top,  Next: OPTIONS,  Up: (dir) -hledger-web(1) hledger-web 1.17.1-*********************************+hledger-web(1) hledger-web 1.18+*******************************  hledger-web - web interface for the hledger accounting tool @@ -330,45 +330,182 @@ 5 JSON API ********** -In addition to the web UI, hledger-web provides some API routes that-serve JSON in response to GET requests.  (And when started with-'--serve-api', it provides only these routes.):+In addition to the web UI, hledger-web also serves a JSON API that can+be used to get data or add new transactions.  If you want the JSON API+only, you can use the '--serve-api' flag.  Eg: +$ hledger-web -f examples/sample.journal --serve-api+...++   You can get JSON data from these routes:+ /accountnames /transactions /prices /commodities /accounts-/accounttransactions/#AccountName+/accounttransactions/ACCOUNTNAME -   Also, you can append a new transaction to the journal by sending a-PUT request to '/add' (hledger-web only).  As with the web UI's add-form, hledger-web must be started with the 'add' capability for this-(enabled by default).+   Eg, all account names in the journal (similar to the accounts+command).  (hledger-web's JSON does not include newlines, here we use+python to prettify it): -   The payload should be a valid hledger transaction as JSON, similar to-what you get from '/transactions' or '/accounttransactions'.+$ curl -s http://127.0.0.1:5000/accountnames | python -m json.tool+[+    "assets",+    "assets:bank",+    "assets:bank:checking",+    "assets:bank:saving",+    "assets:cash",+    "expenses",+    "expenses:food",+    "expenses:supplies",+    "income",+    "income:gifts",+    "income:salary",+    "liabilities",+    "liabilities:debts"+] -   Another way to generate test data is with the-'readJsonFile'/'writeJsonFile' helpers in Hledger.Web.Json, which can-write or read most of hledger's data types to or from a file.  Eg here-we write the first transaction of a sample journal:+   Or all transactions: -$ make ghci-web->>> :m +*Hledger.Web.Json+$ curl -s http://127.0.0.1:5000/transactions | python -m json.tool+[+    {+        "tcode": "",+        "tcomment": "",+        "tdate": "2008-01-01",+        "tdate2": null,+        "tdescription": "income",+        "tindex": 1,+        "tpostings": [+            {+                "paccount": "assets:bank:checking",+                "pamount": [+                    {+                        "acommodity": "$",+                        "aismultiplier": false,+                        "aprice": null,+...++   Most of the JSON corresponds to hledger's data types; for details of+what the fields mean, see the Hledger.Data.Json haddock docs and click+on the various data types, eg Transaction.  And for a higher level+understanding, see the journal manual.++   In some cases there is outer JSON corresponding to a "Report" type.+To understand that, go to the Hledger.Web.Handler.MiscR haddock and look+at the source for the appropriate handler to see what it returns.  Eg+for '/accounttransactions' it's getAccounttransactionsR, returning a+"'accountTransactionsReport ...'".  Looking up the haddock for that we+can see that /accounttransactions returns an AccountTransactionsReport,+which consists of a report title and a list of+AccountTransactionsReportItem (etc).++   You can add a new transaction to the journal with a PUT request to+'/add', if hledger-web was started with the 'add' capability (enabled by+default).  The payload must be the full, exact JSON representation of a+hledger transaction (partial data won't do).  You can get sample JSON+from hledger-web's '/transactions' or '/accounttransactions', or you can+export it with hledger-lib, eg like so:++.../hledger$ stack ghci hledger-lib >>> writeJsonFile "txn.json" (head $ jtxns samplejournal) >>> :q-$ python -m json.tool <txn.json >txn.pretty.json  # optional: make human-readable -   (sample output & discussion)+   Here's how it looks as of hledger-1.17 (remember, this JSON+corresponds to hledger's Transaction and related data types): -   And here's how to test adding that with curl:+{+    "tcomment": "",+    "tpostings": [+        {+            "pbalanceassertion": null,+            "pstatus": "Unmarked",+            "pamount": [+                {+                    "aprice": null,+                    "acommodity": "$",+                    "aquantity": {+                        "floatingPoint": 1,+                        "decimalPlaces": 10,+                        "decimalMantissa": 10000000000+                    },+                    "aismultiplier": false,+                    "astyle": {+                        "ascommodityside": "L",+                        "asdigitgroups": null,+                        "ascommodityspaced": false,+                        "asprecision": 2,+                        "asdecimalpoint": "."+                    }+                }+            ],+            "ptransaction_": "1",+            "paccount": "assets:bank:checking",+            "pdate": null,+            "ptype": "RegularPosting",+            "pcomment": "",+            "pdate2": null,+            "ptags": [],+            "poriginal": null+        },+        {+            "pbalanceassertion": null,+            "pstatus": "Unmarked",+            "pamount": [+                {+                    "aprice": null,+                    "acommodity": "$",+                    "aquantity": {+                        "floatingPoint": -1,+                        "decimalPlaces": 10,+                        "decimalMantissa": -10000000000+                    },+                    "aismultiplier": false,+                    "astyle": {+                        "ascommodityside": "L",+                        "asdigitgroups": null,+                        "ascommodityspaced": false,+                        "asprecision": 2,+                        "asdecimalpoint": "."+                    }+                }+            ],+            "ptransaction_": "1",+            "paccount": "income:salary",+            "pdate": null,+            "ptype": "RegularPosting",+            "pcomment": "",+            "pdate2": null,+            "ptags": [],+            "poriginal": null+        }+    ],+    "ttags": [],+    "tsourcepos": {+        "tag": "JournalSourcePos",+        "contents": [+            "",+            [+                1,+                1+            ]+        ]+    },+    "tdate": "2008-01-01",+    "tcode": "",+    "tindex": 1,+    "tprecedingcomment": "",+    "tdate2": null,+    "tdescription": "income",+    "tstatus": "Unmarked"+} -$ curl -s http://127.0.0.1:5000/add -X PUT -H 'Content-Type: application/json' --data-binary @txn.pretty.json; echo+   And here's how to test adding it with curl.  This should add a new+entry to your journal: -   By default, both the server-side HTML UI and the JSON API are served.-Running with '--serve-api' disables the former, useful if you only want-to serve the API.+$ curl http://127.0.0.1:5000/add -X PUT -H 'Content-Type: application/json' --data-binary @txn.json   File: hledger-web.info,  Node: ENVIRONMENT,  Next: FILES,  Prev: JSON API,  Up: Top@@ -427,22 +564,22 @@  Tag Table: Node: Top72-Node: OPTIONS1750-Ref: #options1855-Node: PERMISSIONS8199-Ref: #permissions8338-Node: EDITING UPLOADING DOWNLOADING9550-Ref: #editing-uploading-downloading9731-Node: RELOADING10565-Ref: #reloading10699-Node: JSON API11132-Ref: #json-api11246-Node: ENVIRONMENT12684-Ref: #environment12800-Node: FILES13533-Ref: #files13633-Node: BUGS13846-Ref: #bugs13924+Node: OPTIONS1746+Ref: #options1851+Node: PERMISSIONS8195+Ref: #permissions8334+Node: EDITING UPLOADING DOWNLOADING9546+Ref: #editing-uploading-downloading9727+Node: RELOADING10561+Ref: #reloading10695+Node: JSON API11128+Ref: #json-api11242+Node: ENVIRONMENT16723+Ref: #environment16839+Node: FILES17572+Ref: #files17672+Node: BUGS17885+Ref: #bugs17963  End Tag Table 
hledger-web.txt view
@@ -289,45 +289,182 @@        that both machine clocks are roughly in step.)  JSON API-       In addition to the web UI, hledger-web provides some  API  routes  that-       serve  JSON  in  response  to  GET  requests.   (And  when started with-       --serve-api, it provides only these routes.):+       In addition to the web UI, hledger-web also serves a JSON API that  can+       be  used to get data or add new transactions.  If you want the JSON API+       only, you can use the --serve-api flag.  Eg: +              $ hledger-web -f examples/sample.journal --serve-api+              ...++       You can get JSON data from these routes:+               /accountnames               /transactions               /prices               /commodities               /accounts-              /accounttransactions/#AccountName+              /accounttransactions/ACCOUNTNAME -       Also, you can append a new transaction to the journal by sending a  PUT-       request  to  /add  (hledger-web  only).  As with the web UI's add form,-       hledger-web must be started with the add capability for  this  (enabled-       by default).+       Eg, all account names in the journal (similar to the accounts command).+       (hledger-web's  JSON  does  not include newlines, here we use python to+       prettify it): -       The  payload  should be a valid hledger transaction as JSON, similar to-       what you get from /transactions or /accounttransactions.+              $ curl -s http://127.0.0.1:5000/accountnames | python -m json.tool+              [+                  "assets",+                  "assets:bank",+                  "assets:bank:checking",+                  "assets:bank:saving",+                  "assets:cash",+                  "expenses",+                  "expenses:food",+                  "expenses:supplies",+                  "income",+                  "income:gifts",+                  "income:salary",+                  "liabilities",+                  "liabilities:debts"+              ] -       Another way to generate test data is with  the  readJsonFile/writeJson--       File  helpers  in  Hledger.Web.Json,  which  can  write or read most of-       hledger's data types to or from a file.  Eg here  we  write  the  first-       transaction of a sample journal:+       Or all transactions: -              $ make ghci-web-              >>> :m +*Hledger.Web.Json+              $ curl -s http://127.0.0.1:5000/transactions | python -m json.tool+              [+                  {+                      "tcode": "",+                      "tcomment": "",+                      "tdate": "2008-01-01",+                      "tdate2": null,+                      "tdescription": "income",+                      "tindex": 1,+                      "tpostings": [+                          {+                              "paccount": "assets:bank:checking",+                              "pamount": [+                                  {+                                      "acommodity": "$",+                                      "aismultiplier": false,+                                      "aprice": null,+              ...++       Most of the JSON corresponds to hledger's data types;  for  details  of+       what  the fields mean, see the Hledger.Data.Json haddock docs and click+       on the various data types, eg Transaction.  And for a higher level  un-+       derstanding, see the journal manual.++       In some cases there is outer JSON corresponding to a "Report" type.  To+       understand that, go to the Hledger.Web.Handler.MiscR haddock  and  look+       at  the  source for the appropriate handler to see what it returns.  Eg+       for /accounttransactions it's getAccounttransactionsR, returning a "ac-+       countTransactionsReport  ...".   Looking up the haddock for that we can+       see that  /accounttransactions  returns  an  AccountTransactionsReport,+       which  consists  of a report title and a list of AccountTransactionsRe-+       portItem (etc).++       You can add a new transaction to the journal  with  a  PUT  request  to+       /add,  if  hledger-web  was started with the add capability (enabled by+       default).  The payload must be the full, exact JSON representation of a+       hledger  transaction  (partial data won't do).  You can get sample JSON+       from hledger-web's /transactions or /accounttransactions,  or  you  can+       export it with hledger-lib, eg like so:++              .../hledger$ stack ghci hledger-lib               >>> writeJsonFile "txn.json" (head $ jtxns samplejournal)               >>> :q-              $ python -m json.tool <txn.json >txn.pretty.json  # optional: make human-readable -       (sample output & discussion)+       Here's how it looks as of hledger-1.17 (remember, this JSON corresponds+       to hledger's Transaction and related data types): -       And here's how to test adding that with curl:+              {+                  "tcomment": "",+                  "tpostings": [+                      {+                          "pbalanceassertion": null,+                          "pstatus": "Unmarked",+                          "pamount": [+                              {+                                  "aprice": null,+                                  "acommodity": "$",+                                  "aquantity": {+                                      "floatingPoint": 1,+                                      "decimalPlaces": 10,+                                      "decimalMantissa": 10000000000+                                  },+                                  "aismultiplier": false,+                                  "astyle": {+                                      "ascommodityside": "L",+                                      "asdigitgroups": null,+                                      "ascommodityspaced": false,+                                      "asprecision": 2,+                                      "asdecimalpoint": "."+                                  }+                              }+                          ],+                          "ptransaction_": "1",+                          "paccount": "assets:bank:checking",+                          "pdate": null,+                          "ptype": "RegularPosting",+                          "pcomment": "",+                          "pdate2": null,+                          "ptags": [],+                          "poriginal": null+                      },+                      {+                          "pbalanceassertion": null,+                          "pstatus": "Unmarked",+                          "pamount": [+                              {+                                  "aprice": null,+                                  "acommodity": "$",+                                  "aquantity": {+                                      "floatingPoint": -1,+                                      "decimalPlaces": 10,+                                      "decimalMantissa": -10000000000+                                  },+                                  "aismultiplier": false,+                                  "astyle": {+                                      "ascommodityside": "L",+                                      "asdigitgroups": null,+                                      "ascommodityspaced": false,+                                      "asprecision": 2,+                                      "asdecimalpoint": "."+                                  }+                              }+                          ],+                          "ptransaction_": "1",+                          "paccount": "income:salary",+                          "pdate": null,+                          "ptype": "RegularPosting",+                          "pcomment": "",+                          "pdate2": null,+                          "ptags": [],+                          "poriginal": null+                      }+                  ],+                  "ttags": [],+                  "tsourcepos": {+                      "tag": "JournalSourcePos",+                      "contents": [+                          "",+                          [+                              1,+                              1+                          ]+                      ]+                  },+                  "tdate": "2008-01-01",+                  "tcode": "",+                  "tindex": 1,+                  "tprecedingcomment": "",+                  "tdate2": null,+                  "tdescription": "income",+                  "tstatus": "Unmarked"+              } -              $ curl -s http://127.0.0.1:5000/add -X PUT -H 'Content-Type: application/json' --data-binary @txn.pretty.json; echo+       And here's how to test adding it with curl.  This should add a new  en-+       try to your journal: -       By  default,  both the server-side HTML UI and the JSON API are served.-       Running with --serve-api disables the former, useful if you  only  want-       to serve the API.+              $ curl http://127.0.0.1:5000/add -X PUT -H 'Content-Type: application/json' --data-binary @txn.json  ENVIRONMENT        LEDGER_FILE The journal file path when not specified with -f.  Default:@@ -392,4 +529,4 @@   -hledger-web 1.17.1                March 2020                    hledger-web(1)+hledger-web 1.18                   June 2020                    hledger-web(1)
static/hledger.js view
@@ -33,11 +33,6 @@   $('body').bind('keydown', 'a',       function(){ addformShow(); return false; });   $('body').bind('keydown', 'n',       function(){ addformShow(); return false; });   $('body').bind('keydown', 'f',       function(){ $('#searchform input').focus(); return false; });-  $('body, #addform input, #addform select').bind('keydown', 'ctrl++',       addformAddPosting);-  $('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);-  $('.amount-input:last').keypress(addformAddPosting);    // highlight the entry from the url hash   if (window.location.hash && $(window.location.hash)[0]) {@@ -138,18 +133,27 @@   $('#addmodal').modal('show'); } -// Make sure the add form is empty and clean for display.+// Make sure the add form is empty and clean and has the default number of rows. function addformReset(showmsg) {   showmsg = typeof showmsg !== 'undefined' ? showmsg : false;   if ($('form#addform').length > 0) {     if (!showmsg) $('div#message').html('');-    $('form#addform')[0].reset();+    $('#addform .account-group.added-row').remove();+    addformLastAmountBindKey();+    $('#addform')[0].reset();     // reset typehead state (though not fetched completions)     $('.typeahead').typeahead('val', '');     $('.tt-dropdown-menu').hide();   } } +// Set the add-new-row-on-keypress handler on the add form's current last amount field, only.+// (NB: removes all other keypress handlers from all amount fields).+function addformLastAmountBindKey() {+  $('.amount-input').off('keypress');+  $('.amount-input:last').keypress(addformAddPosting);+}+ // Focus the first add form field. function addformFocus() {   focus($('#addform input#date'));@@ -167,28 +171,14 @@   if (!$('#addform').is(':visible')) {     return;   }--  var prevLastRow = $('#addform .account-group:last');-  prevLastRow.off('keypress');--  // Clone the currently last row-  $('#addform .account-postings').append(prevLastRow.clone());-  var num = $('#addform .account-group').length;--  // clear and renumber the field, add keybindings-  // XXX Enable typehead on dynamically created inputs-  $('.amount-input:last')-    .val('')-    .prop('placeholder','Amount ' + num)-    .keypress(addformAddPosting);--  $('.account-input:last')-    .val('')-    .prop('placeholder', 'Account ' + num)-    .bind('keydown', 'ctrl++', addformAddPosting)-    .bind('keydown', 'ctrl+shift+=', addformAddPosting)-    .bind('keydown', 'ctrl+=', addformAddPosting)-    .bind('keydown', 'ctrl+-', addformDeletePosting);+  // Clone the old last row to make a new last row+  $('#addform .account-postings').append( $('#addform .account-group:last').clone().addClass('added-row') );+  // renumber and clear the new last account and amount fields+  var n = $('#addform .account-group').length;+  $('.account-input:last').prop('placeholder', 'Account '+n).val('');+  $('.amount-input:last').prop('placeholder','Amount '+n).val('');  // XXX Enable typehead on dynamically created inputs+  // and move the keypress handler to the new last amount field+  addformLastAmountBindKey(); }  // Remove the add form's last posting row, if empty, keeping at least two.@@ -205,8 +195,8 @@   if (focuslost) {     focus($('.account-input:last'));   }-  // Rebind keypress-  $('.amount-input:last').keypress(addformAddPosting);+  // move the keypress handler to the new last amount field+  addformLastAmountBindKey(); }  //----------------------------------------------------------------------
templates/add-form.hamlet view
@@ -65,7 +65,3 @@       $forall p <- journals         <option value=#{p}>#{p} <span .small style="padding-left:2em;">-  Enter a value in the last field for #-  <a href="#" onclick="addformAddPosting(); return false;">-    more-  \ (or ctrl +, ctrl -)
templates/balance-report.hamlet view
@@ -7,15 +7,15 @@ $forall (acct, adisplay, aindent, abal) <- items   <tr      :matchesAcctSelector acct:.inacct-     :isZeroMixedAmount abal && hideEmpty:.hide>-    <td .acct :isZeroMixedAmount abal:.empty>+     :mixedAmountLooksZero abal && hideEmpty:.hide>+    <td .acct :mixedAmountLooksZero abal:.empty>       <div .ff-wrapper>         \#{indent aindent}-        <a.acct-name href="@?{(registerR, [("q", accountQuery acct)])}"+        <a.acct-name href="@?{(registerR, [("q", replaceInacct q $ accountQuery acct)])}"            title="Show transactions affecting this account and subaccounts">           #{adisplay}         $if hasSubAccounts acct-          <a href="@?{(registerR, [("q", accountOnlyQuery acct)])}" .only.hidden-sm.hidden-xs+          <a href="@?{(registerR, [("q", replaceInacct q $ accountOnlyQuery acct)])}" .only.hidden-sm.hidden-xs              title="Show transactions affecting this account but not subaccounts">only     <td>       ^{mixedAmountAsHtml abal}
templates/journal.hamlet view
@@ -15,13 +15,13 @@       <th .amount style="text-align:right;">Amount      $forall (torig, _, split, _, amt, _) <- items-      <tr .title #transaction-#{tindex torig} title="#{showTransaction torig}">+      <tr .title ##{transactionFrag torig} title="#{showTransaction torig}">         <td .date nowrap>           #{show (tdate torig)}         <td colspan=2>           #{textElideRight 60 (tdescription torig)}         <td .amount style="text-align:right;">-          $if not split && not (isZeroMixedAmount amt)+          $if not split && not (mixedAmountLooksZero amt)             ^{mixedAmountAsHtml amt}        $forall Posting { paccount = acc, pamount = amt } <- tpostings torig
templates/register.hamlet view
@@ -21,7 +21,7 @@       $forall (torig, tacct, split, _acct, amt, bal) <- items         <tr ##{tindex torig} title="#{showTransaction torig}" style="vertical-align:top;">           <td .date>-            <a href="@{JournalR}#transaction-#{tindex torig}">+            <a href="@?{(JournalR, [("q", T.unwords $ removeInacct q)])}##{transactionFrag torig}">               #{show (tdate tacct)}           <td>             #{textElideRight 30 (tdescription tacct)}@@ -30,7 +30,7 @@               <a href="@?{acctlink acc}##{tindex torig}" title="#{acc}">                 #{summName}</a>#{comma}           <td .amount style="text-align:right; white-space:nowrap;">-            $if not split || not (isZeroMixedAmount amt)+            $if not split || not (mixedAmountLooksZero amt)               ^{mixedAmountAsHtml amt}           <td style="text-align:right;">             ^{mixedAmountAsHtml bal}