mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-04-04 10:29:34 +00:00
Allow bookmarks in periodic query notification
This commit is contained in:
parent
ccb4df5bd7
commit
699cf091e6
@ -20,6 +20,8 @@ trait OQueryBookmarks[F[_]] {
|
||||
|
||||
def getAll(account: AccountId): F[Vector[OQueryBookmarks.Bookmark]]
|
||||
|
||||
def findOne(account: AccountId, nameOrId: String): F[Option[OQueryBookmarks.Bookmark]]
|
||||
|
||||
def create(account: AccountId, bookmark: OQueryBookmarks.NewBookmark): F[AddResult]
|
||||
|
||||
def update(
|
||||
@ -53,9 +55,15 @@ object OQueryBookmarks {
|
||||
def getAll(account: AccountId): F[Vector[Bookmark]] =
|
||||
store
|
||||
.transact(RQueryBookmark.allForUser(account))
|
||||
.map(
|
||||
_.map(r => Bookmark(r.id, r.name, r.label, r.query, r.isPersonal, r.created))
|
||||
)
|
||||
.map(_.map(convert.toModel))
|
||||
|
||||
def findOne(
|
||||
account: AccountId,
|
||||
nameOrId: String
|
||||
): F[Option[OQueryBookmarks.Bookmark]] =
|
||||
store
|
||||
.transact(RQueryBookmark.findByNameOrId(account, nameOrId))
|
||||
.map(_.map(convert.toModel))
|
||||
|
||||
def create(account: AccountId, b: NewBookmark): F[AddResult] = {
|
||||
val record =
|
||||
@ -65,23 +73,28 @@ object OQueryBookmarks {
|
||||
|
||||
def update(account: AccountId, id: Ident, b: NewBookmark): F[UpdateResult] =
|
||||
UpdateResult.fromUpdate(
|
||||
store.transact(
|
||||
RQueryBookmark.update(
|
||||
RQueryBookmark(
|
||||
id,
|
||||
b.name,
|
||||
b.label,
|
||||
None, // userId and some other values are not used
|
||||
account.collective,
|
||||
b.query,
|
||||
Timestamp.Epoch
|
||||
)
|
||||
)
|
||||
)
|
||||
store.transact(RQueryBookmark.update(convert.toRecord(account, id, b)))
|
||||
)
|
||||
|
||||
def delete(account: AccountId, bookmark: Ident): F[Unit] =
|
||||
store.transact(RQueryBookmark.deleteById(account.collective, bookmark)).as(())
|
||||
|
||||
})
|
||||
|
||||
private object convert {
|
||||
|
||||
def toModel(r: RQueryBookmark): Bookmark =
|
||||
Bookmark(r.id, r.name, r.label, r.query, r.isPersonal, r.created)
|
||||
|
||||
def toRecord(account: AccountId, id: Ident, b: NewBookmark): RQueryBookmark =
|
||||
RQueryBookmark(
|
||||
id,
|
||||
b.name,
|
||||
b.label,
|
||||
None, // userId and some other values are not used
|
||||
account.collective,
|
||||
b.query,
|
||||
Timestamp.Epoch
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
package docspell.joex.notify
|
||||
|
||||
import cats.data.OptionT
|
||||
import cats.data.{NonEmptyList => Nel}
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
@ -17,10 +18,14 @@ import docspell.joex.scheduler.Task
|
||||
import docspell.notification.api.EventContext
|
||||
import docspell.notification.api.NotificationChannel
|
||||
import docspell.notification.api.PeriodicQueryArgs
|
||||
import docspell.query.ItemQuery
|
||||
import docspell.query.ItemQuery.Expr.AndExpr
|
||||
import docspell.query.ItemQueryParser
|
||||
import docspell.store.qb.Batch
|
||||
import docspell.store.queries.ListItem
|
||||
import docspell.store.queries.{QItem, Query}
|
||||
import docspell.store.records.RQueryBookmark
|
||||
import docspell.store.records.RShare
|
||||
import docspell.store.records.RUser
|
||||
|
||||
object PeriodicQueryTask {
|
||||
@ -54,22 +59,77 @@ object PeriodicQueryTask {
|
||||
)
|
||||
.getOrElse(())
|
||||
|
||||
private def queryString(q: ItemQuery.Expr) =
|
||||
ItemQueryParser.asString(q)
|
||||
|
||||
def makeQuery[F[_]: Sync](ctx: Context[F, Args])(cont: Query => F[Unit]): F[Unit] = {
|
||||
def fromBookmark(id: String) =
|
||||
ctx.store
|
||||
.transact(RQueryBookmark.findByNameOrId(ctx.args.account, id))
|
||||
.map(_.map(_.query))
|
||||
.flatTap(q =>
|
||||
ctx.logger.debug(s"Loaded bookmark '$id': ${q.map(_.expr).map(queryString)}")
|
||||
)
|
||||
|
||||
def fromShare(id: String) =
|
||||
ctx.store
|
||||
.transact(RShare.findOneByCollective(ctx.args.account.collective, Some(true), id))
|
||||
.map(_.map(_.query))
|
||||
.flatTap(q =>
|
||||
ctx.logger.debug(s"Loaded share '$id': ${q.map(_.expr).map(queryString)}")
|
||||
)
|
||||
|
||||
def fromBookmarkOrShare(id: String) =
|
||||
OptionT(fromBookmark(id)).orElse(OptionT(fromShare(id))).value
|
||||
|
||||
def withQuery(bm: Option[ItemQuery], str: String): F[Unit] =
|
||||
ItemQueryParser.parse(str) match {
|
||||
case Right(q) =>
|
||||
val expr = bm.map(b => AndExpr(Nel.of(b.expr, q.expr))).getOrElse(q.expr)
|
||||
val query = Query(Query.Fix(ctx.args.account, Some(expr), None))
|
||||
ctx.logger.debug(s"Running query: ${queryString(expr)}") *> cont(query)
|
||||
|
||||
case Left(err) =>
|
||||
ctx.logger.error(
|
||||
s"Item query is invalid, stopping: ${ctx.args.query.map(_.query)} - ${err.render}"
|
||||
)
|
||||
}
|
||||
|
||||
(ctx.args.bookmark, ctx.args.query) match {
|
||||
case (Some(bm), Some(qstr)) =>
|
||||
ctx.logger.debug(s"Using bookmark $bm and query $qstr") *>
|
||||
fromBookmarkOrShare(bm).flatMap(bq => withQuery(bq, qstr.query))
|
||||
|
||||
case (Some(bm), None) =>
|
||||
fromBookmarkOrShare(bm).flatMap {
|
||||
case Some(bq) =>
|
||||
val query = Query(Query.Fix(ctx.args.account, Some(bq.expr), None))
|
||||
ctx.logger.debug(s"Using bookmark: ${queryString(bq.expr)}") *> cont(query)
|
||||
|
||||
case None =>
|
||||
ctx.logger.error(
|
||||
s"No bookmark found for id: $bm. Can't continue. Please fix the task query."
|
||||
)
|
||||
}
|
||||
|
||||
case (None, Some(qstr)) =>
|
||||
ctx.logger.debug(s"Using query: ${qstr.query}") *> withQuery(None, qstr.query)
|
||||
|
||||
case (None, None) =>
|
||||
ctx.logger.error(s"No query provided for task $taskName!")
|
||||
}
|
||||
}
|
||||
|
||||
def withItems[F[_]: Sync](ctx: Context[F, Args], limit: Int, now: Timestamp)(
|
||||
cont: Vector[ListItem] => F[Unit]
|
||||
): F[Unit] =
|
||||
ItemQueryParser.parse(ctx.args.query.query) match {
|
||||
case Right(q) =>
|
||||
val query = Query(Query.Fix(ctx.args.account, Some(q.expr), None))
|
||||
val items = ctx.store
|
||||
.transact(QItem.findItems(query, now.toUtcDate, 0, Batch.limit(limit)))
|
||||
.compile
|
||||
.to(Vector)
|
||||
makeQuery(ctx) { query =>
|
||||
val items = ctx.store
|
||||
.transact(QItem.findItems(query, now.toUtcDate, 0, Batch.limit(limit)))
|
||||
.compile
|
||||
.to(Vector)
|
||||
|
||||
items.flatMap(cont)
|
||||
case Left(err) =>
|
||||
ctx.logger.error(
|
||||
s"Item query is invalid, stopping: ${ctx.args.query} - ${err.render}"
|
||||
)
|
||||
items.flatMap(cont)
|
||||
}
|
||||
|
||||
def withEventContext[F[_]](
|
||||
|
@ -15,7 +15,8 @@ import io.circe.{Decoder, Encoder}
|
||||
final case class PeriodicQueryArgs(
|
||||
account: AccountId,
|
||||
channel: ChannelOrRef,
|
||||
query: ItemQueryString,
|
||||
query: Option[ItemQueryString],
|
||||
bookmark: Option[String],
|
||||
baseUrl: Option[LenientUri]
|
||||
)
|
||||
|
||||
|
@ -8037,12 +8037,12 @@ extraSchemas:
|
||||
|
||||
PeriodicQuerySettings:
|
||||
description: |
|
||||
Settings for the periodc-query task.
|
||||
Settings for the periodc-query task. At least one of `query` and
|
||||
`bookmark` is required!
|
||||
required:
|
||||
- id
|
||||
- enabled
|
||||
- channel
|
||||
- query
|
||||
- schedule
|
||||
properties:
|
||||
id:
|
||||
@ -8065,6 +8065,10 @@ extraSchemas:
|
||||
query:
|
||||
type: string
|
||||
format: itemquery
|
||||
bookmark:
|
||||
type: string
|
||||
description: |
|
||||
Name or ID of bookmark to use.
|
||||
|
||||
PeriodicDueItemsSettings:
|
||||
description: |
|
||||
|
@ -21,7 +21,8 @@ final case class PeriodicQuerySettings(
|
||||
summary: Option[String],
|
||||
enabled: Boolean,
|
||||
channel: NotificationChannel,
|
||||
query: ItemQuery,
|
||||
query: Option[ItemQuery],
|
||||
bookmark: Option[String],
|
||||
schedule: CalEvent
|
||||
) {}
|
||||
|
||||
|
@ -117,11 +117,20 @@ object PeriodicQueryRoutes extends MailAddressCodec {
|
||||
Sync[F]
|
||||
.pure(for {
|
||||
ch <- NotificationChannel.convert(settings.channel)
|
||||
qstr <- ItemQueryParser
|
||||
.asString(settings.query.expr)
|
||||
.left
|
||||
.map(err => new IllegalArgumentException(s"Query not renderable: $err"))
|
||||
} yield (ch, ItemQueryString(qstr)))
|
||||
qstr <- settings.query match {
|
||||
case Some(q) =>
|
||||
ItemQueryParser
|
||||
.asString(q.expr)
|
||||
.left
|
||||
.map(err => new IllegalArgumentException(s"Query not renderable: $err"))
|
||||
.map(Option.apply)
|
||||
case None =>
|
||||
Right(None)
|
||||
}
|
||||
_ <-
|
||||
if (qstr.nonEmpty || settings.bookmark.nonEmpty) Right(())
|
||||
else Left(new IllegalArgumentException("No query or bookmark provided"))
|
||||
} yield (ch, qstr.map(ItemQueryString.apply)))
|
||||
.rethrow
|
||||
.map { case (channel, qstr) =>
|
||||
UserTask(
|
||||
@ -134,6 +143,7 @@ object PeriodicQueryRoutes extends MailAddressCodec {
|
||||
user,
|
||||
Right(channel),
|
||||
qstr,
|
||||
settings.bookmark,
|
||||
Some(baseUrl / "app" / "item")
|
||||
)
|
||||
)
|
||||
@ -155,7 +165,8 @@ object PeriodicQueryRoutes extends MailAddressCodec {
|
||||
task.summary,
|
||||
task.enabled,
|
||||
ch,
|
||||
ItemQueryParser.parseUnsafe(task.args.query.query),
|
||||
task.args.query.map(_.query).map(ItemQueryParser.parseUnsafe),
|
||||
task.args.bookmark,
|
||||
task.timer
|
||||
)
|
||||
}
|
||||
|
@ -11,12 +11,12 @@ import cats.syntax.all._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.query.ItemQuery
|
||||
import docspell.store.AddResult
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
import docspell.store.AddResult
|
||||
|
||||
final case class RQueryBookmark(
|
||||
id: Ident,
|
||||
@ -153,4 +153,25 @@ object RQueryBookmark {
|
||||
bm.cid === account.collective && (bm.userId.isNull || bm.userId.in(users))
|
||||
).build.query[RQueryBookmark].to[Vector]
|
||||
}
|
||||
|
||||
def findByNameOrId(
|
||||
account: AccountId,
|
||||
nameOrId: String
|
||||
): ConnectionIO[Option[RQueryBookmark]] = {
|
||||
val user = RUser.as("u")
|
||||
val bm = RQueryBookmark.as("bm")
|
||||
|
||||
val users = Select(
|
||||
user.uid.s,
|
||||
from(user),
|
||||
user.cid === account.collective && user.login === account.user
|
||||
)
|
||||
Select(
|
||||
select(bm.all),
|
||||
from(bm),
|
||||
bm.cid === account.collective &&
|
||||
(bm.userId.isNull || bm.userId.in(users)) &&
|
||||
(bm.name === nameOrId || bm.id ==== nameOrId)
|
||||
).build.query[RQueryBookmark].option
|
||||
}
|
||||
}
|
||||
|
@ -138,6 +138,23 @@ object RShare {
|
||||
.option
|
||||
})
|
||||
|
||||
def findOneByCollective(
|
||||
cid: Ident,
|
||||
enabled: Option[Boolean],
|
||||
nameOrId: String
|
||||
): ConnectionIO[Option[RShare]] = {
|
||||
val s = RShare.as("s")
|
||||
val u = RUser.as("u")
|
||||
|
||||
Select(
|
||||
select(s.all),
|
||||
from(s).innerJoin(u, u.uid === s.userId),
|
||||
u.cid === cid &&
|
||||
(s.name === nameOrId || s.id ==== nameOrId) &&?
|
||||
enabled.map(e => s.enabled === e)
|
||||
).build.query[RShare].option
|
||||
}
|
||||
|
||||
def findAllByCollective(
|
||||
cid: Ident,
|
||||
ownerLogin: Option[Ident],
|
||||
|
177
modules/webapp/src/main/elm/Comp/BookmarkDropdown.elm
Normal file
177
modules/webapp/src/main/elm/Comp/BookmarkDropdown.elm
Normal file
@ -0,0 +1,177 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.BookmarkDropdown exposing (Item(..), Model, Msg, getSelected, getSelectedId, init, initWith, update, view)
|
||||
|
||||
import Api
|
||||
import Api.Model.BookmarkedQuery exposing (BookmarkedQuery)
|
||||
import Api.Model.ShareDetail exposing (ShareDetail)
|
||||
import Comp.Dropdown exposing (Option)
|
||||
import Data.Bookmarks exposing (AllBookmarks)
|
||||
import Data.DropdownStyle
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (Html)
|
||||
import Http
|
||||
import Messages.Comp.BookmarkDropdown exposing (Texts)
|
||||
import Util.List
|
||||
|
||||
|
||||
type Model
|
||||
= Model (Comp.Dropdown.Model Item)
|
||||
|
||||
|
||||
type Msg
|
||||
= DropdownMsg (Comp.Dropdown.Msg Item)
|
||||
| GetBookmarksResp (Maybe String) (Result Http.Error AllBookmarks)
|
||||
|
||||
|
||||
initCmd : Flags -> Maybe String -> Cmd Msg
|
||||
initCmd flags selected =
|
||||
Api.getBookmarks flags (GetBookmarksResp selected)
|
||||
|
||||
|
||||
type Item
|
||||
= BM BookmarkedQuery
|
||||
| Share ShareDetail
|
||||
|
||||
|
||||
toItems : AllBookmarks -> List Item
|
||||
toItems all =
|
||||
List.map BM all.bookmarks
|
||||
++ List.map Share all.shares
|
||||
|
||||
|
||||
initWith : AllBookmarks -> Maybe String -> Model
|
||||
initWith bms selected =
|
||||
let
|
||||
items =
|
||||
toItems bms
|
||||
|
||||
findSel id =
|
||||
Util.List.find
|
||||
(\b ->
|
||||
case b of
|
||||
BM m ->
|
||||
m.id == id
|
||||
|
||||
Share s ->
|
||||
s.id == id
|
||||
)
|
||||
items
|
||||
in
|
||||
Model <|
|
||||
Comp.Dropdown.makeSingleList
|
||||
{ options = items, selected = Maybe.andThen findSel selected }
|
||||
|
||||
|
||||
init : Flags -> Maybe String -> ( Model, Cmd Msg )
|
||||
init flags selected =
|
||||
( Model Comp.Dropdown.makeSingle, initCmd flags selected )
|
||||
|
||||
|
||||
getSelected : Model -> Maybe Item
|
||||
getSelected model =
|
||||
case model of
|
||||
Model dm ->
|
||||
Comp.Dropdown.getSelected dm
|
||||
|> List.head
|
||||
|
||||
|
||||
getSelectedId : Model -> Maybe String
|
||||
getSelectedId model =
|
||||
let
|
||||
id item =
|
||||
case item of
|
||||
BM b ->
|
||||
b.id
|
||||
|
||||
Share s ->
|
||||
s.id
|
||||
in
|
||||
getSelected model |> Maybe.map id
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
let
|
||||
dmodel =
|
||||
case model of
|
||||
Model a ->
|
||||
a
|
||||
in
|
||||
case msg of
|
||||
GetBookmarksResp sel (Ok all) ->
|
||||
( initWith all sel, Cmd.none )
|
||||
|
||||
GetBookmarksResp _ (Err err) ->
|
||||
( model, Cmd.none )
|
||||
|
||||
DropdownMsg lm ->
|
||||
let
|
||||
( dm, dc ) =
|
||||
Comp.Dropdown.update lm dmodel
|
||||
in
|
||||
( Model dm, Cmd.map DropdownMsg dc )
|
||||
|
||||
|
||||
|
||||
--- View
|
||||
|
||||
|
||||
itemOption : Texts -> Item -> Option
|
||||
itemOption texts item =
|
||||
case item of
|
||||
BM b ->
|
||||
{ text = b.name
|
||||
, additional =
|
||||
if b.personal then
|
||||
texts.personal
|
||||
|
||||
else
|
||||
texts.collective
|
||||
}
|
||||
|
||||
Share s ->
|
||||
{ text = Maybe.withDefault "-" s.name, additional = texts.share }
|
||||
|
||||
|
||||
itemColor : Item -> String
|
||||
itemColor item =
|
||||
case item of
|
||||
BM b ->
|
||||
if b.personal then
|
||||
"text-cyan-600 dark:text-indigo-300"
|
||||
|
||||
else
|
||||
"text-sky-600 dark:text-violet-300"
|
||||
|
||||
Share _ ->
|
||||
"text-blue-600 dark:text-purple-300"
|
||||
|
||||
|
||||
view : Texts -> UiSettings -> Model -> Html Msg
|
||||
view texts settings model =
|
||||
let
|
||||
viewSettings =
|
||||
{ makeOption = itemOption texts
|
||||
, placeholder = texts.placeholder
|
||||
, labelColor = \a -> \_ -> itemColor a
|
||||
, style = Data.DropdownStyle.mainStyle
|
||||
}
|
||||
|
||||
dm =
|
||||
case model of
|
||||
Model a ->
|
||||
a
|
||||
in
|
||||
Html.map DropdownMsg
|
||||
(Comp.Dropdown.viewSingle2 viewSettings settings dm)
|
@ -834,7 +834,7 @@ viewIntern2 texts settings withButtons model =
|
||||
]
|
||||
, case model.form of
|
||||
TM tm ->
|
||||
Html.map TagMsg (Comp.TagForm.view2 texts.tagForm tm)
|
||||
Html.map TagMsg (Comp.TagForm.view2 texts.tagForm settings tm)
|
||||
|
||||
PMR pm ->
|
||||
Html.map PersonMsg (Comp.PersonForm.view2 texts.personForm True settings pm)
|
||||
|
@ -462,16 +462,17 @@ view2 cfg settings model =
|
||||
viewMultiple2 cfg settings model
|
||||
|
||||
else
|
||||
viewSingle2 cfg model
|
||||
viewSingle2 cfg settings model
|
||||
|
||||
|
||||
viewSingle2 : ViewSettings a -> Model a -> Html (Msg a)
|
||||
viewSingle2 cfg model =
|
||||
viewSingle2 : ViewSettings a -> UiSettings -> Model a -> Html (Msg a)
|
||||
viewSingle2 cfg settings model =
|
||||
let
|
||||
renderItem item =
|
||||
a
|
||||
[ href "#"
|
||||
, class cfg.style.item
|
||||
, class (cfg.labelColor item.value settings)
|
||||
, classList
|
||||
[ ( cfg.style.itemActive, item.active )
|
||||
, ( "font-semibold", item.selected )
|
||||
@ -480,7 +481,7 @@ viewSingle2 cfg model =
|
||||
, onKeyUp KeyPress
|
||||
]
|
||||
[ text <| (.value >> cfg.makeOption >> .text) item
|
||||
, span [ class "text-gray-400 float-right" ]
|
||||
, span [ class "text-gray-400 opacity-75 float-right" ]
|
||||
[ text <| (.value >> cfg.makeOption >> .additional) item
|
||||
]
|
||||
]
|
||||
|
@ -17,6 +17,7 @@ module Comp.PeriodicQueryTaskForm exposing
|
||||
)
|
||||
|
||||
import Comp.Basic as B
|
||||
import Comp.BookmarkDropdown
|
||||
import Comp.CalEventInput
|
||||
import Comp.ChannelForm
|
||||
import Comp.MenuBar as MB
|
||||
@ -44,6 +45,7 @@ type alias Model =
|
||||
, scheduleModel : Comp.CalEventInput.Model
|
||||
, queryModel : Comp.PowerSearchInput.Model
|
||||
, channelModel : Comp.ChannelForm.Model
|
||||
, bookmarkDropdown : Comp.BookmarkDropdown.Model
|
||||
, formState : FormState
|
||||
, loading : Int
|
||||
}
|
||||
@ -75,6 +77,7 @@ type Msg
|
||||
| CalEventMsg Comp.CalEventInput.Msg
|
||||
| QueryMsg Comp.PowerSearchInput.Msg
|
||||
| ChannelMsg Comp.ChannelForm.Msg
|
||||
| BookmarkMsg Comp.BookmarkDropdown.Msg
|
||||
| StartOnce
|
||||
| Cancel
|
||||
| RequestDelete
|
||||
@ -93,11 +96,14 @@ initWith flags s =
|
||||
|
||||
res =
|
||||
Comp.PowerSearchInput.update
|
||||
(Comp.PowerSearchInput.setSearchString s.query)
|
||||
(Comp.PowerSearchInput.setSearchString (Maybe.withDefault "" s.query))
|
||||
Comp.PowerSearchInput.init
|
||||
|
||||
( cfm, cfc ) =
|
||||
Comp.ChannelForm.initWith flags s.channel
|
||||
|
||||
( bm, bc ) =
|
||||
Comp.BookmarkDropdown.init flags s.bookmark
|
||||
in
|
||||
( { settings = s
|
||||
, enabled = s.enabled
|
||||
@ -105,6 +111,7 @@ initWith flags s =
|
||||
, scheduleModel = sm
|
||||
, queryModel = res.model
|
||||
, channelModel = cfm
|
||||
, bookmarkDropdown = bm
|
||||
, formState = FormStateInitial
|
||||
, loading = 0
|
||||
, summary = s.summary
|
||||
@ -113,6 +120,7 @@ initWith flags s =
|
||||
[ Cmd.map CalEventMsg sc
|
||||
, Cmd.map QueryMsg res.cmd
|
||||
, Cmd.map ChannelMsg cfc
|
||||
, Cmd.map BookmarkMsg bc
|
||||
]
|
||||
)
|
||||
|
||||
@ -128,6 +136,9 @@ init flags ct =
|
||||
|
||||
( cfm, cfc ) =
|
||||
Comp.ChannelForm.init flags ct
|
||||
|
||||
( bm, bc ) =
|
||||
Comp.BookmarkDropdown.init flags Nothing
|
||||
in
|
||||
( { settings = Data.PeriodicQuerySettings.empty ct
|
||||
, enabled = False
|
||||
@ -135,6 +146,7 @@ init flags ct =
|
||||
, scheduleModel = sm
|
||||
, queryModel = Comp.PowerSearchInput.init
|
||||
, channelModel = cfm
|
||||
, bookmarkDropdown = bm
|
||||
, formState = FormStateInitial
|
||||
, loading = 0
|
||||
, summary = Nothing
|
||||
@ -142,6 +154,7 @@ init flags ct =
|
||||
, Cmd.batch
|
||||
[ Cmd.map CalEventMsg scmd
|
||||
, Cmd.map ChannelMsg cfc
|
||||
, Cmd.map BookmarkMsg bc
|
||||
]
|
||||
)
|
||||
|
||||
@ -172,27 +185,46 @@ makeSettings model =
|
||||
Nothing ->
|
||||
Err ValidateCalEventInvalid
|
||||
|
||||
queryString =
|
||||
Result.fromMaybe ValidateQueryStringRequired model.queryModel.input
|
||||
query =
|
||||
let
|
||||
qstr =
|
||||
model.queryModel.input
|
||||
|
||||
bm =
|
||||
Comp.BookmarkDropdown.getSelectedId model.bookmarkDropdown
|
||||
in
|
||||
case ( qstr, bm ) of
|
||||
( Just _, Just _ ) ->
|
||||
Result.Ok ( qstr, bm )
|
||||
|
||||
( Just _, Nothing ) ->
|
||||
Result.Ok ( qstr, bm )
|
||||
|
||||
( Nothing, Just _ ) ->
|
||||
Result.Ok ( qstr, bm )
|
||||
|
||||
( Nothing, Nothing ) ->
|
||||
Result.Err ValidateQueryStringRequired
|
||||
|
||||
channelM =
|
||||
Result.fromMaybe
|
||||
ValidateChannelRequired
|
||||
(Comp.ChannelForm.getChannel model.channelModel)
|
||||
|
||||
make timer channel query =
|
||||
make timer channel q =
|
||||
{ prev
|
||||
| enabled = model.enabled
|
||||
, schedule = Data.CalEvent.makeEvent timer
|
||||
, summary = model.summary
|
||||
, channel = channel
|
||||
, query = query
|
||||
, query = Tuple.first q
|
||||
, bookmark = Tuple.second q
|
||||
}
|
||||
in
|
||||
Result.map3 make
|
||||
schedule_
|
||||
channelM
|
||||
queryString
|
||||
query
|
||||
|
||||
|
||||
withValidSettings : (PeriodicQuerySettings -> Action) -> Model -> UpdateResult
|
||||
@ -257,6 +289,17 @@ update flags msg model =
|
||||
, sub = Sub.none
|
||||
}
|
||||
|
||||
BookmarkMsg lm ->
|
||||
let
|
||||
( bm, bc ) =
|
||||
Comp.BookmarkDropdown.update lm model.bookmarkDropdown
|
||||
in
|
||||
{ model = { model | bookmarkDropdown = bm }
|
||||
, action = NoAction
|
||||
, cmd = Cmd.map BookmarkMsg bc
|
||||
, sub = Sub.none
|
||||
}
|
||||
|
||||
ToggleEnabled ->
|
||||
{ model =
|
||||
{ model
|
||||
@ -344,9 +387,14 @@ view texts extraClasses settings model =
|
||||
(Comp.PowerSearchInput.viewResult [] model.queryModel)
|
||||
]
|
||||
|
||||
formHeader txt =
|
||||
formHeader txt req =
|
||||
h2 [ class S.formHeader, class "mt-2" ]
|
||||
[ text txt
|
||||
, if req then
|
||||
B.inputRequired
|
||||
|
||||
else
|
||||
span [] []
|
||||
]
|
||||
in
|
||||
div
|
||||
@ -438,23 +486,29 @@ view texts extraClasses settings model =
|
||||
]
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ formHeader (texts.channelHeader (Comp.ChannelForm.channelType model.channelModel))
|
||||
[ formHeader (texts.channelHeader (Comp.ChannelForm.channelType model.channelModel)) False
|
||||
, Html.map ChannelMsg
|
||||
(Comp.ChannelForm.view texts.channelForm settings model.channelModel)
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ formHeader texts.queryLabel
|
||||
, label
|
||||
[ for "sharequery"
|
||||
, class S.inputLabel
|
||||
[ formHeader texts.queryLabel True
|
||||
, div [ class "mb-3" ]
|
||||
[ label [ class S.inputLabel ]
|
||||
[ text "Bookmark" ]
|
||||
, Html.map BookmarkMsg (Comp.BookmarkDropdown.view texts.bookmarkDropdown settings model.bookmarkDropdown)
|
||||
]
|
||||
[ text texts.queryLabel
|
||||
, B.inputRequired
|
||||
, div [ class "mb-3" ]
|
||||
[ label
|
||||
[ for "sharequery"
|
||||
, class S.inputLabel
|
||||
]
|
||||
[ text texts.queryLabel
|
||||
]
|
||||
, queryInput
|
||||
]
|
||||
, queryInput
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ formHeader texts.schedule
|
||||
[ formHeader texts.schedule False
|
||||
, label [ class S.inputLabel ]
|
||||
[ text texts.schedule
|
||||
, B.inputRequired
|
||||
|
@ -20,6 +20,7 @@ import Comp.Basic as B
|
||||
import Comp.Dropdown
|
||||
import Data.DropdownStyle as DS
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onInput)
|
||||
@ -126,8 +127,8 @@ update _ msg model =
|
||||
--- View2
|
||||
|
||||
|
||||
view2 : Texts -> Model -> Html Msg
|
||||
view2 texts model =
|
||||
view2 : Texts -> UiSettings -> Model -> Html Msg
|
||||
view2 texts settings model =
|
||||
let
|
||||
categoryCfg =
|
||||
{ makeOption = \s -> Comp.Dropdown.mkOption s
|
||||
@ -170,6 +171,7 @@ view2 texts model =
|
||||
, Html.map CatMsg
|
||||
(Comp.Dropdown.viewSingle2
|
||||
categoryCfg
|
||||
settings
|
||||
model.catDropdown
|
||||
)
|
||||
]
|
||||
|
@ -24,6 +24,7 @@ import Comp.TagTable
|
||||
import Comp.YesNoDimmer
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.TagOrder exposing (TagOrder)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onSubmit)
|
||||
@ -247,13 +248,13 @@ update flags msg model =
|
||||
--- View2
|
||||
|
||||
|
||||
view2 : Texts -> Model -> Html Msg
|
||||
view2 texts model =
|
||||
view2 : Texts -> UiSettings -> Model -> Html Msg
|
||||
view2 texts settings model =
|
||||
if model.viewMode == Table then
|
||||
viewTable2 texts model
|
||||
|
||||
else
|
||||
viewForm2 texts model
|
||||
viewForm2 texts settings model
|
||||
|
||||
|
||||
viewTable2 : Texts -> Model -> Html Msg
|
||||
@ -290,8 +291,8 @@ viewTable2 texts model =
|
||||
]
|
||||
|
||||
|
||||
viewForm2 : Texts -> Model -> Html Msg
|
||||
viewForm2 texts model =
|
||||
viewForm2 : Texts -> UiSettings -> Model -> Html Msg
|
||||
viewForm2 texts settings model =
|
||||
let
|
||||
newTag =
|
||||
model.tagFormModel.tag.id == ""
|
||||
@ -373,7 +374,7 @@ viewForm2 texts model =
|
||||
FormErrorSubmit m ->
|
||||
text m
|
||||
]
|
||||
, Html.map FormMsg (Comp.TagForm.view2 texts.tagForm model.tagFormModel)
|
||||
, Html.map FormMsg (Comp.TagForm.view2 texts.tagForm settings model.tagFormModel)
|
||||
, B.loadingDimmer
|
||||
{ active = model.loading
|
||||
, label = texts.basics.loading
|
||||
|
@ -18,7 +18,8 @@ type alias PeriodicQuerySettings =
|
||||
, enabled : Bool
|
||||
, summary : Maybe String
|
||||
, channel : NotificationChannel
|
||||
, query : String
|
||||
, query : Maybe String
|
||||
, bookmark : Maybe String
|
||||
, schedule : String
|
||||
}
|
||||
|
||||
@ -29,19 +30,21 @@ empty ct =
|
||||
, enabled = False
|
||||
, summary = Nothing
|
||||
, channel = Data.NotificationChannel.empty ct
|
||||
, query = ""
|
||||
, query = Nothing
|
||||
, bookmark = Nothing
|
||||
, schedule = ""
|
||||
}
|
||||
|
||||
|
||||
decoder : D.Decoder PeriodicQuerySettings
|
||||
decoder =
|
||||
D.map6 PeriodicQuerySettings
|
||||
D.map7 PeriodicQuerySettings
|
||||
(D.field "id" D.string)
|
||||
(D.field "enabled" D.bool)
|
||||
(D.field "summary" (D.maybe D.string))
|
||||
(D.maybe (D.field "summary" D.string))
|
||||
(D.field "channel" Data.NotificationChannel.decoder)
|
||||
(D.field "query" D.string)
|
||||
(D.maybe (D.field "query" D.string))
|
||||
(D.maybe (D.field "bookmark" D.string))
|
||||
(D.field "schedule" D.string)
|
||||
|
||||
|
||||
@ -52,6 +55,7 @@ encode s =
|
||||
, ( "enabled", E.bool s.enabled )
|
||||
, ( "summary", Maybe.map E.string s.summary |> Maybe.withDefault E.null )
|
||||
, ( "channel", Data.NotificationChannel.encode s.channel )
|
||||
, ( "query", E.string s.query )
|
||||
, ( "query", Maybe.map E.string s.query |> Maybe.withDefault E.null )
|
||||
, ( "bookmark", Maybe.map E.string s.bookmark |> Maybe.withDefault E.null )
|
||||
, ( "schedule", E.string s.schedule )
|
||||
]
|
||||
|
@ -0,0 +1,43 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Messages.Comp.BookmarkDropdown exposing
|
||||
( Texts
|
||||
, de
|
||||
, gb
|
||||
)
|
||||
|
||||
import Messages.Basics
|
||||
|
||||
|
||||
type alias Texts =
|
||||
{ basics : Messages.Basics.Texts
|
||||
, placeholder : String
|
||||
, personal : String
|
||||
, collective : String
|
||||
, share : String
|
||||
}
|
||||
|
||||
|
||||
gb : Texts
|
||||
gb =
|
||||
{ basics = Messages.Basics.gb
|
||||
, placeholder = "Bookmark…"
|
||||
, personal = "Personal"
|
||||
, collective = "Collective"
|
||||
, share = "Share"
|
||||
}
|
||||
|
||||
|
||||
de : Texts
|
||||
de =
|
||||
{ basics = Messages.Basics.de
|
||||
, placeholder = "Bookmark…"
|
||||
, personal = "Persönlich"
|
||||
, collective = "Kollektiv"
|
||||
, share = "Freigabe"
|
||||
}
|
@ -33,7 +33,7 @@ gb =
|
||||
, userLocationText = "The bookmarked query is just for you"
|
||||
, collectiveLocation = "Collective scope"
|
||||
, collectiveLocationText = "The bookmarked query can be used and edited by all users"
|
||||
, nameExistsWarning = "A bookmark with this name exists!"
|
||||
, nameExistsWarning = "A bookmark with this name exists! Choose another name."
|
||||
}
|
||||
|
||||
|
||||
@ -45,5 +45,5 @@ de =
|
||||
, userLocationText = "Der Bookmark ist nur für dich"
|
||||
, collectiveLocation = "Kollektiv-Bookmark"
|
||||
, collectiveLocationText = "Der Bookmark kann von allen Benutzer verwendet werden"
|
||||
, nameExistsWarning = "Der Bookmark existiert bereits!"
|
||||
, nameExistsWarning = "Der Bookmark existiert bereits! Verwende einen anderen Namen."
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ module Messages.Comp.PeriodicQueryTaskForm exposing
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Http
|
||||
import Messages.Basics
|
||||
import Messages.Comp.BookmarkDropdown
|
||||
import Messages.Comp.CalEventInput
|
||||
import Messages.Comp.ChannelForm
|
||||
import Messages.Comp.HttpError
|
||||
@ -24,6 +25,7 @@ type alias Texts =
|
||||
{ basics : Messages.Basics.Texts
|
||||
, calEventInput : Messages.Comp.CalEventInput.Texts
|
||||
, channelForm : Messages.Comp.ChannelForm.Texts
|
||||
, bookmarkDropdown : Messages.Comp.BookmarkDropdown.Texts
|
||||
, httpError : Http.Error -> String
|
||||
, reallyDeleteTask : String
|
||||
, startOnce : String
|
||||
@ -49,6 +51,7 @@ gb =
|
||||
, calEventInput = Messages.Comp.CalEventInput.gb
|
||||
, channelForm = Messages.Comp.ChannelForm.gb
|
||||
, httpError = Messages.Comp.HttpError.gb
|
||||
, bookmarkDropdown = Messages.Comp.BookmarkDropdown.gb
|
||||
, reallyDeleteTask = "Really delete this notification task?"
|
||||
, startOnce = "Start Once"
|
||||
, startTaskNow = "Start this task now"
|
||||
@ -66,7 +69,7 @@ gb =
|
||||
, invalidCalEvent = "The calendar event is not valid."
|
||||
, queryLabel = "Query"
|
||||
, channelRequired = "A valid channel must be given."
|
||||
, queryStringRequired = "A query string must be supplied"
|
||||
, queryStringRequired = "A query string and/or bookmark must be supplied"
|
||||
, channelHeader = \ct -> "Connection details for " ++ Messages.Data.ChannelType.gb ct
|
||||
}
|
||||
|
||||
@ -77,6 +80,7 @@ de =
|
||||
, calEventInput = Messages.Comp.CalEventInput.de
|
||||
, channelForm = Messages.Comp.ChannelForm.de
|
||||
, httpError = Messages.Comp.HttpError.de
|
||||
, bookmarkDropdown = Messages.Comp.BookmarkDropdown.de
|
||||
, reallyDeleteTask = "Diesen Benachrichtigungsauftrag wirklich löschen?"
|
||||
, startOnce = "Jetzt starten"
|
||||
, startTaskNow = "Starte den Auftrag sofort"
|
||||
@ -94,6 +98,6 @@ de =
|
||||
, invalidCalEvent = "Das Kalenderereignis ist nicht gültig."
|
||||
, queryLabel = "Abfrage"
|
||||
, channelRequired = "Ein Versandkanal muss angegeben werden."
|
||||
, queryStringRequired = "Eine Suchabfrage muss angegeben werden."
|
||||
, queryStringRequired = "Eine Suchabfrage und/oder ein Bookmark muss angegeben werden."
|
||||
, channelHeader = \ct -> "Details für " ++ Messages.Data.ChannelType.de ct
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ viewContent texts flags settings model =
|
||||
]
|
||||
(case model.currentTab of
|
||||
Just TagTab ->
|
||||
viewTags texts model
|
||||
viewTags texts settings model
|
||||
|
||||
Just EquipTab ->
|
||||
viewEquip texts model
|
||||
@ -180,8 +180,8 @@ menuEntryActive model tab =
|
||||
class ""
|
||||
|
||||
|
||||
viewTags : Texts -> Model -> List (Html Msg)
|
||||
viewTags texts model =
|
||||
viewTags : Texts -> UiSettings -> Model -> List (Html Msg)
|
||||
viewTags texts settings model =
|
||||
[ h2
|
||||
[ class S.header1
|
||||
, class "inline-flex items-center"
|
||||
@ -194,6 +194,7 @@ viewTags texts model =
|
||||
, Html.map TagManageMsg
|
||||
(Comp.TagManage.view2
|
||||
texts.tagManage
|
||||
settings
|
||||
model.tagManageModel
|
||||
)
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user