Compare commits

..

10 Commits

7 changed files with 96 additions and 13 deletions

View File

@ -61,6 +61,7 @@ import Data.List (isPrefixOf)
data App = App data App = App
{ settings :: AppConfig DefaultEnv Extra { settings :: AppConfig DefaultEnv Extra
, getStatic :: Static -- ^ Settings for static file serving. , getStatic :: Static -- ^ Settings for static file serving.
, getDocument :: Static
, httpManager :: Manager , httpManager :: Manager
-- --
, appOpts :: WebOpts , appOpts :: WebOpts
@ -141,6 +142,7 @@ instance Yesod App where
ropts' = (_rsReportOpts rspec) ropts' = (_rsReportOpts rspec)
{accountlistmode_ = ALTree -- force tree mode for sidebar {accountlistmode_ = ALTree -- force tree mode for sidebar
,empty_ = True -- show zero items by default ,empty_ = True -- show zero items by default
,no_elide_ = True -- list every account on its own row
} }
rspec' = rspec{_rsQuery=q,_rsReportOpts=ropts'} rspec' = rspec{_rsQuery=q,_rsReportOpts=ropts'}

View File

@ -19,8 +19,14 @@ import Network.Wai.Middleware.RequestLogger (logStdoutDev, logStdout)
import Network.HTTP.Client (defaultManagerSettings) import Network.HTTP.Client (defaultManagerSettings)
import Network.HTTP.Conduit (newManager) import Network.HTTP.Conduit (newManager)
import Yesod.Default.Config import Yesod.Default.Config
import Yesod.Static (staticDevel, Static(Static))
import WaiAppStatic.Types (StaticSettings(..), LookupResult(LRNotFound), MaxAge(NoMaxAge))
import Control.Applicative ((<|>))
import Control.Monad (sequence)
import System.FilePath (takeDirectory, (</>))
import System.Directory (doesDirectoryExist)
import Hledger.Data (Journal, nulljournal) import Hledger.Data (Journal(jfiles), nulljournal)
import Hledger.Web.Handler.AddR import Hledger.Web.Handler.AddR
import Hledger.Web.Handler.MiscR import Hledger.Web.Handler.MiscR
@ -29,7 +35,7 @@ import Hledger.Web.Handler.UploadR
import Hledger.Web.Handler.JournalR import Hledger.Web.Handler.JournalR
import Hledger.Web.Handler.RegisterR import Hledger.Web.Handler.RegisterR
import Hledger.Web.Import import Hledger.Web.Import
import Hledger.Web.WebOptions (ServerMode(..), WebOpts(server_mode_), corsPolicy) import Hledger.Web.WebOptions (ServerMode(..), WebOpts(server_mode_, document_directory_), corsPolicy)
-- mkYesodDispatch creates our YesodDispatch instance. -- mkYesodDispatch creates our YesodDispatch instance.
-- It complements the mkYesodData call in App.hs, -- It complements the mkYesodData call in App.hs,
@ -42,7 +48,7 @@ mkYesodDispatch "App" resourcesApp
-- migrations handled by Yesod. -- migrations handled by Yesod.
makeApplication :: WebOpts -> Journal -> AppConfig DefaultEnv Extra -> IO Application makeApplication :: WebOpts -> Journal -> AppConfig DefaultEnv Extra -> IO Application
makeApplication opts' j' conf' = do makeApplication opts' j' conf' = do
app <- makeApp conf' opts' app <- makeAppWith j' conf' opts'
writeIORef (appJournal app) j' writeIORef (appJournal app) j'
(logWare . (corsPolicy opts')) <$> toWaiApp app (logWare . (corsPolicy opts')) <$> toWaiApp app
where where
@ -59,11 +65,29 @@ makeApp = makeAppWith nulljournal
makeAppWith :: Journal -> AppConfig DefaultEnv Extra -> WebOpts -> IO App makeAppWith :: Journal -> AppConfig DefaultEnv Extra -> WebOpts -> IO App
makeAppWith j' aconf wopts = do makeAppWith j' aconf wopts = do
s <- staticSite s <- staticSite
let ddd = fmap ((</> "tositteet") . takeDirectory . fst) . listToMaybe $ jfiles j'
when pred a = (\x -> if x then Just a else Nothing) <$> pred
defaultDocumentDir <- fmap join . sequence $ (when <$> doesDirectoryExist <*> id) <$> ddd
let documentDir = document_directory_ wopts <|> defaultDocumentDir
nullStatic = Static $ StaticSettings
{ ssLookupFile = const $ pure LRNotFound
, ssGetMimeType = const $ pure ""
, ssIndices = []
, ssListing = Nothing
, ssMaxAge = NoMaxAge
, ssMkRedirect = const id
, ssRedirectToIndex = False
, ssUseHash = False
, ssAddTrailingSlash = True
, ss404Handler = Nothing
}
documentStatic <- sequence $ staticDevel <$> documentDir
m <- newManager defaultManagerSettings m <- newManager defaultManagerSettings
jref <- newIORef j' jref <- newIORef j'
return App{ return App{
settings = aconf settings = aconf
, getStatic = s , getStatic = s
, getDocument = fromMaybe nullStatic documentStatic
, httpManager = m , httpManager = m
, appOpts = wopts , appOpts = wopts
, appJournal = jref , appJournal = jref

View File

@ -7,7 +7,10 @@
module Hledger.Web.Handler.JournalR where module Hledger.Web.Handler.JournalR where
import qualified Data.Text as T -- for journal.hamlet import qualified Data.Text as T -- also for journal.hamlet
import qualified Text.URI as URI
import Yesod.Static
import Hledger import Hledger
import Hledger.Cli.CliOptions import Hledger.Cli.CliOptions
@ -36,6 +39,16 @@ getJournalR = do
transactionFrag = transactionFragment j transactionFrag = transactionFragment j
isVisibleTag = not . isHiddenTagName . fst isVisibleTag = not . isHiddenTagName . fst
isPostingTag account tag = not $ tag `elem` journalInheritedAccountTags j account isPostingTag account tag = not $ tag `elem` journalInheritedAccountTags j account
isAbsoluteURI = maybe False URI.isPathAbsolute . URI.mkURI
documentRoute = DocumentR . flip StaticRoute [] . T.splitOn "/"
escapeRegex = T.pack . concatMap escape . T.unpack
escape c
| c `elem` (".[$^()|*+?{\\" :: [Char]) = ['\\', c]
| otherwise = [c]
-- XXX:is there no way to escape quotes in queries
addTagQuery name value = (JournalR, [("q", qparam <> " \"tag:^" <> escapeRegex name <> "$" <> (if T.null value then "" else "=^" <> escapeRegex value <> "$") <> "\"")])
addCodeQuery code = (JournalR, [("q", qparam <> " \"code:^" <> escapeRegex code <> "$\"")])
addPayeeQuery code = (JournalR, [("q", qparam <> " \"payee:^" <> escapeRegex code <> "$\"")])
defaultLayout $ do defaultLayout $ do
setTitle "päiväkirja - hledger-web" setTitle "päiväkirja - hledger-web"

View File

@ -100,6 +100,11 @@ webflags =
(\s opts -> Right $ setopt "base-url" s opts) (\s opts -> Right $ setopt "base-url" s opts)
"BASEURL" "BASEURL"
"set the base url (default: http://IPADDR:PORT)" "set the base url (default: http://IPADDR:PORT)"
, flagReq
["document-directory"]
(\s opts -> Right $ setopt "document-directory" s opts)
"DIRECTORY"
"set the directory to search for tositteet (default: JOURNAL-DIRECTORY/tositteet)"
-- XXX #2139 -- XXX #2139
-- , flagReq -- , flagReq
-- ["file-url"] -- ["file-url"]
@ -143,6 +148,7 @@ data WebOpts = WebOpts
, port_ :: !Int , port_ :: !Int
, base_url_ :: !String , base_url_ :: !String
, file_url_ :: !(Maybe String) , file_url_ :: !(Maybe String)
, document_directory_ :: !(Maybe FilePath)
, allow_ :: !AccessLevel , allow_ :: !AccessLevel
, cliopts_ :: !CliOpts , cliopts_ :: !CliOpts
, socket_ :: !(Maybe String) , socket_ :: !(Maybe String)
@ -156,6 +162,7 @@ defwebopts = WebOpts
, port_ = def , port_ = def
, base_url_ = "" , base_url_ = ""
, file_url_ = Nothing , file_url_ = Nothing
, document_directory_ = Nothing
, allow_ = AddAccess , allow_ = AddAccess
, cliopts_ = def , cliopts_ = def
, socket_ = Nothing , socket_ = Nothing
@ -191,6 +198,7 @@ rawOptsToWebOpts rawopts =
, port_ = p , port_ = p
, base_url_ = b , base_url_ = b
, file_url_ = stripTrailingSlash <$> maybestringopt "file-url" rawopts , file_url_ = stripTrailingSlash <$> maybestringopt "file-url" rawopts
, document_directory_ = maybestringopt "document-directory" rawopts
, allow_ = access , allow_ = access
, cliopts_ = cliopts , cliopts_ = cliopts
, socket_ = sock , socket_ = sock

View File

@ -1,6 +1,7 @@
/favicon.ico FaviconR GET /favicon.ico FaviconR GET
/robots.txt RobotsR GET /robots.txt RobotsR GET
/static StaticR Static getStatic /static StaticR Static getStatic
/document DocumentR Static getDocument
/openapi.json OpenApiR GET /openapi.json OpenApiR GET

View File

@ -100,9 +100,13 @@ ul {
word-wrap: break-word; word-wrap: break-word;
} }
#sidebar-menu .main-menu tr {
border-top: 1px solid #ebebeb;
}
#sidebar-menu .main-menu td { #sidebar-menu .main-menu td {
padding: 1px !important; padding: 1px !important;
border-top: 1px solid #ebebeb; border-top: none;
overflow: hidden; overflow: hidden;
white-space:nowrap; white-space:nowrap;
text-overflow:ellipsis; text-overflow:ellipsis;
@ -187,6 +191,14 @@ ul {
max-width: 10em; max-width: 10em;
} }
a.filter {
color: inherit;
}
a:hover.filter {
text-decoration: underline dotted;
}
.tag-value { .tag-value {
background-color: #eee; background-color: #eee;
padding: 0 6px; padding: 0 6px;
@ -219,6 +231,11 @@ ul {
justify-content: space-between; justify-content: space-between;
} }
.balance-assertion {
filter: contrast(50%);
font-size: smaller;
}
.negative { .negative {
color: #a94442; color: #a94442;
} }

View File

@ -25,9 +25,11 @@ $if elem AddPermission perms
<div .status-cleared title="Tarkistettu" aria-label="Tarkistettu"> <div .status-cleared title="Tarkistettu" aria-label="Tarkistettu">
$if not $ T.null $ tcode torig $if not $ T.null $ tcode torig
<div .transaction-code> <div .transaction-code>
<a href=@?{addCodeQuery $ tcode torig} .filter>
(#{tcode torig}) (#{tcode torig})
$if not $ T.null $ T.strip payee $if not $ T.null $ T.strip payee
<span .transaction-payee> <span .transaction-payee>
<a href=@?{addPayeeQuery payee} .filter>
#{payee} #{payee}
$if not $ T.null $ T.strip note $if not $ T.null $ T.strip note
<details .transaction-note .elided-text> <details .transaction-note .elided-text>
@ -40,29 +42,45 @@ $if elem AddPermission perms
$forall (tagName, tagValue) <- filter isVisibleTag $ ttags torig $forall (tagName, tagValue) <- filter isVisibleTag $ ttags torig
<li .tag title="#{mconcat [tagName, ": ", tagValue]}"> <li .tag title="#{mconcat [tagName, ": ", tagValue]}">
<div .tag-name> <div .tag-name>
<a href=@?{addTagQuery tagName tagValue} .filter>
#{tagName} #{tagName}
$if not $ T.null $ T.strip tagValue $if not $ T.null $ T.strip tagValue
<div .tag-value> <div .tag-value>
$if isAbsoluteURI tagValue
<a href="#{tagValue}">#{tagValue}
$elseif tagName == "tosite"
<a href="@{documentRoute tagValue}">#{tagValue}
$else
#{tagValue} #{tagValue}
<ul .postings> <ul .postings>
$forall Posting { paccount = acc, pamount = amt, ptags = tags } <- tpostings torig $forall Posting { paccount = acc, pamount = amt, pbalanceassertion = passert, ptags = tags } <- tpostings torig
<li .posting> <li .posting>
<div .posting-row> <div .posting-row>
<div .account> <div .account>
<a href="@?{acctlink acc}##{tindex torig}" title="#{acc}"> <a href="@?{acctlink acc}##{tindex torig}" title="#{acc}">
#{elideAccountName 40 acc} #{elideAccountName 40 acc}
<div .amount style="text-align:right;"> <div .amount style="text-align:right;">
<span .posted-amount>
^{mixedAmountAsHtml amt} ^{mixedAmountAsHtml amt}
$maybe BalanceAssertion { baamount = assertAmt } <- passert
<span .balance-assertion>
jälkeen ^{mixedAmountAsHtml $ mixedAmount assertAmt}
$if null $ filter (isPostingTag acc) $ filter isVisibleTag tags $if null $ filter (isPostingTag acc) $ filter isVisibleTag tags
$else $else
<ul .tags> <ul .tags>
$forall (tagName, tagValue) <- filter (isPostingTag acc) $ filter isVisibleTag tags $forall (tagName, tagValue) <- filter (isPostingTag acc) $ filter isVisibleTag tags
<li .tag title="#{mconcat [tagName, ": ", tagValue]}"> <li .tag title="#{mconcat [tagName, ": ", tagValue]}">
<div .tag-name> <div .tag-name>
<a href=@?{addTagQuery tagName tagValue} .filter>
#{tagName} #{tagName}
$if not $ T.null $ T.strip tagValue $if not $ T.null $ T.strip tagValue
<div .tag-value> <div .tag-value>
$if isAbsoluteURI tagValue
<a href="#{tagValue}">#{tagValue}
$elseif tagName == "tosite"
<a href="@{documentRoute tagValue}">#{tagValue}
$else
#{tagValue} #{tagValue}
$if elem AddPermission perms $if elem AddPermission perms