Allow bookmarks in periodic query notification

This commit is contained in:
eikek
2022-01-10 14:25:20 +01:00
parent ccb4df5bd7
commit 699cf091e6
19 changed files with 497 additions and 82 deletions

View File

@ -20,6 +20,8 @@ trait OQueryBookmarks[F[_]] {
def getAll(account: AccountId): F[Vector[OQueryBookmarks.Bookmark]] 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 create(account: AccountId, bookmark: OQueryBookmarks.NewBookmark): F[AddResult]
def update( def update(
@ -53,9 +55,15 @@ object OQueryBookmarks {
def getAll(account: AccountId): F[Vector[Bookmark]] = def getAll(account: AccountId): F[Vector[Bookmark]] =
store store
.transact(RQueryBookmark.allForUser(account)) .transact(RQueryBookmark.allForUser(account))
.map( .map(_.map(convert.toModel))
_.map(r => Bookmark(r.id, r.name, r.label, r.query, r.isPersonal, r.created))
) 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] = { def create(account: AccountId, b: NewBookmark): F[AddResult] = {
val record = val record =
@ -65,23 +73,28 @@ object OQueryBookmarks {
def update(account: AccountId, id: Ident, b: NewBookmark): F[UpdateResult] = def update(account: AccountId, id: Ident, b: NewBookmark): F[UpdateResult] =
UpdateResult.fromUpdate( UpdateResult.fromUpdate(
store.transact( store.transact(RQueryBookmark.update(convert.toRecord(account, id, b)))
RQueryBookmark.update(
RQueryBookmark(
id,
b.name,
b.label,
None, // userId and some other values are not used
account.collective,
b.query,
Timestamp.Epoch
)
)
)
) )
def delete(account: AccountId, bookmark: Ident): F[Unit] = def delete(account: AccountId, bookmark: Ident): F[Unit] =
store.transact(RQueryBookmark.deleteById(account.collective, bookmark)).as(()) 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
)
}
} }

View File

@ -7,6 +7,7 @@
package docspell.joex.notify package docspell.joex.notify
import cats.data.OptionT import cats.data.OptionT
import cats.data.{NonEmptyList => Nel}
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
@ -17,10 +18,14 @@ import docspell.joex.scheduler.Task
import docspell.notification.api.EventContext import docspell.notification.api.EventContext
import docspell.notification.api.NotificationChannel import docspell.notification.api.NotificationChannel
import docspell.notification.api.PeriodicQueryArgs import docspell.notification.api.PeriodicQueryArgs
import docspell.query.ItemQuery
import docspell.query.ItemQuery.Expr.AndExpr
import docspell.query.ItemQueryParser import docspell.query.ItemQueryParser
import docspell.store.qb.Batch import docspell.store.qb.Batch
import docspell.store.queries.ListItem import docspell.store.queries.ListItem
import docspell.store.queries.{QItem, Query} import docspell.store.queries.{QItem, Query}
import docspell.store.records.RQueryBookmark
import docspell.store.records.RShare
import docspell.store.records.RUser import docspell.store.records.RUser
object PeriodicQueryTask { object PeriodicQueryTask {
@ -54,22 +59,77 @@ object PeriodicQueryTask {
) )
.getOrElse(()) .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)( def withItems[F[_]: Sync](ctx: Context[F, Args], limit: Int, now: Timestamp)(
cont: Vector[ListItem] => F[Unit] cont: Vector[ListItem] => F[Unit]
): F[Unit] = ): F[Unit] =
ItemQueryParser.parse(ctx.args.query.query) match { makeQuery(ctx) { query =>
case Right(q) => val items = ctx.store
val query = Query(Query.Fix(ctx.args.account, Some(q.expr), None)) .transact(QItem.findItems(query, now.toUtcDate, 0, Batch.limit(limit)))
val items = ctx.store .compile
.transact(QItem.findItems(query, now.toUtcDate, 0, Batch.limit(limit))) .to(Vector)
.compile
.to(Vector)
items.flatMap(cont) items.flatMap(cont)
case Left(err) =>
ctx.logger.error(
s"Item query is invalid, stopping: ${ctx.args.query} - ${err.render}"
)
} }
def withEventContext[F[_]]( def withEventContext[F[_]](

View File

@ -15,7 +15,8 @@ import io.circe.{Decoder, Encoder}
final case class PeriodicQueryArgs( final case class PeriodicQueryArgs(
account: AccountId, account: AccountId,
channel: ChannelOrRef, channel: ChannelOrRef,
query: ItemQueryString, query: Option[ItemQueryString],
bookmark: Option[String],
baseUrl: Option[LenientUri] baseUrl: Option[LenientUri]
) )

View File

@ -8037,12 +8037,12 @@ extraSchemas:
PeriodicQuerySettings: PeriodicQuerySettings:
description: | description: |
Settings for the periodc-query task. Settings for the periodc-query task. At least one of `query` and
`bookmark` is required!
required: required:
- id - id
- enabled - enabled
- channel - channel
- query
- schedule - schedule
properties: properties:
id: id:
@ -8065,6 +8065,10 @@ extraSchemas:
query: query:
type: string type: string
format: itemquery format: itemquery
bookmark:
type: string
description: |
Name or ID of bookmark to use.
PeriodicDueItemsSettings: PeriodicDueItemsSettings:
description: | description: |

View File

@ -21,7 +21,8 @@ final case class PeriodicQuerySettings(
summary: Option[String], summary: Option[String],
enabled: Boolean, enabled: Boolean,
channel: NotificationChannel, channel: NotificationChannel,
query: ItemQuery, query: Option[ItemQuery],
bookmark: Option[String],
schedule: CalEvent schedule: CalEvent
) {} ) {}

View File

@ -117,11 +117,20 @@ object PeriodicQueryRoutes extends MailAddressCodec {
Sync[F] Sync[F]
.pure(for { .pure(for {
ch <- NotificationChannel.convert(settings.channel) ch <- NotificationChannel.convert(settings.channel)
qstr <- ItemQueryParser qstr <- settings.query match {
.asString(settings.query.expr) case Some(q) =>
.left ItemQueryParser
.map(err => new IllegalArgumentException(s"Query not renderable: $err")) .asString(q.expr)
} yield (ch, ItemQueryString(qstr))) .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 .rethrow
.map { case (channel, qstr) => .map { case (channel, qstr) =>
UserTask( UserTask(
@ -134,6 +143,7 @@ object PeriodicQueryRoutes extends MailAddressCodec {
user, user,
Right(channel), Right(channel),
qstr, qstr,
settings.bookmark,
Some(baseUrl / "app" / "item") Some(baseUrl / "app" / "item")
) )
) )
@ -155,7 +165,8 @@ object PeriodicQueryRoutes extends MailAddressCodec {
task.summary, task.summary,
task.enabled, task.enabled,
ch, ch,
ItemQueryParser.parseUnsafe(task.args.query.query), task.args.query.map(_.query).map(ItemQueryParser.parseUnsafe),
task.args.bookmark,
task.timer task.timer
) )
} }

View File

@ -11,12 +11,12 @@ import cats.syntax.all._
import docspell.common._ import docspell.common._
import docspell.query.ItemQuery import docspell.query.ItemQuery
import docspell.store.AddResult
import docspell.store.qb.DSL._ import docspell.store.qb.DSL._
import docspell.store.qb._ import docspell.store.qb._
import doobie._ import doobie._
import doobie.implicits._ import doobie.implicits._
import docspell.store.AddResult
final case class RQueryBookmark( final case class RQueryBookmark(
id: Ident, id: Ident,
@ -153,4 +153,25 @@ object RQueryBookmark {
bm.cid === account.collective && (bm.userId.isNull || bm.userId.in(users)) bm.cid === account.collective && (bm.userId.isNull || bm.userId.in(users))
).build.query[RQueryBookmark].to[Vector] ).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
}
} }

View File

@ -138,6 +138,23 @@ object RShare {
.option .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( def findAllByCollective(
cid: Ident, cid: Ident,
ownerLogin: Option[Ident], ownerLogin: Option[Ident],

View 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)

View File

@ -834,7 +834,7 @@ viewIntern2 texts settings withButtons model =
] ]
, case model.form of , case model.form of
TM tm -> TM tm ->
Html.map TagMsg (Comp.TagForm.view2 texts.tagForm tm) Html.map TagMsg (Comp.TagForm.view2 texts.tagForm settings tm)
PMR pm -> PMR pm ->
Html.map PersonMsg (Comp.PersonForm.view2 texts.personForm True settings pm) Html.map PersonMsg (Comp.PersonForm.view2 texts.personForm True settings pm)

View File

@ -462,16 +462,17 @@ view2 cfg settings model =
viewMultiple2 cfg settings model viewMultiple2 cfg settings model
else else
viewSingle2 cfg model viewSingle2 cfg settings model
viewSingle2 : ViewSettings a -> Model a -> Html (Msg a) viewSingle2 : ViewSettings a -> UiSettings -> Model a -> Html (Msg a)
viewSingle2 cfg model = viewSingle2 cfg settings model =
let let
renderItem item = renderItem item =
a a
[ href "#" [ href "#"
, class cfg.style.item , class cfg.style.item
, class (cfg.labelColor item.value settings)
, classList , classList
[ ( cfg.style.itemActive, item.active ) [ ( cfg.style.itemActive, item.active )
, ( "font-semibold", item.selected ) , ( "font-semibold", item.selected )
@ -480,7 +481,7 @@ viewSingle2 cfg model =
, onKeyUp KeyPress , onKeyUp KeyPress
] ]
[ text <| (.value >> cfg.makeOption >> .text) item [ 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 [ text <| (.value >> cfg.makeOption >> .additional) item
] ]
] ]

View File

@ -17,6 +17,7 @@ module Comp.PeriodicQueryTaskForm exposing
) )
import Comp.Basic as B import Comp.Basic as B
import Comp.BookmarkDropdown
import Comp.CalEventInput import Comp.CalEventInput
import Comp.ChannelForm import Comp.ChannelForm
import Comp.MenuBar as MB import Comp.MenuBar as MB
@ -44,6 +45,7 @@ type alias Model =
, scheduleModel : Comp.CalEventInput.Model , scheduleModel : Comp.CalEventInput.Model
, queryModel : Comp.PowerSearchInput.Model , queryModel : Comp.PowerSearchInput.Model
, channelModel : Comp.ChannelForm.Model , channelModel : Comp.ChannelForm.Model
, bookmarkDropdown : Comp.BookmarkDropdown.Model
, formState : FormState , formState : FormState
, loading : Int , loading : Int
} }
@ -75,6 +77,7 @@ type Msg
| CalEventMsg Comp.CalEventInput.Msg | CalEventMsg Comp.CalEventInput.Msg
| QueryMsg Comp.PowerSearchInput.Msg | QueryMsg Comp.PowerSearchInput.Msg
| ChannelMsg Comp.ChannelForm.Msg | ChannelMsg Comp.ChannelForm.Msg
| BookmarkMsg Comp.BookmarkDropdown.Msg
| StartOnce | StartOnce
| Cancel | Cancel
| RequestDelete | RequestDelete
@ -93,11 +96,14 @@ initWith flags s =
res = res =
Comp.PowerSearchInput.update Comp.PowerSearchInput.update
(Comp.PowerSearchInput.setSearchString s.query) (Comp.PowerSearchInput.setSearchString (Maybe.withDefault "" s.query))
Comp.PowerSearchInput.init Comp.PowerSearchInput.init
( cfm, cfc ) = ( cfm, cfc ) =
Comp.ChannelForm.initWith flags s.channel Comp.ChannelForm.initWith flags s.channel
( bm, bc ) =
Comp.BookmarkDropdown.init flags s.bookmark
in in
( { settings = s ( { settings = s
, enabled = s.enabled , enabled = s.enabled
@ -105,6 +111,7 @@ initWith flags s =
, scheduleModel = sm , scheduleModel = sm
, queryModel = res.model , queryModel = res.model
, channelModel = cfm , channelModel = cfm
, bookmarkDropdown = bm
, formState = FormStateInitial , formState = FormStateInitial
, loading = 0 , loading = 0
, summary = s.summary , summary = s.summary
@ -113,6 +120,7 @@ initWith flags s =
[ Cmd.map CalEventMsg sc [ Cmd.map CalEventMsg sc
, Cmd.map QueryMsg res.cmd , Cmd.map QueryMsg res.cmd
, Cmd.map ChannelMsg cfc , Cmd.map ChannelMsg cfc
, Cmd.map BookmarkMsg bc
] ]
) )
@ -128,6 +136,9 @@ init flags ct =
( cfm, cfc ) = ( cfm, cfc ) =
Comp.ChannelForm.init flags ct Comp.ChannelForm.init flags ct
( bm, bc ) =
Comp.BookmarkDropdown.init flags Nothing
in in
( { settings = Data.PeriodicQuerySettings.empty ct ( { settings = Data.PeriodicQuerySettings.empty ct
, enabled = False , enabled = False
@ -135,6 +146,7 @@ init flags ct =
, scheduleModel = sm , scheduleModel = sm
, queryModel = Comp.PowerSearchInput.init , queryModel = Comp.PowerSearchInput.init
, channelModel = cfm , channelModel = cfm
, bookmarkDropdown = bm
, formState = FormStateInitial , formState = FormStateInitial
, loading = 0 , loading = 0
, summary = Nothing , summary = Nothing
@ -142,6 +154,7 @@ init flags ct =
, Cmd.batch , Cmd.batch
[ Cmd.map CalEventMsg scmd [ Cmd.map CalEventMsg scmd
, Cmd.map ChannelMsg cfc , Cmd.map ChannelMsg cfc
, Cmd.map BookmarkMsg bc
] ]
) )
@ -172,27 +185,46 @@ makeSettings model =
Nothing -> Nothing ->
Err ValidateCalEventInvalid Err ValidateCalEventInvalid
queryString = query =
Result.fromMaybe ValidateQueryStringRequired model.queryModel.input 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 = channelM =
Result.fromMaybe Result.fromMaybe
ValidateChannelRequired ValidateChannelRequired
(Comp.ChannelForm.getChannel model.channelModel) (Comp.ChannelForm.getChannel model.channelModel)
make timer channel query = make timer channel q =
{ prev { prev
| enabled = model.enabled | enabled = model.enabled
, schedule = Data.CalEvent.makeEvent timer , schedule = Data.CalEvent.makeEvent timer
, summary = model.summary , summary = model.summary
, channel = channel , channel = channel
, query = query , query = Tuple.first q
, bookmark = Tuple.second q
} }
in in
Result.map3 make Result.map3 make
schedule_ schedule_
channelM channelM
queryString query
withValidSettings : (PeriodicQuerySettings -> Action) -> Model -> UpdateResult withValidSettings : (PeriodicQuerySettings -> Action) -> Model -> UpdateResult
@ -257,6 +289,17 @@ update flags msg model =
, sub = Sub.none , 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 -> ToggleEnabled ->
{ model = { model =
{ model { model
@ -344,9 +387,14 @@ view texts extraClasses settings model =
(Comp.PowerSearchInput.viewResult [] model.queryModel) (Comp.PowerSearchInput.viewResult [] model.queryModel)
] ]
formHeader txt = formHeader txt req =
h2 [ class S.formHeader, class "mt-2" ] h2 [ class S.formHeader, class "mt-2" ]
[ text txt [ text txt
, if req then
B.inputRequired
else
span [] []
] ]
in in
div div
@ -438,23 +486,29 @@ view texts extraClasses settings model =
] ]
] ]
, div [ class "mb-4" ] , div [ class "mb-4" ]
[ formHeader (texts.channelHeader (Comp.ChannelForm.channelType model.channelModel)) [ formHeader (texts.channelHeader (Comp.ChannelForm.channelType model.channelModel)) False
, Html.map ChannelMsg , Html.map ChannelMsg
(Comp.ChannelForm.view texts.channelForm settings model.channelModel) (Comp.ChannelForm.view texts.channelForm settings model.channelModel)
] ]
, div [ class "mb-4" ] , div [ class "mb-4" ]
[ formHeader texts.queryLabel [ formHeader texts.queryLabel True
, label , div [ class "mb-3" ]
[ for "sharequery" [ label [ class S.inputLabel ]
, class S.inputLabel [ text "Bookmark" ]
, Html.map BookmarkMsg (Comp.BookmarkDropdown.view texts.bookmarkDropdown settings model.bookmarkDropdown)
] ]
[ text texts.queryLabel , div [ class "mb-3" ]
, B.inputRequired [ label
[ for "sharequery"
, class S.inputLabel
]
[ text texts.queryLabel
]
, queryInput
] ]
, queryInput
] ]
, div [ class "mb-4" ] , div [ class "mb-4" ]
[ formHeader texts.schedule [ formHeader texts.schedule False
, label [ class S.inputLabel ] , label [ class S.inputLabel ]
[ text texts.schedule [ text texts.schedule
, B.inputRequired , B.inputRequired

View File

@ -20,6 +20,7 @@ import Comp.Basic as B
import Comp.Dropdown import Comp.Dropdown
import Data.DropdownStyle as DS import Data.DropdownStyle as DS
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (onInput) import Html.Events exposing (onInput)
@ -126,8 +127,8 @@ update _ msg model =
--- View2 --- View2
view2 : Texts -> Model -> Html Msg view2 : Texts -> UiSettings -> Model -> Html Msg
view2 texts model = view2 texts settings model =
let let
categoryCfg = categoryCfg =
{ makeOption = \s -> Comp.Dropdown.mkOption s { makeOption = \s -> Comp.Dropdown.mkOption s
@ -170,6 +171,7 @@ view2 texts model =
, Html.map CatMsg , Html.map CatMsg
(Comp.Dropdown.viewSingle2 (Comp.Dropdown.viewSingle2
categoryCfg categoryCfg
settings
model.catDropdown model.catDropdown
) )
] ]

View File

@ -24,6 +24,7 @@ import Comp.TagTable
import Comp.YesNoDimmer import Comp.YesNoDimmer
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.TagOrder exposing (TagOrder) import Data.TagOrder exposing (TagOrder)
import Data.UiSettings exposing (UiSettings)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (onSubmit) import Html.Events exposing (onSubmit)
@ -247,13 +248,13 @@ update flags msg model =
--- View2 --- View2
view2 : Texts -> Model -> Html Msg view2 : Texts -> UiSettings -> Model -> Html Msg
view2 texts model = view2 texts settings model =
if model.viewMode == Table then if model.viewMode == Table then
viewTable2 texts model viewTable2 texts model
else else
viewForm2 texts model viewForm2 texts settings model
viewTable2 : Texts -> Model -> Html Msg viewTable2 : Texts -> Model -> Html Msg
@ -290,8 +291,8 @@ viewTable2 texts model =
] ]
viewForm2 : Texts -> Model -> Html Msg viewForm2 : Texts -> UiSettings -> Model -> Html Msg
viewForm2 texts model = viewForm2 texts settings model =
let let
newTag = newTag =
model.tagFormModel.tag.id == "" model.tagFormModel.tag.id == ""
@ -373,7 +374,7 @@ viewForm2 texts model =
FormErrorSubmit m -> FormErrorSubmit m ->
text 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 , B.loadingDimmer
{ active = model.loading { active = model.loading
, label = texts.basics.loading , label = texts.basics.loading

View File

@ -18,7 +18,8 @@ type alias PeriodicQuerySettings =
, enabled : Bool , enabled : Bool
, summary : Maybe String , summary : Maybe String
, channel : NotificationChannel , channel : NotificationChannel
, query : String , query : Maybe String
, bookmark : Maybe String
, schedule : String , schedule : String
} }
@ -29,19 +30,21 @@ empty ct =
, enabled = False , enabled = False
, summary = Nothing , summary = Nothing
, channel = Data.NotificationChannel.empty ct , channel = Data.NotificationChannel.empty ct
, query = "" , query = Nothing
, bookmark = Nothing
, schedule = "" , schedule = ""
} }
decoder : D.Decoder PeriodicQuerySettings decoder : D.Decoder PeriodicQuerySettings
decoder = decoder =
D.map6 PeriodicQuerySettings D.map7 PeriodicQuerySettings
(D.field "id" D.string) (D.field "id" D.string)
(D.field "enabled" D.bool) (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 "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) (D.field "schedule" D.string)
@ -52,6 +55,7 @@ encode s =
, ( "enabled", E.bool s.enabled ) , ( "enabled", E.bool s.enabled )
, ( "summary", Maybe.map E.string s.summary |> Maybe.withDefault E.null ) , ( "summary", Maybe.map E.string s.summary |> Maybe.withDefault E.null )
, ( "channel", Data.NotificationChannel.encode s.channel ) , ( "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 ) , ( "schedule", E.string s.schedule )
] ]

View File

@ -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"
}

View File

@ -33,7 +33,7 @@ gb =
, userLocationText = "The bookmarked query is just for you" , userLocationText = "The bookmarked query is just for you"
, collectiveLocation = "Collective scope" , collectiveLocation = "Collective scope"
, collectiveLocationText = "The bookmarked query can be used and edited by all users" , 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" , userLocationText = "Der Bookmark ist nur für dich"
, collectiveLocation = "Kollektiv-Bookmark" , collectiveLocation = "Kollektiv-Bookmark"
, collectiveLocationText = "Der Bookmark kann von allen Benutzer verwendet werden" , collectiveLocationText = "Der Bookmark kann von allen Benutzer verwendet werden"
, nameExistsWarning = "Der Bookmark existiert bereits!" , nameExistsWarning = "Der Bookmark existiert bereits! Verwende einen anderen Namen."
} }

