mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-23 10:58:26 +00:00
624 lines
18 KiB
Elm
624 lines
18 KiB
Elm
{-
|
|
Copyright 2020 Eike K. & Contributors
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
-}
|
|
|
|
|
|
module Comp.ItemCard exposing
|
|
( Model
|
|
, Msg
|
|
, UpdateResult
|
|
, ViewConfig
|
|
, init
|
|
, update
|
|
, view2
|
|
)
|
|
|
|
import Api
|
|
import Api.Model.AttachmentLight exposing (AttachmentLight)
|
|
import Api.Model.HighlightEntry exposing (HighlightEntry)
|
|
import Api.Model.ItemLight exposing (ItemLight)
|
|
import Comp.LinkTarget exposing (LinkTarget(..))
|
|
import Data.Direction
|
|
import Data.Fields
|
|
import Data.Flags exposing (Flags)
|
|
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 (..)
|
|
import Html.Events exposing (onClick)
|
|
import Markdown
|
|
import Messages.Comp.ItemCard exposing (Texts)
|
|
import Page exposing (Page(..))
|
|
import Set exposing (Set)
|
|
import Styles as S
|
|
import Util.CustomField
|
|
import Util.ItemDragDrop as DD
|
|
import Util.List
|
|
import Util.Maybe
|
|
import Util.String
|
|
|
|
|
|
type alias Model =
|
|
{ previewAttach : Maybe AttachmentLight
|
|
}
|
|
|
|
|
|
type Msg
|
|
= CyclePreview ItemLight
|
|
| ToggleSelectItem (Set String) String
|
|
| ItemDDMsg DD.Msg
|
|
| SetLinkTarget LinkTarget
|
|
|
|
|
|
type alias ViewConfig =
|
|
{ selection : ItemSelection
|
|
, extraClasses : String
|
|
, previewUrl : AttachmentLight -> String
|
|
, previewUrlFallback : ItemLight -> String
|
|
, attachUrl : AttachmentLight -> String
|
|
, detailPage : ItemLight -> Page
|
|
}
|
|
|
|
|
|
type alias UpdateResult =
|
|
{ model : Model
|
|
, dragModel : DD.Model
|
|
, selection : ItemSelection
|
|
, linkTarget : LinkTarget
|
|
}
|
|
|
|
|
|
init : Model
|
|
init =
|
|
{ previewAttach = Nothing
|
|
}
|
|
|
|
|
|
currentAttachment : Model -> ItemLight -> Maybe AttachmentLight
|
|
currentAttachment model item =
|
|
Util.Maybe.or
|
|
[ model.previewAttach
|
|
, List.head item.attachments
|
|
]
|
|
|
|
|
|
currentPosition : Model -> ItemLight -> Int
|
|
currentPosition model item =
|
|
let
|
|
filter cur el =
|
|
cur.id == el.id
|
|
in
|
|
case model.previewAttach of
|
|
Just a ->
|
|
case Util.List.findIndexed (filter a) item.attachments of
|
|
Just ( _, n ) ->
|
|
n + 1
|
|
|
|
Nothing ->
|
|
1
|
|
|
|
Nothing ->
|
|
1
|
|
|
|
|
|
|
|
--- Update
|
|
|
|
|
|
update : DD.Model -> Msg -> Model -> UpdateResult
|
|
update ddm msg model =
|
|
case msg of
|
|
ItemDDMsg lm ->
|
|
let
|
|
ddd =
|
|
DD.update lm ddm
|
|
in
|
|
UpdateResult model ddd.model Data.ItemSelection.Inactive LinkNone
|
|
|
|
ToggleSelectItem ids id ->
|
|
let
|
|
newSet =
|
|
if Set.member id ids then
|
|
Set.remove id ids
|
|
|
|
else
|
|
Set.insert id ids
|
|
in
|
|
UpdateResult model ddm (Data.ItemSelection.Active newSet) LinkNone
|
|
|
|
CyclePreview item ->
|
|
let
|
|
mainAttach =
|
|
currentAttachment model item
|
|
|
|
next =
|
|
Util.List.findNext (\e -> Just e.id == Maybe.map .id mainAttach) item.attachments
|
|
in
|
|
UpdateResult { model | previewAttach = next }
|
|
ddm
|
|
Data.ItemSelection.Inactive
|
|
LinkNone
|
|
|
|
SetLinkTarget target ->
|
|
UpdateResult model ddm Data.ItemSelection.Inactive target
|
|
|
|
|
|
|
|
--- View2
|
|
|
|
|
|
view2 : Texts -> ViewConfig -> UiSettings -> Flags -> Model -> ItemLight -> Html Msg
|
|
view2 texts cfg settings flags model item =
|
|
let
|
|
isCreated =
|
|
item.state == "created"
|
|
|
|
isDeleted =
|
|
item.state == "deleted"
|
|
|
|
cardColor =
|
|
if isCreated then
|
|
"text-blue-500 dark:text-lightblue-500"
|
|
|
|
else if isDeleted then
|
|
"text-red-600 dark:text-orange-600"
|
|
|
|
else
|
|
""
|
|
|
|
fieldHidden f =
|
|
Data.UiSettings.fieldHidden settings f
|
|
|
|
cardAction =
|
|
case cfg.selection of
|
|
Data.ItemSelection.Inactive ->
|
|
[ Page.href (cfg.detailPage item)
|
|
]
|
|
|
|
Data.ItemSelection.Active ids ->
|
|
[ onClick (ToggleSelectItem ids item.id)
|
|
, href "#"
|
|
]
|
|
|
|
selectedDimmer =
|
|
div
|
|
[ classList
|
|
[ ( "hidden", not (isSelected cfg item.id) )
|
|
]
|
|
, class S.dimmerCard
|
|
, class "rounded-lg"
|
|
]
|
|
[ div [ class "text-9xl text-blue-400 hover:text-blue-500 dark:text-lightblue-300 dark:hover:text-lightblue-200" ]
|
|
[ a
|
|
cardAction
|
|
[ i [ class "fa fa-check-circle font-thin" ] []
|
|
]
|
|
]
|
|
]
|
|
in
|
|
div
|
|
([ class cfg.extraClasses
|
|
, class "ds-item-card relative hover:shadow-lg rounded-lg flex flex-col break-all"
|
|
, classList
|
|
[ ( "border border-gray-400 dark:border-bluegray-600 dark:hover:border-bluegray-500", not (isMultiSelectMode cfg) )
|
|
, ( "border-2 border-gray-800 border-dashed dark:border-lightblue-500", isMultiSelectMode cfg )
|
|
]
|
|
, id item.id
|
|
]
|
|
++ DD.draggable ItemDDMsg item.id
|
|
)
|
|
((if fieldHidden Data.Fields.PreviewImage then
|
|
[]
|
|
|
|
else
|
|
[ previewImage2 cfg settings cardAction model item
|
|
]
|
|
)
|
|
++ [ mainContent2 texts cardAction cardColor isCreated isDeleted settings cfg item
|
|
, metaDataContent2 texts settings item
|
|
, notesContent2 settings item
|
|
, fulltextResultsContent2 item
|
|
, previewMenu2 texts settings flags cfg model item (currentAttachment model item)
|
|
, selectedDimmer
|
|
]
|
|
)
|
|
|
|
|
|
fulltextResultsContent2 : ItemLight -> Html Msg
|
|
fulltextResultsContent2 item =
|
|
div
|
|
[ class "ds-card-search-hl flex flex-col text-sm px-2 bg-yellow-50 dark:bg-indigo-800 dark:bg-opacity-60 "
|
|
, classList
|
|
[ ( "hidden", item.highlighting == [] )
|
|
]
|
|
]
|
|
(List.map renderHighlightEntry2 item.highlighting)
|
|
|
|
|
|
templateCtx : Texts -> IT.TemplateContext
|
|
templateCtx texts =
|
|
{ dateFormatLong = texts.formatDateLong
|
|
, dateFormatShort = texts.formatDateShort
|
|
, directionLabel = texts.directionLabel
|
|
}
|
|
|
|
|
|
metaDataContent2 : Texts -> UiSettings -> ItemLight -> Html Msg
|
|
metaDataContent2 texts settings item =
|
|
let
|
|
fieldHidden f =
|
|
Data.UiSettings.fieldHidden settings f
|
|
in
|
|
div [ class "px-2 pb-1 flex flex-row items-center justify-between text-sm opacity-80" ]
|
|
[ div [ class "flex flex-row justify-between" ]
|
|
[ div
|
|
[ classList
|
|
[ ( "hidden", fieldHidden Data.Fields.Folder )
|
|
]
|
|
, class "hover:opacity-60"
|
|
, title texts.basics.folder
|
|
]
|
|
[ Icons.folderIcon2 "mr-2"
|
|
, Comp.LinkTarget.makeFolderLink item
|
|
[ ( "hover:opacity-60", True ) ]
|
|
SetLinkTarget
|
|
]
|
|
]
|
|
, div [ class "flex-grow" ] []
|
|
, div [ class "flex flex-row items-center justify-end" ]
|
|
[ div [ class "" ]
|
|
[ Icons.sourceIcon2 "mr-2"
|
|
, Comp.LinkTarget.makeSourceLink
|
|
[ ( "hover:opacity-60", True ) ]
|
|
SetLinkTarget
|
|
(IT.render IT.source (templateCtx texts) item)
|
|
]
|
|
]
|
|
]
|
|
|
|
|
|
notesContent2 : UiSettings -> ItemLight -> Html Msg
|
|
notesContent2 settings item =
|
|
div
|
|
[ classList
|
|
[ ( "hidden"
|
|
, settings.itemSearchNoteLength
|
|
<= 0
|
|
|| Util.String.isNothingOrBlank item.notes
|
|
)
|
|
]
|
|
, class "px-2 py-2 border-t dark:border-bluegray-600 opacity-50 text-sm"
|
|
]
|
|
[ Maybe.withDefault "" item.notes
|
|
|> Util.String.ellipsis settings.itemSearchNoteLength
|
|
|> text
|
|
]
|
|
|
|
|
|
mainContent2 :
|
|
Texts
|
|
-> List (Attribute Msg)
|
|
-> String
|
|
-> Bool
|
|
-> Bool
|
|
-> UiSettings
|
|
-> ViewConfig
|
|
-> ItemLight
|
|
-> Html Msg
|
|
mainContent2 texts _ cardColor isCreated isDeleted settings _ item =
|
|
let
|
|
dirIcon =
|
|
i
|
|
[ class (Data.Direction.iconFromMaybe2 item.direction)
|
|
, class "mr-2 w-4 text-center"
|
|
, classList [ ( "hidden", fieldHidden Data.Fields.Direction ) ]
|
|
, IT.render IT.direction (templateCtx texts) item |> title
|
|
]
|
|
[]
|
|
|
|
fieldHidden f =
|
|
Data.UiSettings.fieldHidden settings f
|
|
|
|
titlePattern =
|
|
settings.cardTitleTemplate.template
|
|
|
|
subtitlePattern =
|
|
settings.cardSubtitleTemplate.template
|
|
in
|
|
div
|
|
[ class "flex flex-col px-2 py-2 mb-auto" ]
|
|
[ div
|
|
[ classList
|
|
[ ( "hidden"
|
|
, fieldHidden Data.Fields.CorrOrg
|
|
&& fieldHidden Data.Fields.CorrPerson
|
|
)
|
|
]
|
|
, title texts.basics.correspondent
|
|
]
|
|
(Icons.correspondentIcon2 "mr-2 w-4 text-center"
|
|
:: Comp.LinkTarget.makeCorrLink item [ ( "hover:opacity-75", True ) ] SetLinkTarget
|
|
)
|
|
, div
|
|
[ classList
|
|
[ ( "hidden"
|
|
, fieldHidden Data.Fields.ConcPerson
|
|
&& fieldHidden Data.Fields.ConcEquip
|
|
)
|
|
]
|
|
, title texts.basics.concerning
|
|
]
|
|
(Icons.concernedIcon2 "mr-2 w-4 text-center"
|
|
:: Comp.LinkTarget.makeConcLink item [ ( "hover:opacity-75", True ) ] SetLinkTarget
|
|
)
|
|
, div
|
|
[ class "font-bold py-1 text-lg"
|
|
, classList [ ( "hidden", IT.render titlePattern (templateCtx texts) item == "" ) ]
|
|
]
|
|
[ IT.render titlePattern (templateCtx texts) item |> text
|
|
]
|
|
, div
|
|
[ classList
|
|
[ ( "absolute right-1 top-1 text-4xl", True )
|
|
, ( cardColor, True )
|
|
, ( "hidden", not isCreated )
|
|
]
|
|
, title texts.new
|
|
]
|
|
[ i [ class "ml-2 fa fa-exclamation-circle" ] []
|
|
]
|
|
, div
|
|
[ classList
|
|
[ ( "absolute right-1 top-1 text-4xl", True )
|
|
, ( cardColor, True )
|
|
, ( "hidden", not isDeleted )
|
|
]
|
|
, title texts.basics.deleted
|
|
]
|
|
[ i [ class "ml-2 fa fa-trash-alt" ] []
|
|
]
|
|
, div
|
|
[ classList
|
|
[ ( "opacity-75", True )
|
|
, ( "hidden", IT.render subtitlePattern (templateCtx texts) item == "" )
|
|
]
|
|
]
|
|
[ dirIcon
|
|
, IT.render subtitlePattern (templateCtx texts) item |> text
|
|
]
|
|
, div [ class "" ]
|
|
[ mainTagsAndFields2 settings item
|
|
]
|
|
]
|
|
|
|
|
|
mainTagsAndFields2 : UiSettings -> ItemLight -> Html Msg
|
|
mainTagsAndFields2 settings item =
|
|
let
|
|
fieldHidden f =
|
|
Data.UiSettings.fieldHidden settings f
|
|
|
|
hideTags =
|
|
item.tags == [] || fieldHidden Data.Fields.Tag
|
|
|
|
hideFields =
|
|
item.customfields == [] || fieldHidden Data.Fields.CustomFields
|
|
|
|
showTag tag =
|
|
Comp.LinkTarget.makeTagIconLink
|
|
tag
|
|
(i [ class "fa fa-tag mr-2" ] [])
|
|
[ ( "label ml-1 mt-1 font-semibold hover:opacity-75", True )
|
|
, ( Data.UiSettings.tagColorString2 tag settings, True )
|
|
]
|
|
SetLinkTarget
|
|
|
|
showField fv =
|
|
Comp.LinkTarget.makeCustomFieldLink2 fv
|
|
[ ( S.basicLabel, True )
|
|
, ( "ml-1 mt-1 font-semibold hover:opacity-75", True )
|
|
]
|
|
SetLinkTarget
|
|
|
|
renderFields =
|
|
if hideFields then
|
|
[]
|
|
|
|
else
|
|
List.sortBy Util.CustomField.nameOrLabel item.customfields
|
|
|> List.map showField
|
|
|
|
renderTags =
|
|
if hideTags then
|
|
[]
|
|
|
|
else
|
|
List.map showTag item.tags
|
|
in
|
|
div
|
|
[ classList
|
|
[ ( "flex flex-row items-center flex-wrap justify-end text-xs font-medium my-1", True )
|
|
, ( "hidden", hideTags && hideFields )
|
|
]
|
|
]
|
|
(renderFields ++ renderTags)
|
|
|
|
|
|
previewImage2 : ViewConfig -> UiSettings -> List (Attribute Msg) -> Model -> ItemLight -> Html Msg
|
|
previewImage2 cfg settings cardAction model item =
|
|
let
|
|
mainAttach =
|
|
currentAttachment model item
|
|
|
|
previewUrl =
|
|
Maybe.map cfg.previewUrl mainAttach
|
|
|> Maybe.withDefault (cfg.previewUrlFallback item)
|
|
in
|
|
a
|
|
([ class "overflow-hidden block bg-gray-50 dark:bg-bluegray-700 dark:bg-opacity-40 border-gray-400 dark:hover:border-bluegray-500 rounded-t-lg"
|
|
, class (Data.UiSettings.cardPreviewSize2 settings)
|
|
]
|
|
++ cardAction
|
|
)
|
|
[ img
|
|
[ class "preview-image mx-auto pt-1"
|
|
, classList
|
|
[ ( "rounded-t-lg w-full -mt-1", settings.cardPreviewFullWidth )
|
|
, ( Data.UiSettings.cardPreviewSize2 settings, not settings.cardPreviewFullWidth )
|
|
]
|
|
, src previewUrl
|
|
]
|
|
[]
|
|
]
|
|
|
|
|
|
previewMenu2 : Texts -> UiSettings -> Flags -> ViewConfig -> Model -> ItemLight -> Maybe AttachmentLight -> Html Msg
|
|
previewMenu2 texts settings flags cfg model item mainAttach =
|
|
let
|
|
pageCount =
|
|
Maybe.andThen .pageCount mainAttach
|
|
|> Maybe.withDefault 0
|
|
|
|
attachCount =
|
|
List.length item.attachments
|
|
|
|
fieldHidden f =
|
|
Data.UiSettings.fieldHidden settings f
|
|
|
|
mkAttachUrl attach =
|
|
Data.UiSettings.pdfUrl settings flags (cfg.attachUrl attach)
|
|
|
|
attachUrl =
|
|
Maybe.map mkAttachUrl mainAttach
|
|
|> Maybe.withDefault "/api/v1/sec/attachment/none"
|
|
|
|
dueDate =
|
|
IT.render IT.dueDateShort (templateCtx texts) item
|
|
|
|
dueDateLabel =
|
|
div
|
|
[ classList
|
|
[ ( " hidden"
|
|
, item.dueDate
|
|
== Nothing
|
|
|| fieldHidden Data.Fields.DueDate
|
|
)
|
|
]
|
|
, class "label font-semibold text-sm border-gray-300 dark:border-bluegray-600"
|
|
, title (texts.dueOn ++ " " ++ dueDate)
|
|
]
|
|
[ Icons.dueDateIcon2 "mr-2"
|
|
, text (" " ++ dueDate)
|
|
]
|
|
in
|
|
div [ class "px-2 py-1 flex flex-row flex-wrap bg-gray-50 dark:bg-bluegray-700 dark:bg-opacity-40 border-0 rounded-b-lg md:text-sm" ]
|
|
[ a
|
|
[ class S.secondaryBasicButtonPlain
|
|
, class "px-2 py-1 border rounded "
|
|
, href attachUrl
|
|
, target "_self"
|
|
, title texts.openAttachmentFile
|
|
]
|
|
[ i [ class "fa fa-eye" ] []
|
|
]
|
|
, a
|
|
[ class S.secondaryBasicButtonPlain
|
|
, class "px-2 py-1 border rounded ml-2"
|
|
, Page.href (cfg.detailPage item)
|
|
, title texts.gotoDetail
|
|
]
|
|
[ i [ class "fa fa-edit" ] []
|
|
]
|
|
, div
|
|
[ classList [ ( "hidden", attachCount > 1 && not (fieldHidden Data.Fields.PreviewImage) ) ]
|
|
, class "ml-2"
|
|
]
|
|
[ div [ class "px-2 rounded border border-gray-300 dark:border-bluegray-600 py-1" ]
|
|
[ text (String.fromInt pageCount)
|
|
, text "p."
|
|
]
|
|
]
|
|
, div
|
|
[ class "flex flex-row items-center ml-2"
|
|
, classList [ ( "hidden", attachCount <= 1 || fieldHidden Data.Fields.PreviewImage ) ]
|
|
]
|
|
[ a
|
|
[ class S.secondaryBasicButtonPlain
|
|
, class "px-2 py-1 border rounded-l block"
|
|
, title texts.cycleAttachments
|
|
, href "#"
|
|
, onClick (CyclePreview item)
|
|
]
|
|
[ i [ class "fa fa-arrow-right" ] []
|
|
]
|
|
, div [ class "px-2 rounded-r border-t border-r border-b border-gray-500 dark:border-bluegray-500 py-1" ]
|
|
[ currentPosition model item
|
|
|> String.fromInt
|
|
|> text
|
|
, text "/"
|
|
, text (attachCount |> String.fromInt)
|
|
, text ", "
|
|
, text (String.fromInt pageCount)
|
|
, text "p."
|
|
]
|
|
]
|
|
, div [ class "flex-grow" ] []
|
|
, div [ class "flex flex-row items-center justify-end" ]
|
|
[ dueDateLabel
|
|
]
|
|
]
|
|
|
|
|
|
renderHighlightEntry2 : HighlightEntry -> Html Msg
|
|
renderHighlightEntry2 entry =
|
|
let
|
|
stripWhitespace str =
|
|
String.trim str
|
|
|> String.replace "```" ""
|
|
|> String.replace "\t" " "
|
|
|> String.replace "\n\n" "\n"
|
|
|> String.lines
|
|
|> List.map String.trim
|
|
|> String.join "\n"
|
|
in
|
|
div [ class "content" ]
|
|
(div [ class "font-semibold" ]
|
|
[ i [ class "fa fa-caret-right mr-1 " ] []
|
|
, text (entry.name ++ ":")
|
|
]
|
|
:: List.map
|
|
(\str ->
|
|
Markdown.toHtml [ class "opacity-80 " ] <|
|
|
(stripWhitespace str ++ "…")
|
|
)
|
|
entry.lines
|
|
)
|
|
|
|
|
|
|
|
--- Helpers
|
|
|
|
|
|
isSelected : ViewConfig -> String -> Bool
|
|
isSelected cfg id =
|
|
case cfg.selection of
|
|
Data.ItemSelection.Active ids ->
|
|
Set.member id ids
|
|
|
|
Data.ItemSelection.Inactive ->
|
|
False
|
|
|
|
|
|
isMultiSelectMode : ViewConfig -> Bool
|
|
isMultiSelectMode cfg =
|
|
case cfg.selection of
|
|
Data.ItemSelection.Active _ ->
|
|
True
|
|
|
|
Data.ItemSelection.Inactive ->
|
|
False
|