mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-06 15:15:58 +00:00
Refactor scan mailbox form and add flag for post-processing
Mails are filtered once by using an imap search and then by some globs to filter files and subjects. Imap can search by subject via a string-contains, but not via globs or patterns (afaik). The subject filter is applied to all downloaded mail headers. Now for post processing (moving to some target folder or deleting), it can be chosen to post-process all "seen" mails or only those that matched all filters.
This commit is contained in:
parent
e6d67c368b
commit
96612e0e59
@ -37,7 +37,9 @@ case class ScanMailboxArgs(
|
|||||||
// a glob filter for the mail subject
|
// a glob filter for the mail subject
|
||||||
subjectFilter: Option[Glob],
|
subjectFilter: Option[Glob],
|
||||||
// the language for extraction and analysis
|
// the language for extraction and analysis
|
||||||
language: Option[Language]
|
language: Option[Language],
|
||||||
|
// apply additional filter to all mails or only imported
|
||||||
|
postHandleAll: Option[Boolean]
|
||||||
)
|
)
|
||||||
|
|
||||||
object ScanMailboxArgs {
|
object ScanMailboxArgs {
|
||||||
|
@ -14,6 +14,7 @@ import docspell.joex.scheduler.{Context, Task}
|
|||||||
import docspell.store.queries.QOrganization
|
import docspell.store.queries.QOrganization
|
||||||
import docspell.store.records._
|
import docspell.store.records._
|
||||||
|
|
||||||
|
import _root_.io.circe.syntax._
|
||||||
import emil.SearchQuery.{All, ReceivedDate}
|
import emil.SearchQuery.{All, ReceivedDate}
|
||||||
import emil.javamail.syntax._
|
import emil.javamail.syntax._
|
||||||
import emil.{MimeType => _, _}
|
import emil.{MimeType => _, _}
|
||||||
@ -31,8 +32,9 @@ object ScanMailboxTask {
|
|||||||
Task { ctx =>
|
Task { ctx =>
|
||||||
for {
|
for {
|
||||||
_ <- ctx.logger.info(
|
_ <- ctx.logger.info(
|
||||||
s"Start importing mails for user ${ctx.args.account.user.id}"
|
s"=== Start importing mails for user ${ctx.args.account.user.id}"
|
||||||
)
|
)
|
||||||
|
_ <- ctx.logger.debug(s"Settings: ${ctx.args.asJson.noSpaces}")
|
||||||
mailCfg <- getMailSettings(ctx)
|
mailCfg <- getMailSettings(ctx)
|
||||||
folders = ctx.args.folders.mkString(", ")
|
folders = ctx.args.folders.mkString(", ")
|
||||||
userId = ctx.args.account.user
|
userId = ctx.args.account.user
|
||||||
@ -249,25 +251,29 @@ object ScanMailboxTask {
|
|||||||
.getOrElse(Direction.Incoming)
|
.getOrElse(Direction.Incoming)
|
||||||
}
|
}
|
||||||
|
|
||||||
def postHandle[C](a: Access[F, C])(mh: MailHeader): MailOp[F, C, Unit] =
|
def postHandle[C](a: Access[F, C])(mh: MailHeaderItem): MailOp[F, C, Unit] = {
|
||||||
|
val postHandleAll = ctx.args.postHandleAll.exists(identity)
|
||||||
ctx.args.targetFolder match {
|
ctx.args.targetFolder match {
|
||||||
case Some(tf) =>
|
case Some(tf) if postHandleAll || mh.process =>
|
||||||
logOp(_.debug(s"Post handling mail: ${mh.subject} - moving to folder: $tf"))
|
logOp(
|
||||||
|
_.debug(s"Post handling mail: ${mh.mh.subject} - moving to folder: $tf")
|
||||||
|
)
|
||||||
.flatMap(_ =>
|
.flatMap(_ =>
|
||||||
a.getOrCreateFolder(None, tf).flatMap(folder => a.moveMail(mh, folder))
|
a.getOrCreateFolder(None, tf).flatMap(folder => a.moveMail(mh.mh, folder))
|
||||||
)
|
)
|
||||||
case None if ctx.args.deleteMail =>
|
case None if ctx.args.deleteMail && (postHandleAll || mh.process) =>
|
||||||
logOp(_.debug(s"Post handling mail: ${mh.subject} - deleting mail.")).flatMap(
|
logOp(_.debug(s"Post handling mail: ${mh.mh.subject} - deleting mail."))
|
||||||
_ =>
|
.flatMap(_ =>
|
||||||
a.deleteMail(mh).flatMapF { r =>
|
a.deleteMail(mh.mh).flatMapF { r =>
|
||||||
if (r.count == 0)
|
if (r.count == 0)
|
||||||
ctx.logger.warn(s"Mail could not be deleted!")
|
ctx.logger.warn(s"Mail could not be deleted!")
|
||||||
else ().pure[F]
|
else ().pure[F]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
case None =>
|
case _ =>
|
||||||
logOp(_.debug(s"Post handling mail: ${mh.subject} - no handling defined!"))
|
logOp(_.debug(s"Post handling mail: ${mh.mh.subject} - no handling defined!"))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def submitMail(upload: OUpload[F], args: Args)(
|
def submitMail(upload: OUpload[F], args: Args)(
|
||||||
mail: Mail[F]
|
mail: Mail[F]
|
||||||
@ -321,7 +327,7 @@ object ScanMailboxTask {
|
|||||||
Kleisli.liftF(
|
Kleisli.liftF(
|
||||||
ctx.logger.warn(s"Error submitting '${mh.mh.subject}': ${ex.getMessage}")
|
ctx.logger.warn(s"Error submitting '${mh.mh.subject}': ${ex.getMessage}")
|
||||||
),
|
),
|
||||||
_ => postHandle(a)(mh.mh)
|
_ => postHandle(a)(mh)
|
||||||
)
|
)
|
||||||
} yield ()
|
} yield ()
|
||||||
}
|
}
|
||||||
|
@ -3904,6 +3904,8 @@ components:
|
|||||||
processing mails.
|
processing mails.
|
||||||
type: string
|
type: string
|
||||||
format: language
|
format: language
|
||||||
|
postHandleAll:
|
||||||
|
type: boolean
|
||||||
|
|
||||||
ImapSettingsList:
|
ImapSettingsList:
|
||||||
description: |
|
description: |
|
||||||
|
@ -117,7 +117,8 @@ object ScanMailboxRoutes {
|
|||||||
settings.fileFilter,
|
settings.fileFilter,
|
||||||
settings.tags.map(_.items),
|
settings.tags.map(_.items),
|
||||||
settings.subjectFilter,
|
settings.subjectFilter,
|
||||||
settings.language
|
settings.language,
|
||||||
|
settings.postHandleAll
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -149,6 +150,7 @@ object ScanMailboxRoutes {
|
|||||||
task.args.tags.map(StringList.apply),
|
task.args.tags.map(StringList.apply),
|
||||||
task.args.fileFilter,
|
task.args.fileFilter,
|
||||||
task.args.subjectFilter,
|
task.args.subjectFilter,
|
||||||
task.args.language
|
task.args.language,
|
||||||
|
task.args.postHandleAll
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,8 @@ type alias Model =
|
|||||||
, subjectFilter : Maybe String
|
, subjectFilter : Maybe String
|
||||||
, languageModel : Comp.FixedDropdown.Model Language
|
, languageModel : Comp.FixedDropdown.Model Language
|
||||||
, language : Maybe Language
|
, language : Maybe Language
|
||||||
|
, postHandleAll : Bool
|
||||||
|
, menuTab : MenuTab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -79,6 +81,15 @@ type Action
|
|||||||
| NoAction
|
| NoAction
|
||||||
|
|
||||||
|
|
||||||
|
type MenuTab
|
||||||
|
= TabGeneral
|
||||||
|
| TabProcessing
|
||||||
|
| TabAdditionalFilter
|
||||||
|
| TabPostProcessing
|
||||||
|
| TabMetadata
|
||||||
|
| TabSchedule
|
||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= Submit
|
= Submit
|
||||||
| Cancel
|
| Cancel
|
||||||
@ -102,6 +113,8 @@ type Msg
|
|||||||
| SetSubjectFilter String
|
| SetSubjectFilter String
|
||||||
| LanguageMsg (Comp.FixedDropdown.Msg Language)
|
| LanguageMsg (Comp.FixedDropdown.Msg Language)
|
||||||
| RemoveLanguage
|
| RemoveLanguage
|
||||||
|
| TogglePostHandleAll
|
||||||
|
| SetMenuTab MenuTab
|
||||||
|
|
||||||
|
|
||||||
initWith : Flags -> ScanMailboxSettings -> ( Model, Cmd Msg )
|
initWith : Flags -> ScanMailboxSettings -> ( Model, Cmd Msg )
|
||||||
@ -147,6 +160,8 @@ initWith flags s =
|
|||||||
, languageModel =
|
, languageModel =
|
||||||
Comp.FixedDropdown.init (List.map mkLanguageItem Data.Language.all)
|
Comp.FixedDropdown.init (List.map mkLanguageItem Data.Language.all)
|
||||||
, language = Maybe.andThen Data.Language.fromString s.language
|
, language = Maybe.andThen Data.Language.fromString s.language
|
||||||
|
, postHandleAll = Maybe.withDefault False s.postHandleAll
|
||||||
|
, menuTab = TabGeneral
|
||||||
}
|
}
|
||||||
, Cmd.batch
|
, Cmd.batch
|
||||||
[ Api.getImapSettings flags "" ConnResp
|
[ Api.getImapSettings flags "" ConnResp
|
||||||
@ -200,6 +215,8 @@ init flags =
|
|||||||
, languageModel =
|
, languageModel =
|
||||||
Comp.FixedDropdown.init (List.map mkLanguageItem Data.Language.all)
|
Comp.FixedDropdown.init (List.map mkLanguageItem Data.Language.all)
|
||||||
, language = Nothing
|
, language = Nothing
|
||||||
|
, postHandleAll = False
|
||||||
|
, menuTab = TabGeneral
|
||||||
}
|
}
|
||||||
, Cmd.batch
|
, Cmd.batch
|
||||||
[ Api.getImapSettings flags "" ConnResp
|
[ Api.getImapSettings flags "" ConnResp
|
||||||
@ -232,7 +249,7 @@ makeSettings model =
|
|||||||
|
|
||||||
infolders =
|
infolders =
|
||||||
if model.folders == [] then
|
if model.folders == [] then
|
||||||
Invalid [ "No folders given" ] []
|
Invalid [ "No processing folders given" ] []
|
||||||
|
|
||||||
else
|
else
|
||||||
Valid model.folders
|
Valid model.folders
|
||||||
@ -260,6 +277,7 @@ makeSettings model =
|
|||||||
|> StringList
|
|> StringList
|
||||||
|> Just
|
|> Just
|
||||||
, language = Maybe.map Data.Language.toIso3 model.language
|
, language = Maybe.map Data.Language.toIso3 model.language
|
||||||
|
, postHandleAll = Just model.postHandleAll
|
||||||
}
|
}
|
||||||
in
|
in
|
||||||
Data.Validated.map3 make
|
Data.Validated.map3 make
|
||||||
@ -646,6 +664,18 @@ update flags msg model =
|
|||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TogglePostHandleAll ->
|
||||||
|
( { model | postHandleAll = not model.postHandleAll }
|
||||||
|
, NoAction
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
SetMenuTab tab ->
|
||||||
|
( { model | menuTab = tab }
|
||||||
|
, NoAction
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- View
|
--- View
|
||||||
@ -674,267 +704,27 @@ view extraClasses settings model =
|
|||||||
, ( "success", isFormSuccess model )
|
, ( "success", isFormSuccess model )
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
[ Html.map YesNoDeleteMsg (Comp.YesNoDimmer.view model.yesNoDelete)
|
[ viewMenu model
|
||||||
, div
|
, div [ class "ui bottom attached segment" ]
|
||||||
[ classList
|
(case model.menuTab of
|
||||||
[ ( "ui dimmer", True )
|
TabGeneral ->
|
||||||
, ( "active", model.loading > 0 )
|
viewGeneral settings model
|
||||||
]
|
|
||||||
]
|
TabProcessing ->
|
||||||
[ div [ class "ui text loader" ]
|
viewProcessing model
|
||||||
[ text "Loading..."
|
|
||||||
]
|
TabAdditionalFilter ->
|
||||||
]
|
viewAdditionalFilter model
|
||||||
, div [ class "inline field" ]
|
|
||||||
[ div [ class "ui checkbox" ]
|
TabPostProcessing ->
|
||||||
[ input
|
viewPostProcessing model
|
||||||
[ type_ "checkbox"
|
|
||||||
, onCheck (\_ -> ToggleEnabled)
|
TabMetadata ->
|
||||||
, checked model.enabled
|
viewMetadata settings model
|
||||||
]
|
|
||||||
[]
|
TabSchedule ->
|
||||||
, label [] [ text "Enabled" ]
|
viewSchedule model
|
||||||
]
|
|
||||||
, span [ class "small-info" ]
|
|
||||||
[ text "Enable or disable this task."
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "required field" ]
|
|
||||||
[ label [] [ text "Mailbox" ]
|
|
||||||
, Html.map ConnMsg (Comp.Dropdown.view settings model.connectionModel)
|
|
||||||
, span [ class "small-info" ]
|
|
||||||
[ text "The IMAP connection to use when sending notification mails."
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "required field" ]
|
|
||||||
[ label [] [ text "Folders" ]
|
|
||||||
, Html.map FoldersMsg (Comp.StringListInput.view model.folders model.foldersModel)
|
|
||||||
, span [ class "small-info" ]
|
|
||||||
[ text "The folders to go through"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "field" ]
|
|
||||||
[ label [] [ text "Target folder" ]
|
|
||||||
, input
|
|
||||||
[ type_ "text"
|
|
||||||
, onInput SetTargetFolder
|
|
||||||
, Maybe.withDefault "" model.targetFolder |> value
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
, span [ class "small-info" ]
|
|
||||||
[ text "Move all mails successfully submitted into this folder."
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "inline field" ]
|
|
||||||
[ div [ class "ui checkbox" ]
|
|
||||||
[ input
|
|
||||||
[ type_ "checkbox"
|
|
||||||
, onCheck (\_ -> ToggleDeleteMail)
|
|
||||||
, checked model.deleteMail
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
, label [] [ text "Delete imported mails" ]
|
|
||||||
]
|
|
||||||
, span [ class "small-info" ]
|
|
||||||
[ text "Whether to delete all mails successfully imported into docspell. This only applies if "
|
|
||||||
, em [] [ text "target folder" ]
|
|
||||||
, text " is not set."
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "ui dividing header" ]
|
|
||||||
[ text "Filter"
|
|
||||||
]
|
|
||||||
, Html.map ReceivedHoursMsg
|
|
||||||
(Comp.IntField.viewWithInfo
|
|
||||||
"Select mails newer than `now - receivedHours`"
|
|
||||||
model.receivedHours
|
|
||||||
"field"
|
|
||||||
model.receivedHoursModel
|
|
||||||
)
|
)
|
||||||
, div
|
|
||||||
[ class "field"
|
|
||||||
]
|
|
||||||
[ label [] [ text "File Filter" ]
|
|
||||||
, input
|
|
||||||
[ type_ "text"
|
|
||||||
, onInput SetFileFilter
|
|
||||||
, placeholder "File Filter"
|
|
||||||
, model.fileFilter
|
|
||||||
|> Maybe.withDefault ""
|
|
||||||
|> value
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
, div [ class "small-info" ]
|
|
||||||
[ text "Specify a file glob to filter attachments. For example, to only extract pdf files: "
|
|
||||||
, code []
|
|
||||||
[ text "*.pdf"
|
|
||||||
]
|
|
||||||
, text ". If you want to include the mail body, allow html files or "
|
|
||||||
, code []
|
|
||||||
[ text "mail.html"
|
|
||||||
]
|
|
||||||
, text ". Globs can be combined via OR, like this: "
|
|
||||||
, code []
|
|
||||||
[ text "*.pdf|mail.html"
|
|
||||||
]
|
|
||||||
, text ". No file filter defaults to "
|
|
||||||
, code []
|
|
||||||
[ text "*"
|
|
||||||
]
|
|
||||||
, text " that includes all"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div
|
|
||||||
[ class "field"
|
|
||||||
]
|
|
||||||
[ label [] [ text "Subject Filter" ]
|
|
||||||
, input
|
|
||||||
[ type_ "text"
|
|
||||||
, onInput SetSubjectFilter
|
|
||||||
, placeholder "Subject Filter"
|
|
||||||
, model.subjectFilter
|
|
||||||
|> Maybe.withDefault ""
|
|
||||||
|> value
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
, div [ class "small-info" ]
|
|
||||||
[ text "Specify a file glob to filter mails by subject. For example: "
|
|
||||||
, code []
|
|
||||||
[ text "*Scanned Document*"
|
|
||||||
]
|
|
||||||
, text ". No file filter defaults to "
|
|
||||||
, code []
|
|
||||||
[ text "*"
|
|
||||||
]
|
|
||||||
, text " that includes all"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "ui dividing header" ]
|
|
||||||
[ text "Metadata"
|
|
||||||
]
|
|
||||||
, div [ class "required field" ]
|
|
||||||
[ label [] [ text "Item direction" ]
|
|
||||||
, div [ class "grouped fields" ]
|
|
||||||
[ div [ class "field" ]
|
|
||||||
[ div [ class "ui radio checkbox" ]
|
|
||||||
[ input
|
|
||||||
[ type_ "radio"
|
|
||||||
, checked (model.direction == Nothing)
|
|
||||||
, onCheck (\_ -> DirectionMsg Nothing)
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
, label [] [ text "Automatic" ]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "field" ]
|
|
||||||
[ div [ class "ui radio checkbox" ]
|
|
||||||
[ input
|
|
||||||
[ type_ "radio"
|
|
||||||
, checked (model.direction == Just Incoming)
|
|
||||||
, onCheck (\_ -> DirectionMsg (Just Incoming))
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
, label [] [ text "Incoming" ]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "field" ]
|
|
||||||
[ div [ class "ui radio checkbox" ]
|
|
||||||
[ input
|
|
||||||
[ type_ "radio"
|
|
||||||
, checked (model.direction == Just Outgoing)
|
|
||||||
, onCheck (\_ -> DirectionMsg (Just Outgoing))
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
, label [] [ text "Outgoing" ]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, span [ class "small-info" ]
|
|
||||||
[ text "Sets the direction for an item. If you know all mails are incoming or "
|
|
||||||
, text "outgoing, you can set it here. Otherwise it will be guessed from looking "
|
|
||||||
, text "at sender and receiver."
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "field" ]
|
|
||||||
[ label []
|
|
||||||
[ text "Item Folder"
|
|
||||||
]
|
|
||||||
, Html.map FolderDropdownMsg (Comp.Dropdown.view settings model.folderModel)
|
|
||||||
, span [ class "small-info" ]
|
|
||||||
[ text "Put all items from this mailbox into the selected folder"
|
|
||||||
]
|
|
||||||
, div
|
|
||||||
[ classList
|
|
||||||
[ ( "ui warning message", True )
|
|
||||||
, ( "hidden", isFolderMember model )
|
|
||||||
]
|
|
||||||
]
|
|
||||||
[ Markdown.toHtml [] """
|
|
||||||
You are **not a member** of this folder. Items created from mails in
|
|
||||||
this mailbox will be **hidden** from any search results. Use a folder
|
|
||||||
where you are a member of to make items visible. This message will
|
|
||||||
disappear then.
|
|
||||||
"""
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "field" ]
|
|
||||||
[ label [] [ text "Tags" ]
|
|
||||||
, Html.map TagDropdownMsg (Comp.Dropdown.view settings model.tagModel)
|
|
||||||
, div [ class "small-info" ]
|
|
||||||
[ text "Choose tags that should be applied to items."
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "field" ]
|
|
||||||
[ label []
|
|
||||||
[ text "Language"
|
|
||||||
]
|
|
||||||
, div [ class "ui action input" ]
|
|
||||||
[ Html.map LanguageMsg
|
|
||||||
(Comp.FixedDropdown.viewStyled "fluid"
|
|
||||||
(Maybe.map mkLanguageItem model.language)
|
|
||||||
model.languageModel
|
|
||||||
)
|
|
||||||
, a
|
|
||||||
[ class "ui icon button"
|
|
||||||
, href "#"
|
|
||||||
, onClick RemoveLanguage
|
|
||||||
]
|
|
||||||
[ i [ class "delete icon" ] []
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "small-info" ]
|
|
||||||
[ text "Used for text extraction and text analysis. The "
|
|
||||||
, text "collective's default language is used, if not specified here."
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "ui dividing header" ]
|
|
||||||
[ text "Schedule"
|
|
||||||
]
|
|
||||||
, div [ class "required field" ]
|
|
||||||
[ label []
|
|
||||||
[ text "Schedule"
|
|
||||||
, a
|
|
||||||
[ class "right-float"
|
|
||||||
, href "https://github.com/eikek/calev#what-are-calendar-events"
|
|
||||||
, target "_blank"
|
|
||||||
]
|
|
||||||
[ i [ class "help icon" ] []
|
|
||||||
, text "Click here for help"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, Html.map CalEventMsg
|
|
||||||
(Comp.CalEventInput.view ""
|
|
||||||
(Data.Validated.value model.schedule)
|
|
||||||
model.scheduleModel
|
|
||||||
)
|
|
||||||
, span [ class "small-info" ]
|
|
||||||
[ text "Specify how often and when this task should run. "
|
|
||||||
, text "Use English 3-letter weekdays. Either a single value, "
|
|
||||||
, text "a list (ex. 1,2,3), a range (ex. 1..3) or a '*' (meaning all) "
|
|
||||||
, text "is allowed for each part."
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "ui divider" ] []
|
|
||||||
, div
|
, div
|
||||||
[ classList
|
[ classList
|
||||||
[ ( "ui message", True )
|
[ ( "ui message", True )
|
||||||
@ -974,9 +764,348 @@ disappear then.
|
|||||||
]
|
]
|
||||||
[ text "Start Once"
|
[ text "Start Once"
|
||||||
]
|
]
|
||||||
|
, Html.map YesNoDeleteMsg (Comp.YesNoDimmer.view model.yesNoDelete)
|
||||||
|
, div
|
||||||
|
[ classList
|
||||||
|
[ ( "ui dimmer", True )
|
||||||
|
, ( "active", model.loading > 0 )
|
||||||
|
, ( "invisible hidden", model.loading == 0 )
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[ div [ class "ui text loader" ]
|
||||||
|
[ text "Loading..."
|
||||||
|
]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewMenu : Model -> Html Msg
|
||||||
|
viewMenu model =
|
||||||
|
let
|
||||||
|
tabLink tab txt =
|
||||||
|
a
|
||||||
|
[ class "item"
|
||||||
|
, classList
|
||||||
|
[ ( "active", model.menuTab == tab ) ]
|
||||||
|
, href "#"
|
||||||
|
, onClick (SetMenuTab tab)
|
||||||
|
]
|
||||||
|
[ text txt
|
||||||
|
]
|
||||||
|
in
|
||||||
|
div [ class "ui top attached stacked tabular menu" ]
|
||||||
|
[ tabLink TabGeneral "General"
|
||||||
|
, tabLink TabProcessing "Processing"
|
||||||
|
, tabLink TabAdditionalFilter "Additional Filter"
|
||||||
|
, tabLink TabPostProcessing "Post Processing"
|
||||||
|
, tabLink TabMetadata "Metadata"
|
||||||
|
, tabLink TabSchedule "Schedule"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewGeneral : UiSettings -> Model -> List (Html Msg)
|
||||||
|
viewGeneral settings model =
|
||||||
|
[ div [ class "inline field" ]
|
||||||
|
[ div [ class "ui checkbox" ]
|
||||||
|
[ input
|
||||||
|
[ type_ "checkbox"
|
||||||
|
, onCheck (\_ -> ToggleEnabled)
|
||||||
|
, checked model.enabled
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, label [] [ text "Enabled" ]
|
||||||
|
]
|
||||||
|
, span [ class "small-info" ]
|
||||||
|
[ text "Enable or disable this task."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "required field" ]
|
||||||
|
[ label [] [ text "Mailbox" ]
|
||||||
|
, Html.map ConnMsg (Comp.Dropdown.view settings model.connectionModel)
|
||||||
|
, span [ class "small-info" ]
|
||||||
|
[ text "The IMAP connection to use when sending notification mails."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewProcessing : Model -> List (Html Msg)
|
||||||
|
viewProcessing model =
|
||||||
|
[ div [ class "ui message" ]
|
||||||
|
[ text "These settings define which mails are fetched from the mail server."
|
||||||
|
]
|
||||||
|
, div [ class "required field" ]
|
||||||
|
[ label [] [ text "Folders" ]
|
||||||
|
, Html.map FoldersMsg (Comp.StringListInput.view model.folders model.foldersModel)
|
||||||
|
, span [ class "small-info" ]
|
||||||
|
[ text "The folders to go through"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, Html.map ReceivedHoursMsg
|
||||||
|
(Comp.IntField.viewWithInfo
|
||||||
|
"Select mails newer than `now - receivedHours`"
|
||||||
|
model.receivedHours
|
||||||
|
"field"
|
||||||
|
model.receivedHoursModel
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewAdditionalFilter : Model -> List (Html Msg)
|
||||||
|
viewAdditionalFilter model =
|
||||||
|
[ div [ class "ui message" ]
|
||||||
|
[ text "These filters are applied to mails that have been fetched from the "
|
||||||
|
, text "mailbox to select those that should be imported."
|
||||||
|
]
|
||||||
|
, div
|
||||||
|
[ class "field"
|
||||||
|
]
|
||||||
|
[ label [] [ text "File Filter" ]
|
||||||
|
, input
|
||||||
|
[ type_ "text"
|
||||||
|
, onInput SetFileFilter
|
||||||
|
, placeholder "File Filter"
|
||||||
|
, model.fileFilter
|
||||||
|
|> Maybe.withDefault ""
|
||||||
|
|> value
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, div [ class "small-info" ]
|
||||||
|
[ text "Specify a file glob to filter attachments. For example, to only extract pdf files: "
|
||||||
|
, code []
|
||||||
|
[ text "*.pdf"
|
||||||
|
]
|
||||||
|
, text ". If you want to include the mail body, allow html files or "
|
||||||
|
, code []
|
||||||
|
[ text "mail.html"
|
||||||
|
]
|
||||||
|
, text ". Globs can be combined via OR, like this: "
|
||||||
|
, code []
|
||||||
|
[ text "*.pdf|mail.html"
|
||||||
|
]
|
||||||
|
, text ". No file filter defaults to "
|
||||||
|
, code []
|
||||||
|
[ text "*"
|
||||||
|
]
|
||||||
|
, text " that includes all"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div
|
||||||
|
[ class "field"
|
||||||
|
]
|
||||||
|
[ label [] [ text "Subject Filter" ]
|
||||||
|
, input
|
||||||
|
[ type_ "text"
|
||||||
|
, onInput SetSubjectFilter
|
||||||
|
, placeholder "Subject Filter"
|
||||||
|
, model.subjectFilter
|
||||||
|
|> Maybe.withDefault ""
|
||||||
|
|> value
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, div [ class "small-info" ]
|
||||||
|
[ text "Specify a file glob to filter mails by subject. For example: "
|
||||||
|
, code []
|
||||||
|
[ text "*Scanned Document*"
|
||||||
|
]
|
||||||
|
, text ". No file filter defaults to "
|
||||||
|
, code []
|
||||||
|
[ text "*"
|
||||||
|
]
|
||||||
|
, text " that includes all"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewPostProcessing : Model -> List (Html Msg)
|
||||||
|
viewPostProcessing model =
|
||||||
|
[ div [ class "ui message" ]
|
||||||
|
[ text "This defines what happens to mails that have been downloaded."
|
||||||
|
]
|
||||||
|
, div [ class "inline field" ]
|
||||||
|
[ div [ class "ui checkbox" ]
|
||||||
|
[ input
|
||||||
|
[ type_ "checkbox"
|
||||||
|
, onCheck (\_ -> TogglePostHandleAll)
|
||||||
|
, checked model.postHandleAll
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, label [] [ text "Apply post-processing to all fetched mails." ]
|
||||||
|
]
|
||||||
|
, span [ class "small-info" ]
|
||||||
|
[ text "When mails are not imported due to the 'Additional Filters', this flag can "
|
||||||
|
, text "control whether they should be moved to a target folder or deleted (whatever is "
|
||||||
|
, text "defined here) nevertheless. If unchecked only imported mails "
|
||||||
|
, text "are post-processed, others stay where they are."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "field" ]
|
||||||
|
[ label [] [ text "Target folder" ]
|
||||||
|
, input
|
||||||
|
[ type_ "text"
|
||||||
|
, onInput SetTargetFolder
|
||||||
|
, Maybe.withDefault "" model.targetFolder |> value
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, span [ class "small-info" ]
|
||||||
|
[ text "Move mails into this folder."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "inline field" ]
|
||||||
|
[ div [ class "ui checkbox" ]
|
||||||
|
[ input
|
||||||
|
[ type_ "checkbox"
|
||||||
|
, onCheck (\_ -> ToggleDeleteMail)
|
||||||
|
, checked model.deleteMail
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, label [] [ text "Delete imported mails" ]
|
||||||
|
]
|
||||||
|
, span [ class "small-info" ]
|
||||||
|
[ text "Whether to delete all mails fetched by docspell. This only applies if "
|
||||||
|
, em [] [ text "target folder" ]
|
||||||
|
, text " is not set."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewMetadata : UiSettings -> Model -> List (Html Msg)
|
||||||
|
viewMetadata settings model =
|
||||||
|
[ div [ class "ui message" ]
|
||||||
|
[ text "Define metadata that should be attached to all items created by this task."
|
||||||
|
]
|
||||||
|
, div [ class "required field" ]
|
||||||
|
[ label [] [ text "Item direction" ]
|
||||||
|
, div [ class "grouped fields" ]
|
||||||
|
[ div [ class "field" ]
|
||||||
|
[ div [ class "ui radio checkbox" ]
|
||||||
|
[ input
|
||||||
|
[ type_ "radio"
|
||||||
|
, checked (model.direction == Nothing)
|
||||||
|
, onCheck (\_ -> DirectionMsg Nothing)
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, label [] [ text "Automatic" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "field" ]
|
||||||
|
[ div [ class "ui radio checkbox" ]
|
||||||
|
[ input
|
||||||
|
[ type_ "radio"
|
||||||
|
, checked (model.direction == Just Incoming)
|
||||||
|
, onCheck (\_ -> DirectionMsg (Just Incoming))
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, label [] [ text "Incoming" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "field" ]
|
||||||
|
[ div [ class "ui radio checkbox" ]
|
||||||
|
[ input
|
||||||
|
[ type_ "radio"
|
||||||
|
, checked (model.direction == Just Outgoing)
|
||||||
|
, onCheck (\_ -> DirectionMsg (Just Outgoing))
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, label [] [ text "Outgoing" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, span [ class "small-info" ]
|
||||||
|
[ text "Sets the direction for an item. If you know all mails are incoming or "
|
||||||
|
, text "outgoing, you can set it here. Otherwise it will be guessed from looking "
|
||||||
|
, text "at sender and receiver."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "field" ]
|
||||||
|
[ label []
|
||||||
|
[ text "Item Folder"
|
||||||
|
]
|
||||||
|
, Html.map FolderDropdownMsg (Comp.Dropdown.view settings model.folderModel)
|
||||||
|
, span [ class "small-info" ]
|
||||||
|
[ text "Put all items from this mailbox into the selected folder"
|
||||||
|
]
|
||||||
|
, div
|
||||||
|
[ classList
|
||||||
|
[ ( "ui warning message", True )
|
||||||
|
, ( "hidden", isFolderMember model )
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[ Markdown.toHtml [] """
|
||||||
|
You are **not a member** of this folder. Items created from mails in
|
||||||
|
this mailbox will be **hidden** from any search results. Use a folder
|
||||||
|
where you are a member of to make items visible. This message will
|
||||||
|
disappear then.
|
||||||
|
"""
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "field" ]
|
||||||
|
[ label [] [ text "Tags" ]
|
||||||
|
, Html.map TagDropdownMsg (Comp.Dropdown.view settings model.tagModel)
|
||||||
|
, div [ class "small-info" ]
|
||||||
|
[ text "Choose tags that should be applied to items."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "field" ]
|
||||||
|
[ label []
|
||||||
|
[ text "Language"
|
||||||
|
]
|
||||||
|
, div [ class "ui action input" ]
|
||||||
|
[ Html.map LanguageMsg
|
||||||
|
(Comp.FixedDropdown.viewStyled "fluid"
|
||||||
|
(Maybe.map mkLanguageItem model.language)
|
||||||
|
model.languageModel
|
||||||
|
)
|
||||||
|
, a
|
||||||
|
[ class "ui icon button"
|
||||||
|
, href "#"
|
||||||
|
, onClick RemoveLanguage
|
||||||
|
]
|
||||||
|
[ i [ class "delete icon" ] []
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "small-info" ]
|
||||||
|
[ text "Used for text extraction and text analysis. The "
|
||||||
|
, text "collective's default language is used, if not specified here."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewSchedule : Model -> List (Html Msg)
|
||||||
|
viewSchedule model =
|
||||||
|
[ div [ class "ui message" ]
|
||||||
|
[ text "Define when mails should be imported."
|
||||||
|
]
|
||||||
|
, div [ class "required field" ]
|
||||||
|
[ label []
|
||||||
|
[ text "Schedule"
|
||||||
|
, a
|
||||||
|
[ class "right-float"
|
||||||
|
, href "https://github.com/eikek/calev#what-are-calendar-events"
|
||||||
|
, target "_blank"
|
||||||
|
]
|
||||||
|
[ i [ class "help icon" ] []
|
||||||
|
, text "Click here for help"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, Html.map CalEventMsg
|
||||||
|
(Comp.CalEventInput.view ""
|
||||||
|
(Data.Validated.value model.schedule)
|
||||||
|
model.scheduleModel
|
||||||
|
)
|
||||||
|
, span [ class "small-info" ]
|
||||||
|
[ text "Specify how often and when this task should run. "
|
||||||
|
, text "Use English 3-letter weekdays. Either a single value, "
|
||||||
|
, text "a list (ex. 1,2,3), a range (ex. 1..3) or a '*' (meaning all) "
|
||||||
|
, text "is allowed for each part."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
isFolderMember : Model -> Bool
|
isFolderMember : Model -> Bool
|
||||||
isFolderMember model =
|
isFolderMember model =
|
||||||
let
|
let
|
||||||
|
@ -247,7 +247,7 @@ view settings model =
|
|||||||
|
|
||||||
viewForm : UiSettings -> Comp.ScanMailboxForm.Model -> Html Msg
|
viewForm : UiSettings -> Comp.ScanMailboxForm.Model -> Html Msg
|
||||||
viewForm settings model =
|
viewForm settings model =
|
||||||
Html.map DetailMsg (Comp.ScanMailboxForm.view "segment" settings model)
|
Html.map DetailMsg (Comp.ScanMailboxForm.view "" settings model)
|
||||||
|
|
||||||
|
|
||||||
viewList : Model -> Html Msg
|
viewList : Model -> Html Msg
|
||||||
|
Loading…
x
Reference in New Issue
Block a user