mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-02-15 20:33:26 +00:00
Use a template for rendering title and subtitle of the item card
Introduces `ItemTemplate` to conveniently create strings given an item.
This commit is contained in:
parent
2aecf08706
commit
81a136d915
@ -11,13 +11,13 @@ module Comp.ItemCard exposing
|
||||
import Api
|
||||
import Api.Model.AttachmentLight exposing (AttachmentLight)
|
||||
import Api.Model.HighlightEntry exposing (HighlightEntry)
|
||||
import Api.Model.IdName exposing (IdName)
|
||||
import Api.Model.ItemLight exposing (ItemLight)
|
||||
import Comp.LinkTarget exposing (LinkTarget(..))
|
||||
import Data.Direction
|
||||
import Data.Fields
|
||||
import Data.Icons as Icons
|
||||
import Data.ItemSelection exposing (ItemSelection)
|
||||
import Data.ItemTemplate as IT
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
@ -30,7 +30,6 @@ import Util.ItemDragDrop as DD
|
||||
import Util.List
|
||||
import Util.Maybe
|
||||
import Util.String
|
||||
import Util.Time
|
||||
|
||||
|
||||
type alias Model =
|
||||
@ -220,8 +219,7 @@ metaDataContent settings item =
|
||||
Data.UiSettings.fieldHidden settings f
|
||||
|
||||
dueDate =
|
||||
Maybe.map Util.Time.formatDateShort item.dueDate
|
||||
|> Maybe.withDefault ""
|
||||
IT.render IT.dueDateShort item
|
||||
in
|
||||
div [ class "content" ]
|
||||
[ div [ class "ui horizontal link list" ]
|
||||
@ -268,7 +266,7 @@ metaDataContent settings item =
|
||||
[ class "item"
|
||||
, title "Source"
|
||||
]
|
||||
[ text item.source
|
||||
[ IT.render IT.source item |> text
|
||||
]
|
||||
, div
|
||||
[ classList
|
||||
@ -321,6 +319,12 @@ mainContent cardAction cardColor isConfirmed settings _ item =
|
||||
|
||||
fieldHidden f =
|
||||
Data.UiSettings.fieldHidden settings f
|
||||
|
||||
titlePattern =
|
||||
IT.name
|
||||
|
||||
subtitlePattern =
|
||||
IT.dateLong
|
||||
in
|
||||
a
|
||||
[ class "content"
|
||||
@ -329,18 +333,16 @@ mainContent cardAction cardColor isConfirmed settings _ item =
|
||||
]
|
||||
[ if fieldHidden Data.Fields.Direction then
|
||||
div [ class "header" ]
|
||||
[ Util.String.underscoreToSpace item.name |> text
|
||||
[ IT.render titlePattern item |> text
|
||||
]
|
||||
|
||||
else
|
||||
div
|
||||
[ class "header"
|
||||
, Data.Direction.labelFromMaybe item.direction
|
||||
|> title
|
||||
, IT.render IT.direction item |> title
|
||||
]
|
||||
[ dirIcon
|
||||
, Util.String.underscoreToSpace item.name
|
||||
|> text
|
||||
, IT.render titlePattern item |> text
|
||||
]
|
||||
, div
|
||||
[ classList
|
||||
@ -358,7 +360,7 @@ mainContent cardAction cardColor isConfirmed settings _ item =
|
||||
, ( "invisible hidden", fieldHidden Data.Fields.Date )
|
||||
]
|
||||
]
|
||||
[ Util.Time.formatDate item.date |> text
|
||||
[ IT.render subtitlePattern item |> text
|
||||
]
|
||||
, div [ class "meta description" ]
|
||||
[ mainTagsAndFields settings item
|
||||
|
386
modules/webapp/src/main/elm/Data/ItemTemplate.elm
Normal file
386
modules/webapp/src/main/elm/Data/ItemTemplate.elm
Normal file
@ -0,0 +1,386 @@
|
||||
module Data.ItemTemplate exposing
|
||||
( ItemTemplate
|
||||
, concEquip
|
||||
, concPerson
|
||||
, concat
|
||||
, concerning
|
||||
, corrOrg
|
||||
, corrPerson
|
||||
, correspondent
|
||||
, dateLong
|
||||
, dateShort
|
||||
, direction
|
||||
, dueDateLong
|
||||
, dueDateShort
|
||||
, empty
|
||||
, fileCount
|
||||
, folder
|
||||
, from
|
||||
, fromMaybe
|
||||
, helpMessage
|
||||
, isEmpty
|
||||
, literal
|
||||
, map
|
||||
, name
|
||||
, nonEmpty
|
||||
, readTemplate
|
||||
, render
|
||||
, source
|
||||
, splitTokens
|
||||
)
|
||||
|
||||
import Api.Model.IdName exposing (IdName)
|
||||
import Api.Model.ItemLight exposing (ItemLight)
|
||||
import Data.Direction
|
||||
import Set
|
||||
import Util.List
|
||||
import Util.String
|
||||
import Util.Time
|
||||
|
||||
|
||||
type ItemTemplate
|
||||
= ItemTemplate (ItemLight -> String)
|
||||
|
||||
|
||||
readTemplate : String -> Maybe ItemTemplate
|
||||
readTemplate str =
|
||||
let
|
||||
read tokens =
|
||||
List.map patternToken tokens
|
||||
|> concat
|
||||
in
|
||||
if str == "" then
|
||||
Just empty
|
||||
|
||||
else
|
||||
Maybe.map read (splitTokens str)
|
||||
|
||||
|
||||
render : ItemTemplate -> ItemLight -> String
|
||||
render pattern item =
|
||||
case pattern of
|
||||
ItemTemplate f ->
|
||||
f item
|
||||
|
||||
|
||||
isEmpty : ItemTemplate -> ItemLight -> Bool
|
||||
isEmpty pattern item =
|
||||
render pattern item |> String.isEmpty
|
||||
|
||||
|
||||
nonEmpty : ItemTemplate -> ItemLight -> Bool
|
||||
nonEmpty pattern item =
|
||||
isEmpty pattern item |> not
|
||||
|
||||
|
||||
|
||||
--- Pattern Combinators
|
||||
|
||||
|
||||
map : (String -> String) -> ItemTemplate -> ItemTemplate
|
||||
map f pattern =
|
||||
case pattern of
|
||||
ItemTemplate p ->
|
||||
from (p >> f)
|
||||
|
||||
|
||||
map2 : (String -> String -> String) -> ItemTemplate -> ItemTemplate -> ItemTemplate
|
||||
map2 f pattern1 pattern2 =
|
||||
case ( pattern1, pattern2 ) of
|
||||
( ItemTemplate p1, ItemTemplate p2 ) ->
|
||||
from (\i -> f (p1 i) (p2 i))
|
||||
|
||||
|
||||
combine : String -> ItemTemplate -> ItemTemplate -> ItemTemplate
|
||||
combine sep p1 p2 =
|
||||
map2
|
||||
(\s1 ->
|
||||
\s2 ->
|
||||
List.filter (String.isEmpty >> not) [ s1, s2 ]
|
||||
|> String.join sep
|
||||
)
|
||||
p1
|
||||
p2
|
||||
|
||||
|
||||
concat : List ItemTemplate -> ItemTemplate
|
||||
concat patterns =
|
||||
from
|
||||
(\i ->
|
||||
List.map (\p -> render p i) patterns
|
||||
|> String.join ""
|
||||
)
|
||||
|
||||
|
||||
firstNonEmpty : List ItemTemplate -> ItemTemplate
|
||||
firstNonEmpty patterns =
|
||||
from
|
||||
(\i ->
|
||||
List.map (\p -> render p i) patterns
|
||||
|> List.filter (String.isEmpty >> not)
|
||||
|> List.head
|
||||
|> Maybe.withDefault ""
|
||||
)
|
||||
|
||||
|
||||
|
||||
--- Patterns
|
||||
|
||||
|
||||
from : (ItemLight -> String) -> ItemTemplate
|
||||
from f =
|
||||
ItemTemplate f
|
||||
|
||||
|
||||
fromMaybe : (ItemLight -> Maybe String) -> ItemTemplate
|
||||
fromMaybe f =
|
||||
ItemTemplate (f >> Maybe.withDefault "")
|
||||
|
||||
|
||||
literal : String -> ItemTemplate
|
||||
literal str =
|
||||
ItemTemplate (\_ -> str)
|
||||
|
||||
|
||||
empty : ItemTemplate
|
||||
empty =
|
||||
literal ""
|
||||
|
||||
|
||||
name : ItemTemplate
|
||||
name =
|
||||
ItemTemplate (.name >> Util.String.underscoreToSpace)
|
||||
|
||||
|
||||
direction : ItemTemplate
|
||||
direction =
|
||||
let
|
||||
dirStr ms =
|
||||
Maybe.andThen Data.Direction.fromString ms
|
||||
|> Maybe.map Data.Direction.toString
|
||||
in
|
||||
fromMaybe (.direction >> dirStr)
|
||||
|
||||
|
||||
dateLong : ItemTemplate
|
||||
dateLong =
|
||||
ItemTemplate (.date >> Util.Time.formatDate)
|
||||
|
||||
|
||||
dateShort : ItemTemplate
|
||||
dateShort =
|
||||
ItemTemplate (.date >> Util.Time.formatDateShort)
|
||||
|
||||
|
||||
dueDateLong : ItemTemplate
|
||||
dueDateLong =
|
||||
fromMaybe (.dueDate >> Maybe.map Util.Time.formatDate)
|
||||
|
||||
|
||||
dueDateShort : ItemTemplate
|
||||
dueDateShort =
|
||||
fromMaybe (.dueDate >> Maybe.map Util.Time.formatDateShort)
|
||||
|
||||
|
||||
source : ItemTemplate
|
||||
source =
|
||||
ItemTemplate .source
|
||||
|
||||
|
||||
folder : ItemTemplate
|
||||
folder =
|
||||
ItemTemplate (.folder >> getName)
|
||||
|
||||
|
||||
corrOrg : ItemTemplate
|
||||
corrOrg =
|
||||
ItemTemplate (.corrOrg >> getName)
|
||||
|
||||
|
||||
corrPerson : ItemTemplate
|
||||
corrPerson =
|
||||
ItemTemplate (.corrPerson >> getName)
|
||||
|
||||
|
||||
correspondent : ItemTemplate
|
||||
correspondent =
|
||||
combine ", " corrOrg corrPerson
|
||||
|
||||
|
||||
concPerson : ItemTemplate
|
||||
concPerson =
|
||||
ItemTemplate (.concPerson >> getName)
|
||||
|
||||
|
||||
concEquip : ItemTemplate
|
||||
concEquip =
|
||||
ItemTemplate (.concEquipment >> getName)
|
||||
|
||||
|
||||
concerning : ItemTemplate
|
||||
concerning =
|
||||
combine ", " concPerson concEquip
|
||||
|
||||
|
||||
fileCount : ItemTemplate
|
||||
fileCount =
|
||||
ItemTemplate (.fileCount >> String.fromInt)
|
||||
|
||||
|
||||
|
||||
--- Helpers
|
||||
|
||||
|
||||
getName : Maybe IdName -> String
|
||||
getName =
|
||||
Maybe.map .name >> Maybe.withDefault ""
|
||||
|
||||
|
||||
|
||||
--- Parse pattern
|
||||
|
||||
|
||||
helpMessage : String
|
||||
helpMessage =
|
||||
"""
|
||||
A pattern allows to customize the title and subtitle of each card.
|
||||
Variables expressions are enclosed in `{{` and `}}`, other text is
|
||||
used as-is. The following variables are available:
|
||||
|
||||
- `{{name}}` the item name
|
||||
- `{{source}}` the source the item was created from
|
||||
- `{{folder}}` the items folder
|
||||
- `{{corrOrg}}` the correspondent organization
|
||||
- `{{corrPerson}}` the correspondent person
|
||||
- `{{correspondent}}` both organization and person separated by a comma
|
||||
- `{{concPerson}}` the concerning person
|
||||
- `{{concEquip}}` the concerning equipment
|
||||
- `{{concerning}}` both person and equipment separated by a comma
|
||||
- `{{fileCount}}` the number of attachments of this item
|
||||
- `{{dateLong}}` the item date as full formatted date
|
||||
- `{{dateShort}}` the item date as short formatted date (yyyy/mm/dd)
|
||||
- `{{dueDateLong}}` the item due date as full formatted date
|
||||
- `{{dueDateShort}}` the item due date as short formatted date (yyyy/mm/dd)
|
||||
- `{{direction}}` the items direction values as string
|
||||
|
||||
If some variable is not present, an empty string is rendered. You can
|
||||
combine multiple variables with `|` to use the first non-empty one,
|
||||
for example `{{corrOrg|corrPerson|-}}` would render the organization
|
||||
and if that is not present the person. If both are absent a dash `-`
|
||||
is rendered.
|
||||
"""
|
||||
|
||||
|
||||
knownPattern : String -> Maybe ItemTemplate
|
||||
knownPattern str =
|
||||
case str of
|
||||
"{{name}}" ->
|
||||
Just name
|
||||
|
||||
"{{source}}" ->
|
||||
Just source
|
||||
|
||||
"{{folder}}" ->
|
||||
Just folder
|
||||
|
||||
"{{corrOrg}}" ->
|
||||
Just corrOrg
|
||||
|
||||
"{{corrPerson}}" ->
|
||||
Just corrPerson
|
||||
|
||||
"{{correspondent}}" ->
|
||||
Just correspondent
|
||||
|
||||
"{{concPerson}}" ->
|
||||
Just concPerson
|
||||
|
||||
"{{concEquip}}" ->
|
||||
Just concEquip
|
||||
|
||||
"{{concerning}}" ->
|
||||
Just concerning
|
||||
|
||||
"{{fileCount}}" ->
|
||||
Just fileCount
|
||||
|
||||
"{{dateLong}}" ->
|
||||
Just dateLong
|
||||
|
||||
"{{dateShort}}" ->
|
||||
Just dateShort
|
||||
|
||||
"{{dueDateLong}}" ->
|
||||
Just dueDateLong
|
||||
|
||||
"{{dueDateShort}}" ->
|
||||
Just dueDateShort
|
||||
|
||||
"{{direction}}" ->
|
||||
Just direction
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
|
||||
patternToken : String -> ItemTemplate
|
||||
patternToken str =
|
||||
knownPattern str
|
||||
|> Maybe.withDefault
|
||||
(alternativeToken str
|
||||
|> Maybe.withDefault (literal str)
|
||||
)
|
||||
|
||||
|
||||
alternativeToken : String -> Maybe ItemTemplate
|
||||
alternativeToken str =
|
||||
let
|
||||
inner =
|
||||
String.dropLeft 2 str
|
||||
|> String.dropRight 2
|
||||
|> String.split "|"
|
||||
|> List.filter (String.isEmpty >> not)
|
||||
|
||||
pattern s =
|
||||
knownPattern ("{{" ++ s ++ "}}")
|
||||
|> Maybe.withDefault (literal s)
|
||||
in
|
||||
if String.startsWith "{{" str && String.endsWith "}}" str then
|
||||
case inner of
|
||||
[] ->
|
||||
Nothing
|
||||
|
||||
_ ->
|
||||
List.map pattern inner
|
||||
|> firstNonEmpty
|
||||
|> Just
|
||||
|
||||
else
|
||||
Nothing
|
||||
|
||||
|
||||
splitTokens : String -> Maybe (List String)
|
||||
splitTokens str =
|
||||
let
|
||||
begins =
|
||||
String.indexes "{{" str
|
||||
|
||||
ends =
|
||||
String.indexes "}}" str
|
||||
|> List.map ((+) 2)
|
||||
|
||||
indexes =
|
||||
Set.union (Set.fromList begins) (Set.fromList ends)
|
||||
|> Set.insert 0
|
||||
|> Set.insert (String.length str)
|
||||
|> Set.toList
|
||||
|> List.sort
|
||||
|
||||
mkSubstring i1 i2 =
|
||||
String.slice i1 i2 str
|
||||
in
|
||||
if List.length begins == List.length ends then
|
||||
Util.List.sliding mkSubstring indexes |> Just
|
||||
|
||||
else
|
||||
Nothing
|
@ -6,6 +6,7 @@ module Util.List exposing
|
||||
, findNext
|
||||
, findPrev
|
||||
, get
|
||||
, sliding
|
||||
)
|
||||
|
||||
|
||||
@ -88,3 +89,17 @@ dropRight n list =
|
||||
List.reverse list
|
||||
|> List.drop n
|
||||
|> List.reverse
|
||||
|
||||
|
||||
sliding : (a -> a -> b) -> List a -> List b
|
||||
sliding f list =
|
||||
let
|
||||
windows =
|
||||
case list of
|
||||
_ :: xs ->
|
||||
List.map2 Tuple.pair list xs
|
||||
|
||||
_ ->
|
||||
[]
|
||||
in
|
||||
List.map (\( e1, e2 ) -> f e1 e2) windows
|
||||
|
Loading…
Reference in New Issue
Block a user