View File

@ -14,6 +14,7 @@ module Messages.Comp.PeriodicQueryTaskForm exposing
import Data.ChannelType exposing (ChannelType) import Data.ChannelType exposing (ChannelType)
import Http import Http
import Messages.Basics import Messages.Basics
import Messages.Comp.BookmarkDropdown
import Messages.Comp.CalEventInput import Messages.Comp.CalEventInput
import Messages.Comp.ChannelForm import Messages.Comp.ChannelForm
import Messages.Comp.HttpError import Messages.Comp.HttpError
@ -24,6 +25,7 @@ type alias Texts =
{ basics : Messages.Basics.Texts { basics : Messages.Basics.Texts
, calEventInput : Messages.Comp.CalEventInput.Texts , calEventInput : Messages.Comp.CalEventInput.Texts
, channelForm : Messages.Comp.ChannelForm.Texts , channelForm : Messages.Comp.ChannelForm.Texts
, bookmarkDropdown : Messages.Comp.BookmarkDropdown.Texts
, httpError : Http.Error -> String , httpError : Http.Error -> String
, reallyDeleteTask : String , reallyDeleteTask : String
, startOnce : String , startOnce : String
@ -49,6 +51,7 @@ gb =
, calEventInput = Messages.Comp.CalEventInput.gb , calEventInput = Messages.Comp.CalEventInput.gb
, channelForm = Messages.Comp.ChannelForm.gb , channelForm = Messages.Comp.ChannelForm.gb
, httpError = Messages.Comp.HttpError.gb , httpError = Messages.Comp.HttpError.gb
, bookmarkDropdown = Messages.Comp.BookmarkDropdown.gb
, reallyDeleteTask = "Really delete this notification task?" , reallyDeleteTask = "Really delete this notification task?"
, startOnce = "Start Once" , startOnce = "Start Once"
, startTaskNow = "Start this task now" , startTaskNow = "Start this task now"
@ -66,7 +69,7 @@ gb =
, invalidCalEvent = "The calendar event is not valid." , invalidCalEvent = "The calendar event is not valid."
, queryLabel = "Query" , queryLabel = "Query"
, channelRequired = "A valid channel must be given." , 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 , channelHeader = \ct -> "Connection details for " ++ Messages.Data.ChannelType.gb ct
} }
@ -77,6 +80,7 @@ de =
, calEventInput = Messages.Comp.CalEventInput.de , calEventInput = Messages.Comp.CalEventInput.de
, channelForm = Messages.Comp.ChannelForm.de , channelForm = Messages.Comp.ChannelForm.de
, httpError = Messages.Comp.HttpError.de , httpError = Messages.Comp.HttpError.de
, bookmarkDropdown = Messages.Comp.BookmarkDropdown.de
, reallyDeleteTask = "Diesen Benachrichtigungsauftrag wirklich löschen?" , reallyDeleteTask = "Diesen Benachrichtigungsauftrag wirklich löschen?"
, startOnce = "Jetzt starten" , startOnce = "Jetzt starten"
, startTaskNow = "Starte den Auftrag sofort" , startTaskNow = "Starte den Auftrag sofort"
@ -94,6 +98,6 @@ de =
, invalidCalEvent = "Das Kalenderereignis ist nicht gültig." , invalidCalEvent = "Das Kalenderereignis ist nicht gültig."
, queryLabel = "Abfrage" , queryLabel = "Abfrage"
, channelRequired = "Ein Versandkanal muss angegeben werden." , 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 , channelHeader = \ct -> "Details für " ++ Messages.Data.ChannelType.de ct
} }

View File

@ -146,7 +146,7 @@ viewContent texts flags settings model =
] ]
(case model.currentTab of (case model.currentTab of
Just TagTab -> Just TagTab ->
viewTags texts model viewTags texts settings model
Just EquipTab -> Just EquipTab ->
viewEquip texts model viewEquip texts model
@ -180,8 +180,8 @@ menuEntryActive model tab =
class "" class ""
viewTags : Texts -> Model -> List (Html Msg) viewTags : Texts -> UiSettings -> Model -> List (Html Msg)
viewTags texts model = viewTags texts settings model =
[ h2 [ h2
[ class S.header1 [ class S.header1
, class "inline-flex items-center" , class "inline-flex items-center"
@ -194,6 +194,7 @@ viewTags texts model =
, Html.map TagManageMsg , Html.map TagManageMsg
(Comp.TagManage.view2 (Comp.TagManage.view2
texts.tagManage texts.tagManage
settings
model.tagManageModel model.tagManageModel
) )
] ]