Instead of client only, make bookmarks a server aware feature

Makes it much more useful
This commit is contained in:
eikek
2022-01-09 23:50:34 +01:00
parent 063ae56488
commit 9415f72ec0
19 changed files with 618 additions and 345 deletions

View File

@ -49,6 +49,7 @@ trait BackendApp[F[_]] {
def pubSub: PubSubT[F] def pubSub: PubSubT[F]
def events: EventExchange[F] def events: EventExchange[F]
def notification: ONotification[F] def notification: ONotification[F]
def bookmarks: OQueryBookmarks[F]
} }
object BackendApp { object BackendApp {
@ -89,6 +90,7 @@ object BackendApp {
OShare(store, itemSearchImpl, simpleSearchImpl, javaEmil) OShare(store, itemSearchImpl, simpleSearchImpl, javaEmil)
) )
notifyImpl <- ONotification(store, notificationMod) notifyImpl <- ONotification(store, notificationMod)
bookmarksImpl <- OQueryBookmarks(store)
} yield new BackendApp[F] { } yield new BackendApp[F] {
val pubSub = pubSubT val pubSub = pubSubT
val login = loginImpl val login = loginImpl
@ -115,5 +117,6 @@ object BackendApp {
val share = shareImpl val share = shareImpl
val events = notificationMod val events = notificationMod
val notification = notifyImpl val notification = notifyImpl
val bookmarks = bookmarksImpl
} }
} }

View File

@ -0,0 +1,83 @@
package docspell.backend.ops
import docspell.common._
import docspell.query.ItemQuery
import cats.effect._
import docspell.store.Store
import docspell.store.records.RQueryBookmark
import cats.implicits._
import docspell.store.UpdateResult
import docspell.store.AddResult
trait OQueryBookmarks[F[_]] {
def getAll(account: AccountId): F[Vector[OQueryBookmarks.Bookmark]]
def create(account: AccountId, bookmark: OQueryBookmarks.NewBookmark): F[AddResult]
def update(
account: AccountId,
id: Ident,
bookmark: OQueryBookmarks.NewBookmark
): F[UpdateResult]
def delete(account: AccountId, bookmark: Ident): F[Unit]
}
object OQueryBookmarks {
final case class NewBookmark(
name: String,
label: Option[String],
query: ItemQuery,
personal: Boolean
)
final case class Bookmark(
id: Ident,
name: String,
label: Option[String],
query: ItemQuery,
personal: Boolean,
created: Timestamp
)
def apply[F[_]: Sync](store: Store[F]): Resource[F, OQueryBookmarks[F]] =
Resource.pure(new OQueryBookmarks[F] {
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))
)
def create(account: AccountId, b: NewBookmark): F[AddResult] =
store
.transact(for {
r <- RQueryBookmark.createNew(account, b.name, b.label, b.query, b.personal)
n <- RQueryBookmark.insert(r)
} yield n)
.attempt
.map(AddResult.fromUpdate)
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
)
)
)
)
def delete(account: AccountId, bookmark: Ident): F[Unit] =
store.transact(RQueryBookmark.deleteById(account.collective, bookmark)).as(())
})
}

View File

@ -1880,6 +1880,98 @@ paths:
application/json: application/json:
schema: {} schema: {}
/sec/querybookmark:
get:
operationId: "sec-querybookmark-get-all"
tags: [Query Bookmarks]
summary: Return all query bookmarks
description: |
Returns all query bookmarks of the current user.
Bookmarks can be "global", where they belong to the whole
collective or personal, so they are only for the user. This
returns both.
security:
- authTokenHeader: []
responses:
422:
description: BadRequest
200:
description: Ok
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/BookmarkedQuery"
post:
operationId: "sec-querybookmark-post"
tags: [Query Bookmarks]
summary: Create a new query bookmark
description: |
Creates a new query bookmark.
A bookmark must have a unique name (within both collective and
personal scope). If a name already exists, a failure is
returned - use PUT instead for changing existing bookmarks.
security:
- authTokenHeader: []
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/BookmarkedQuery"
responses:
422:
description: BadRequest
200:
description: Ok
content:
application/json:
schema:
$ref: "#/components/schemas/BasicResult"
put:
operationId: "sec-querybookmark-put"
tags: [Query Bookmarks]
summary: Change a query bookmark
description: |
Changes an existing query bookmark.
A bookmark must have a unique name within the collective
(considering collective and personal scope). The bookmark is
identified by its id, which must exist.
security:
- authTokenHeader: []
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/BookmarkedQuery"
responses:
422:
description: BadRequest
200:
description: Ok
content:
application/json:
schema:
$ref: "#/components/schemas/BasicResult"
/sec/querybookmark/{bookmarkId}:
delete:
operationId: "sec-querybookmark-delete"
tags: [Query Bookmark]
summary: Delete a bookmark.
description: |
Deletes a bookmarks by its id.
responses:
422:
description: BadRequest
200:
description: Ok
content:
application/json:
schema:
$ref: "#/components/schemas/BasicResult"
/sec/clientSettings/{clientId}: /sec/clientSettings/{clientId}:
parameters: parameters:
@ -5314,6 +5406,32 @@ paths:
components: components:
schemas: schemas:
BookmarkedQuery:
description: |
A query bookmark.
required:
- id
- name
- query
- personal
- created
properties:
id:
type: string
format: ident
name:
type: string
label:
type: string
query:
type: string
format: itemquery
personal:
type: boolean
created:
type: integer
format: date-time
StringValue: StringValue:
description: | description: |
A generic string value A generic string value

View File

@ -172,7 +172,8 @@ object RestServer {
"folder" -> FolderRoutes(restApp.backend, token), "folder" -> FolderRoutes(restApp.backend, token),
"customfield" -> CustomFieldRoutes(restApp.backend, token), "customfield" -> CustomFieldRoutes(restApp.backend, token),
"clientSettings" -> ClientSettingsRoutes(restApp.backend, token), "clientSettings" -> ClientSettingsRoutes(restApp.backend, token),
"notification" -> NotificationRoutes(cfg, restApp.backend, token) "notification" -> NotificationRoutes(cfg, restApp.backend, token),
"querybookmark" -> BookmarkRoutes(restApp.backend, token)
) )
def openRoutes[F[_]: Async]( def openRoutes[F[_]: Async](

View File

@ -0,0 +1,58 @@
package docspell.restserver.routes
import docspell.backend.auth.AuthToken
import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl
import cats.effect.Async
import docspell.backend.ops.OQueryBookmarks
import docspell.restapi.model.BookmarkedQuery
import docspell.backend.BackendApp
import cats.implicits._
import docspell.restserver.conv.Conversions
import docspell.common.Ident
object BookmarkRoutes {
def apply[F[_]: Async](backend: BackendApp[F], token: AuthToken): HttpRoutes[F] = {
val dsl = new Http4sDsl[F] {}
import dsl._
HttpRoutes.of {
case GET -> Root =>
for {
all <- backend.bookmarks.getAll(token.account)
resp <- Ok(all.map(convert.toApi))
} yield resp
case req @ POST -> Root =>
for {
data <- req.as[BookmarkedQuery]
res <- backend.bookmarks.create(token.account, convert.toModel(data))
resp <- Ok(Conversions.basicResult(res, "Bookmark created"))
} yield resp
case req @ PUT -> Root =>
for {
data <- req.as[BookmarkedQuery]
res <- backend.bookmarks.update(token.account, data.id, convert.toModel(data))
resp <- Ok(Conversions.basicResult(res, "Bookmark updated"))
} yield resp
case DELETE -> Root / Ident(id) =>
for {
res <- backend.bookmarks.delete(token.account, id).attempt
resp <- Ok(Conversions.basicResult(res, "Bookmark deleted"))
} yield resp
}
}
object convert {
def toApi(b: OQueryBookmarks.Bookmark): BookmarkedQuery =
BookmarkedQuery(b.id, b.name, b.label, b.query, b.personal, b.created)
def toModel(b: BookmarkedQuery): OQueryBookmarks.NewBookmark =
OQueryBookmarks.NewBookmark(b.name, b.label, b.query, b.personal)
}
}

View File

@ -0,0 +1,12 @@
CREATE TABLE "query_bookmark" (
"id" varchar(254) not null primary key,
"name" varchar(254) not null,
"label" varchar(254),
"user_id" varchar(254),
"cid" varchar(254) not null,
"query" varchar(2000) not null,
"created" timestamp,
foreign key ("user_id") references "user_"("uid") on delete cascade,
foreign key ("cid") references "collective"("cid") on delete cascade,
unique("cid", "user_id", "name")
)

View File

@ -0,0 +1,12 @@
CREATE TABLE `query_bookmark` (
`id` varchar(254) not null primary key,
`name` varchar(254) not null,
`label` varchar(254),
`user_id` varchar(254),
`cid` varchar(254) not null,
`query` varchar(2000) not null,
`created` timestamp,
foreign key (`user_id`) references `user_`(`uid`) on delete cascade,
foreign key (`cid`) references `collective`(`cid`) on delete cascade,
unique(`cid`, `user_id`, `name`)
)

View File

@ -0,0 +1,12 @@
CREATE TABLE "query_bookmark" (
"id" varchar(254) not null primary key,
"name" varchar(254) not null,
"label" varchar(254),
"user_id" varchar(254),
"cid" varchar(254) not null,
"query" varchar(2000) not null,
"created" timestamp,
foreign key ("user_id") references "user_"("uid") on delete cascade,
foreign key ("cid") references "collective"("cid") on delete cascade,
unique("cid", "user_id", "name")
)

View File

@ -0,0 +1,108 @@
package docspell.store.records
import docspell.common._
import docspell.query.ItemQuery
import docspell.store.qb.DSL._
import docspell.store.qb._
import doobie._
import doobie.implicits._
import cats.data.NonEmptyList
import cats.syntax.option._
final case class RQueryBookmark(
id: Ident,
name: String,
label: Option[String],
userId: Option[Ident],
cid: Ident,
query: ItemQuery,
created: Timestamp
) {
def isPersonal: Boolean =
userId.isDefined
def asGlobal: RQueryBookmark =
copy(userId = None)
def asPersonal(userId: Ident): RQueryBookmark =
copy(userId = userId.some)
}
object RQueryBookmark {
final case class Table(alias: Option[String]) extends TableDef {
val tableName = "query_bookmark";
val id = Column[Ident]("id", this)
val name = Column[String]("name", this)
val label = Column[String]("label", this)
val userId = Column[Ident]("user_id", this)
val cid = Column[Ident]("cid", this)
val query = Column[ItemQuery]("query", this)
val created = Column[Timestamp]("created", this)
val all: NonEmptyList[Column[_]] =
NonEmptyList.of(id, name, label, userId, cid, query, created)
}
val T: Table = Table(None)
def as(alias: String): Table = Table(Some(alias))
def createNew(
account: AccountId,
name: String,
label: Option[String],
query: ItemQuery,
personal: Boolean
): ConnectionIO[RQueryBookmark] =
for {
userId <- RUser.getIdByAccount(account)
curTime <- Timestamp.current[ConnectionIO]
id <- Ident.randomId[ConnectionIO]
} yield RQueryBookmark(
id,
name,
label,
if (personal) userId.some else None,
account.collective,
query,
curTime
)
def insert(r: RQueryBookmark): ConnectionIO[Int] =
DML.insert(
T,
T.all,
sql"${r.id},${r.name},${r.label},${r.userId},${r.cid},${r.query},${r.created}"
)
def update(r: RQueryBookmark): ConnectionIO[Int] =
DML.update(
T,
T.id === r.id,
DML.set(
T.name.setTo(r.name),
T.label.setTo(r.label),
T.query.setTo(r.query)
)
)
def deleteById(cid: Ident, id: Ident): ConnectionIO[Int] =
DML.delete(T, T.id === id && T.cid === cid)
def allForUser(account: AccountId): ConnectionIO[Vector[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))
).build.query[RQueryBookmark].to[Vector]
}
}

View File

@ -14,6 +14,8 @@ import docspell.store.qb._
import doobie._ import doobie._
import doobie.implicits._ import doobie.implicits._
import cats.data.OptionT
import cats.effect.Sync
case class RUser( case class RUser(
uid: Ident, uid: Ident,
@ -150,6 +152,13 @@ object RUser {
.query[Ident] .query[Ident]
.option .option
def getIdByAccount(account: AccountId): ConnectionIO[Ident] =
OptionT(findIdByAccount(account)).getOrElseF(
Sync[ConnectionIO].raiseError(
new Exception(s"No user found for: ${account.asString}")
)
)
def updateLogin(accountId: AccountId): ConnectionIO[Int] = { def updateLogin(accountId: AccountId): ConnectionIO[Int] = {
val t = Table(None) val t = Table(None)
def stmt(now: Timestamp) = def stmt(now: Timestamp) =

View File

@ -124,7 +124,6 @@ module Api exposing
, restoreAllItems , restoreAllItems
, restoreItem , restoreItem
, sampleEvent , sampleEvent
, saveBookmarks
, saveClientSettings , saveClientSettings
, searchShare , searchShare
, searchShareStats , searchShareStats
@ -188,6 +187,7 @@ module Api exposing
import Api.Model.AttachmentMeta exposing (AttachmentMeta) import Api.Model.AttachmentMeta exposing (AttachmentMeta)
import Api.Model.AuthResult exposing (AuthResult) import Api.Model.AuthResult exposing (AuthResult)
import Api.Model.BasicResult exposing (BasicResult) import Api.Model.BasicResult exposing (BasicResult)
import Api.Model.BookmarkedQuery exposing (BookmarkedQuery)
import Api.Model.CalEventCheck exposing (CalEventCheck) import Api.Model.CalEventCheck exposing (CalEventCheck)
import Api.Model.CalEventCheckResult exposing (CalEventCheckResult) import Api.Model.CalEventCheckResult exposing (CalEventCheckResult)
import Api.Model.Collective exposing (Collective) import Api.Model.Collective exposing (Collective)
@ -266,7 +266,7 @@ import Api.Model.User exposing (User)
import Api.Model.UserList exposing (UserList) import Api.Model.UserList exposing (UserList)
import Api.Model.UserPass exposing (UserPass) import Api.Model.UserPass exposing (UserPass)
import Api.Model.VersionInfo exposing (VersionInfo) import Api.Model.VersionInfo exposing (VersionInfo)
import Data.BookmarkedQuery exposing (AllBookmarks, BookmarkedQuery, BookmarkedQueryDef, Bookmarks) import Data.Bookmarks exposing (AllBookmarks, Bookmarks)
import Data.ContactType exposing (ContactType) import Data.ContactType exposing (ContactType)
import Data.CustomFieldOrder exposing (CustomFieldOrder) import Data.CustomFieldOrder exposing (CustomFieldOrder)
import Data.EquipmentOrder exposing (EquipmentOrder) import Data.EquipmentOrder exposing (EquipmentOrder)
@ -2295,46 +2295,29 @@ saveClientSettings flags settings receive =
--- Query Bookmarks --- Query Bookmarks
type alias BookmarkLocation = bookmarkUri : Flags -> String
Data.BookmarkedQuery.Location bookmarkUri flags =
flags.config.baseUrl ++ "/api/v1/sec/querybookmark"
bookmarkLocationUri : Flags -> BookmarkLocation -> String getBookmarksTask : Flags -> Task.Task Http.Error Bookmarks
bookmarkLocationUri flags loc = getBookmarksTask flags =
case loc of
Data.BookmarkedQuery.User ->
flags.config.baseUrl ++ "/api/v1/sec/clientSettings/user/webClientBookmarks"
Data.BookmarkedQuery.Collective ->
flags.config.baseUrl ++ "/api/v1/sec/clientSettings/collective/webClientBookmarks"
getBookmarksTask : Flags -> BookmarkLocation -> Task.Task Http.Error Bookmarks
getBookmarksTask flags loc =
Http2.authTask Http2.authTask
{ method = "GET" { method = "GET"
, url = bookmarkLocationUri flags loc , url = bookmarkUri flags
, account = getAccount flags , account = getAccount flags
, body = Http.emptyBody , body = Http.emptyBody
, resolver = Http2.jsonResolver Data.BookmarkedQuery.bookmarksDecoder , resolver = Http2.jsonResolver Data.Bookmarks.bookmarksDecoder
, headers = [] , headers = []
, timeout = Nothing , timeout = Nothing
} }
getBookmarksFor : Flags -> BookmarkLocation -> (Result Http.Error Bookmarks -> msg) -> Cmd msg
getBookmarksFor flags loc receive =
Task.attempt receive (getBookmarksTask flags loc)
getBookmarks : Flags -> (Result Http.Error AllBookmarks -> msg) -> Cmd msg getBookmarks : Flags -> (Result Http.Error AllBookmarks -> msg) -> Cmd msg
getBookmarks flags receive = getBookmarks flags receive =
let let
coll = bms =
getBookmarksTask flags Data.BookmarkedQuery.Collective getBookmarksTask flags
user =
getBookmarksTask flags Data.BookmarkedQuery.User
shares = shares =
getSharesTask flags "" False getSharesTask flags "" False
@ -2342,86 +2325,57 @@ getBookmarks flags receive =
activeShare s = activeShare s =
s.enabled && s.name /= Nothing s.enabled && s.name /= Nothing
combine bc bu bs = combine bm bs =
AllBookmarks bc bu (List.filter activeShare bs.items) AllBookmarks (Data.Bookmarks.sort bm) (List.filter activeShare bs.items)
in in
Task.map3 combine coll user shares Task.map2 combine bms shares
|> Task.attempt receive |> Task.attempt receive
saveBookmarksTask : Flags -> BookmarkLocation -> Bookmarks -> Task.Task Http.Error BasicResult addBookmark : Flags -> BookmarkedQuery -> (Result Http.Error BasicResult -> msg) -> Cmd msg
saveBookmarksTask flags loc bookmarks = addBookmark flags model receive =
Http2.authTask Http2.authPost
{ method = "PUT" { url = bookmarkUri flags
, url = bookmarkLocationUri flags loc
, account = getAccount flags , account = getAccount flags
, body = Http.jsonBody (Data.BookmarkedQuery.bookmarksEncode bookmarks) , body = Http.jsonBody (Api.Model.BookmarkedQuery.encode model)
, resolver = Http2.jsonResolver Api.Model.BasicResult.decoder , expect = Http.expectJson receive Api.Model.BasicResult.decoder
, headers = []
, timeout = Nothing
} }
saveBookmarks : Flags -> Bookmarks -> BookmarkLocation -> (Result Http.Error BasicResult -> msg) -> Cmd msg updateBookmark : Flags -> BookmarkedQuery -> (Result Http.Error BasicResult -> msg) -> Cmd msg
saveBookmarks flags bookmarks loc receive = updateBookmark flags model receive =
Task.attempt receive (saveBookmarksTask flags loc bookmarks) Http2.authPut
{ url = bookmarkUri flags
, account = getAccount flags
, body = Http.jsonBody (Api.Model.BookmarkedQuery.encode model)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
addBookmark : Flags -> BookmarkedQueryDef -> (Result Http.Error BasicResult -> msg) -> Cmd msg bookmarkNameExistsTask : Flags -> String -> Task.Task Http.Error Bool
addBookmark flags model receive = bookmarkNameExistsTask flags name =
let let
load = load =
getBookmarksTask flags model.location getBookmarksTask flags
add current =
Data.BookmarkedQuery.add model.query current
|> saveBookmarksTask flags model.location
in
Task.andThen add load |> Task.attempt receive
updateBookmark : Flags -> String -> BookmarkedQueryDef -> (Result Http.Error BasicResult -> msg) -> Cmd msg
updateBookmark flags oldName model receive =
let
load =
getBookmarksTask flags model.location
add current =
Data.BookmarkedQuery.remove oldName current
|> Data.BookmarkedQuery.add model.query
|> saveBookmarksTask flags model.location
in
Task.andThen add load |> Task.attempt receive
bookmarkNameExistsTask : Flags -> BookmarkLocation -> String -> Task.Task Http.Error Bool
bookmarkNameExistsTask flags loc name =
let
load =
getBookmarksTask flags loc
exists current = exists current =
Data.BookmarkedQuery.exists name current Data.Bookmarks.exists name current
in in
Task.map exists load Task.map exists load
bookmarkNameExists : Flags -> BookmarkLocation -> String -> (Result Http.Error Bool -> msg) -> Cmd msg bookmarkNameExists : Flags -> String -> (Result Http.Error Bool -> msg) -> Cmd msg
bookmarkNameExists flags loc name receive = bookmarkNameExists flags name receive =
bookmarkNameExistsTask flags loc name |> Task.attempt receive bookmarkNameExistsTask flags name |> Task.attempt receive
deleteBookmark : Flags -> BookmarkLocation -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg deleteBookmark : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
deleteBookmark flags loc name receive = deleteBookmark flags id receive =
let Http2.authDelete
load = { url = bookmarkUri flags ++ "/" ++ id
getBookmarksTask flags loc , account = getAccount flags
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
remove current = }
Data.BookmarkedQuery.remove name current
|> saveBookmarksTask flags loc
in
Task.andThen remove load |> Task.attempt receive

View File

@ -11,8 +11,9 @@ module Comp.BookmarkChooser exposing
, view , view
) )
import Api.Model.BookmarkedQuery exposing (BookmarkedQuery)
import Api.Model.ShareDetail exposing (ShareDetail) import Api.Model.ShareDetail exposing (ShareDetail)
import Data.BookmarkedQuery exposing (AllBookmarks, BookmarkedQuery) import Data.Bookmarks exposing (AllBookmarks)
import Data.Icons as Icons import Data.Icons as Icons
import Html exposing (Html, a, div, i, label, span, text) import Html exposing (Html, a, div, i, label, span, text)
import Html.Attributes exposing (class, classList, href) import Html.Attributes exposing (class, classList, href)
@ -34,19 +35,18 @@ init all =
isEmpty : Model -> Bool isEmpty : Model -> Bool
isEmpty model = isEmpty model =
model.all == Data.BookmarkedQuery.allBookmarksEmpty model.all == Data.Bookmarks.empty
type alias Selection = type alias Selection =
{ user : Set String { bookmarks : Set String
, collective : Set String
, shares : Set String , shares : Set String
} }
emptySelection : Selection emptySelection : Selection
emptySelection = emptySelection =
{ user = Set.empty, collective = Set.empty, shares = Set.empty } { bookmarks = Set.empty, shares = Set.empty }
isEmptySelection : Selection -> Bool isEmptySelection : Selection -> Bool
@ -55,8 +55,7 @@ isEmptySelection sel =
type Kind type Kind
= User = Bookmark
| Collective
| Share | Share
@ -68,14 +67,13 @@ getQueries : Model -> Selection -> List BookmarkedQuery
getQueries model sel = getQueries model sel =
let let
member set bm = member set bm =
Set.member bm.name set Set.member bm.id set
filterBookmarks f bms = filterBookmarks f bms =
Data.BookmarkedQuery.filter f bms |> Data.BookmarkedQuery.map identity List.filter f bms |> List.map identity
in in
List.concat List.concat
[ filterBookmarks (member sel.user) model.all.user [ filterBookmarks (member sel.bookmarks) model.all.bookmarks
, filterBookmarks (member sel.collective) model.all.collective
, List.map shareToBookmark model.all.shares , List.map shareToBookmark model.all.shares
|> List.filter (member sel.shares) |> List.filter (member sel.shares)
] ]
@ -96,16 +94,13 @@ update msg model current =
Set.insert name set Set.insert name set
in in
case msg of case msg of
Toggle kind name -> Toggle kind id ->
case kind of case kind of
User -> Bookmark ->
( model, { current | user = toggle name current.user } ) ( model, { current | bookmarks = toggle id current.bookmarks } )
Collective ->
( model, { current | collective = toggle name current.collective } )
Share -> Share ->
( model, { current | shares = toggle name current.shares } ) ( model, { current | shares = toggle id current.shares } )
@ -114,9 +109,13 @@ update msg model current =
view : Texts -> Model -> Selection -> Html Msg view : Texts -> Model -> Selection -> Html Msg
view texts model selection = view texts model selection =
let
( user, coll ) =
List.partition .personal model.all.bookmarks
in
div [ class "flex flex-col" ] div [ class "flex flex-col" ]
[ userBookmarks texts model selection [ userBookmarks texts user selection
, collBookmarks texts model selection , collBookmarks texts coll selection
, shares texts model selection , shares texts model selection
] ]
@ -130,27 +129,27 @@ titleDiv label =
] ]
userBookmarks : Texts -> Model -> Selection -> Html Msg userBookmarks : Texts -> List BookmarkedQuery -> Selection -> Html Msg
userBookmarks texts model sel = userBookmarks texts model sel =
div div
[ class "mb-2" [ class "mb-2"
, classList [ ( "hidden", Data.BookmarkedQuery.emptyBookmarks == model.all.user ) ] , classList [ ( "hidden", model == [] ) ]
] ]
[ titleDiv texts.userLabel [ titleDiv texts.userLabel
, div [ class "flex flex-col space-y-2 md:space-y-1" ] , div [ class "flex flex-col space-y-2 md:space-y-1" ]
(Data.BookmarkedQuery.map (mkItem "fa fa-bookmark" sel User) model.all.user) (List.map (mkItem "fa fa-bookmark" sel Bookmark) model)
] ]
collBookmarks : Texts -> Model -> Selection -> Html Msg collBookmarks : Texts -> List BookmarkedQuery -> Selection -> Html Msg
collBookmarks texts model sel = collBookmarks texts model sel =
div div
[ class "mb-2" [ class "mb-2"
, classList [ ( "hidden", Data.BookmarkedQuery.emptyBookmarks == model.all.collective ) ] , classList [ ( "hidden", [] == model ) ]
] ]
[ titleDiv texts.collectiveLabel [ titleDiv texts.collectiveLabel
, div [ class "flex flex-col space-y-2 md:space-y-1" ] , div [ class "flex flex-col space-y-2 md:space-y-1" ]
(Data.BookmarkedQuery.map (mkItem "fa fa-bookmark font-light" sel Collective) model.all.collective) (List.map (mkItem "fa fa-bookmark font-light" sel Bookmark) model)
] ]
@ -175,9 +174,9 @@ mkItem icon sel kind bm =
a a
[ class "flex flex-row items-center rounded px-1 py-1 hover:bg-blue-100 dark:hover:bg-slate-600" [ class "flex flex-row items-center rounded px-1 py-1 hover:bg-blue-100 dark:hover:bg-slate-600"
, href "#" , href "#"
, onClick (Toggle kind bm.name) , onClick (Toggle kind bm.id)
] ]
[ if isSelected sel kind bm.name then [ if isSelected sel kind bm.id then
i [ class "fa fa-check" ] [] i [ class "fa fa-check" ] []
else else
@ -187,14 +186,11 @@ mkItem icon sel kind bm =
isSelected : Selection -> Kind -> String -> Bool isSelected : Selection -> Kind -> String -> Bool
isSelected sel kind name = isSelected sel kind id =
Set.member name <| Set.member id <|
case kind of case kind of
User -> Bookmark ->
sel.user sel.bookmarks
Collective ->
sel.collective
Share -> Share ->
sel.shares sel.shares
@ -202,4 +198,4 @@ isSelected sel kind name =
shareToBookmark : ShareDetail -> BookmarkedQuery shareToBookmark : ShareDetail -> BookmarkedQuery
shareToBookmark share = shareToBookmark share =
BookmarkedQuery (Maybe.withDefault "-" share.name) share.query BookmarkedQuery share.id (Maybe.withDefault "-" share.name) share.name share.query False 0

View File

@ -14,7 +14,7 @@ import Comp.BookmarkQueryForm
import Comp.BookmarkTable import Comp.BookmarkTable
import Comp.ItemDetail.Model exposing (Msg(..)) import Comp.ItemDetail.Model exposing (Msg(..))
import Comp.MenuBar as MB import Comp.MenuBar as MB
import Data.BookmarkedQuery exposing (AllBookmarks) import Data.Bookmarks exposing (AllBookmarks)
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings) import Data.UiSettings exposing (UiSettings)
import Html exposing (..) import Html exposing (..)
@ -43,16 +43,10 @@ type DeleteConfirm
| DeleteConfirmOn | DeleteConfirmOn
type alias FormData =
{ model : Comp.BookmarkQueryForm.Model
, oldName : Maybe String
}
type alias Model = type alias Model =
{ viewMode : ViewMode { viewMode : ViewMode
, bookmarks : AllBookmarks , bookmarks : AllBookmarks
, formData : FormData , formModel : Comp.BookmarkQueryForm.Model
, loading : Bool , loading : Bool
, formError : FormError , formError : FormError
, deleteConfirm : DeleteConfirm , deleteConfirm : DeleteConfirm
@ -66,11 +60,8 @@ init flags =
Comp.BookmarkQueryForm.init Comp.BookmarkQueryForm.init
in in
( { viewMode = Table ( { viewMode = Table
, bookmarks = Data.BookmarkedQuery.allBookmarksEmpty , bookmarks = Data.Bookmarks.empty
, formData = , formModel = fm
{ model = fm
, oldName = Nothing
}
, loading = False , loading = False
, formError = FormErrorNone , formError = FormErrorNone
, deleteConfirm = DeleteConfirmOff , deleteConfirm = DeleteConfirmOff
@ -84,14 +75,14 @@ init flags =
type Msg type Msg
= LoadBookmarks = LoadBookmarks
| TableMsg Data.BookmarkedQuery.Location Comp.BookmarkTable.Msg | TableMsg Comp.BookmarkTable.Msg
| FormMsg Comp.BookmarkQueryForm.Msg | FormMsg Comp.BookmarkQueryForm.Msg
| InitNewBookmark | InitNewBookmark
| SetViewMode ViewMode | SetViewMode ViewMode
| Submit | Submit
| RequestDelete | RequestDelete
| CancelDelete | CancelDelete
| DeleteBookmarkNow Data.BookmarkedQuery.Location String | DeleteBookmarkNow String
| LoadBookmarksResp (Result Http.Error AllBookmarks) | LoadBookmarksResp (Result Http.Error AllBookmarks)
| AddBookmarkResp (Result Http.Error BasicResult) | AddBookmarkResp (Result Http.Error BasicResult)
| UpdateBookmarkResp (Result Http.Error BasicResult) | UpdateBookmarkResp (Result Http.Error BasicResult)
@ -119,8 +110,7 @@ update flags msg model =
{ model { model
| viewMode = Form | viewMode = Form
, formError = FormErrorNone , formError = FormErrorNone
, formData = , formModel = bm
{ model = bm, oldName = Nothing }
} }
in in
( nm, Cmd.map FormMsg bc, Sub.none ) ( nm, Cmd.map FormMsg bc, Sub.none )
@ -138,14 +128,14 @@ update flags msg model =
FormMsg lm -> FormMsg lm ->
let let
( fm, fc, fs ) = ( fm, fc, fs ) =
Comp.BookmarkQueryForm.update flags lm model.formData.model Comp.BookmarkQueryForm.update flags lm model.formModel
in in
( { model | formData = { model = fm, oldName = model.formData.oldName }, formError = FormErrorNone } ( { model | formModel = fm, formError = FormErrorNone }
, Cmd.map FormMsg fc , Cmd.map FormMsg fc
, Sub.map FormMsg fs , Sub.map FormMsg fs
) )
TableMsg loc lm -> TableMsg lm ->
let let
action = action =
Comp.BookmarkTable.update lm Comp.BookmarkTable.update lm
@ -154,15 +144,12 @@ update flags msg model =
Comp.BookmarkTable.Edit bookmark -> Comp.BookmarkTable.Edit bookmark ->
let let
( bm, bc ) = ( bm, bc ) =
Comp.BookmarkQueryForm.initWith Comp.BookmarkQueryForm.initWith bookmark
{ query = bookmark
, location = loc
}
in in
( { model ( { model
| viewMode = Form | viewMode = Form
, formError = FormErrorNone , formError = FormErrorNone
, formData = { model = bm, oldName = Just bookmark.name } , formModel = bm
} }
, Cmd.map FormMsg bc , Cmd.map FormMsg bc
, Sub.none , Sub.none
@ -174,9 +161,9 @@ update flags msg model =
CancelDelete -> CancelDelete ->
( { model | deleteConfirm = DeleteConfirmOff }, Cmd.none, Sub.none ) ( { model | deleteConfirm = DeleteConfirmOff }, Cmd.none, Sub.none )
DeleteBookmarkNow loc name -> DeleteBookmarkNow id ->
( { model | deleteConfirm = DeleteConfirmOff, loading = True } ( { model | deleteConfirm = DeleteConfirmOff, loading = True }
, Api.deleteBookmark flags loc name DeleteBookmarkResp , Api.deleteBookmark flags id DeleteBookmarkResp
, Sub.none , Sub.none
) )
@ -196,14 +183,13 @@ update flags msg model =
( { model | loading = False, formError = FormErrorHttp err }, Cmd.none, Sub.none ) ( { model | loading = False, formError = FormErrorHttp err }, Cmd.none, Sub.none )
Submit -> Submit ->
case Comp.BookmarkQueryForm.get model.formData.model of case Comp.BookmarkQueryForm.get model.formModel of
Just data -> Just data ->
case model.formData.oldName of if data.id /= "" then
Just prevName -> ( { model | loading = True }, Api.updateBookmark flags data AddBookmarkResp, Sub.none )
( { model | loading = True }, Api.updateBookmark flags prevName data AddBookmarkResp, Sub.none )
Nothing -> else
( { model | loading = True }, Api.addBookmark flags data AddBookmarkResp, Sub.none ) ( { model | loading = True }, Api.addBookmark flags data AddBookmarkResp, Sub.none )
Nothing -> Nothing ->
( { model | formError = FormErrorInvalid }, Cmd.none, Sub.none ) ( { model | formError = FormErrorInvalid }, Cmd.none, Sub.none )
@ -254,6 +240,10 @@ view texts settings flags model =
viewTable : Texts -> Model -> Html Msg viewTable : Texts -> Model -> Html Msg
viewTable texts model = viewTable texts model =
let
( user, coll ) =
List.partition .personal model.bookmarks.bookmarks
in
div [ class "flex flex-col" ] div [ class "flex flex-col" ]
[ MB.view [ MB.view
{ start = { start =
@ -268,17 +258,23 @@ viewTable texts model =
] ]
, rootClasses = "mb-4" , rootClasses = "mb-4"
} }
, div [ class "flex flex-col" ] , div
[ class "flex flex-col"
, classList [ ( "hidden", user == [] ) ]
]
[ h3 [ class S.header3 ] [ h3 [ class S.header3 ]
[ text texts.userBookmarks ] [ text texts.userBookmarks ]
, Html.map (TableMsg Data.BookmarkedQuery.User) , Html.map TableMsg
(Comp.BookmarkTable.view texts.bookmarkTable model.bookmarks.user) (Comp.BookmarkTable.view texts.bookmarkTable user)
]
, div
[ class "flex flex-col mt-3"
, classList [ ( "hidden", coll == [] ) ]
] ]
, div [ class "flex flex-col mt-3" ]
[ h3 [ class S.header3 ] [ h3 [ class S.header3 ]
[ text texts.collectiveBookmarks ] [ text texts.collectiveBookmarks ]
, Html.map (TableMsg Data.BookmarkedQuery.Collective) , Html.map TableMsg
(Comp.BookmarkTable.view texts.bookmarkTable model.bookmarks.collective) (Comp.BookmarkTable.view texts.bookmarkTable coll)
] ]
, B.loadingDimmer , B.loadingDimmer
{ label = "" { label = ""
@ -291,10 +287,10 @@ viewForm : Texts -> UiSettings -> Flags -> Model -> Html Msg
viewForm texts _ _ model = viewForm texts _ _ model =
let let
newBookmark = newBookmark =
model.formData.oldName == Nothing model.formModel.bookmark.id == ""
isValid = isValid =
Comp.BookmarkQueryForm.get model.formData.model /= Nothing Comp.BookmarkQueryForm.get model.formModel /= Nothing
in in
div [] div []
[ Html.form [] [ Html.form []
@ -305,7 +301,7 @@ viewForm texts _ _ model =
else else
h1 [ class S.header2 ] h1 [ class S.header2 ]
[ text (Maybe.withDefault "" model.formData.model.name) [ text (Maybe.withDefault "" model.formModel.name)
] ]
, MB.view , MB.view
{ start = { start =
@ -360,7 +356,7 @@ viewForm texts _ _ model =
text m text m
] ]
, div [] , div []
[ Html.map FormMsg (Comp.BookmarkQueryForm.view texts.bookmarkForm model.formData.model) [ Html.map FormMsg (Comp.BookmarkQueryForm.view texts.bookmarkForm model.formModel)
] ]
, B.loadingDimmer , B.loadingDimmer
{ active = model.loading { active = model.loading
@ -378,11 +374,7 @@ viewForm texts _ _ model =
{ label = texts.basics.yes { label = texts.basics.yes
, icon = "fa fa-check" , icon = "fa fa-check"
, disabled = False , disabled = False
, handler = , handler = onClick (DeleteBookmarkNow model.formModel.bookmark.id)
onClick
(DeleteBookmarkNow model.formData.model.location
(Maybe.withDefault "" model.formData.model.name)
)
, attrs = [ href "#" ] , attrs = [ href "#" ]
} }
, B.secondaryButton , B.secondaryButton

View File

@ -8,9 +8,9 @@
module Comp.BookmarkQueryForm exposing (Model, Msg, get, init, initQuery, initWith, update, view) module Comp.BookmarkQueryForm exposing (Model, Msg, get, init, initQuery, initWith, update, view)
import Api import Api
import Api.Model.BookmarkedQuery exposing (BookmarkedQuery)
import Comp.Basic as B import Comp.Basic as B
import Comp.PowerSearchInput import Comp.PowerSearchInput
import Data.BookmarkedQuery exposing (BookmarkedQueryDef, Location(..))
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
@ -24,10 +24,11 @@ import Util.Maybe
type alias Model = type alias Model =
{ name : Maybe String { bookmark : BookmarkedQuery
, name : Maybe String
, nameExists : Bool , nameExists : Bool
, queryModel : Comp.PowerSearchInput.Model , queryModel : Comp.PowerSearchInput.Model
, location : Location , isPersonal : Bool
, nameExistsThrottle : Throttle Msg , nameExistsThrottle : Throttle Msg
} }
@ -40,10 +41,11 @@ initQuery q =
(Comp.PowerSearchInput.setSearchString q) (Comp.PowerSearchInput.setSearchString q)
Comp.PowerSearchInput.init Comp.PowerSearchInput.init
in in
( { name = Nothing ( { bookmark = Api.Model.BookmarkedQuery.empty
, name = Nothing
, nameExists = False , nameExists = False
, queryModel = res.model , queryModel = res.model
, location = User , isPersonal = True
, nameExistsThrottle = Throttle.create 1 , nameExistsThrottle = Throttle.create 1
} }
, Cmd.batch , Cmd.batch
@ -57,15 +59,16 @@ init =
initQuery "" initQuery ""
initWith : BookmarkedQueryDef -> ( Model, Cmd Msg ) initWith : BookmarkedQuery -> ( Model, Cmd Msg )
initWith bm = initWith bm =
let let
( m, c ) = ( m, c ) =
initQuery bm.query.query initQuery bm.query
in in
( { m ( { m
| name = Just bm.query.name | name = Just bm.name
, location = bm.location , isPersonal = bm.personal
, bookmark = bm
} }
, c , c
) )
@ -78,19 +81,21 @@ isValid model =
/= Nothing /= Nothing
get : Model -> Maybe BookmarkedQueryDef get : Model -> Maybe BookmarkedQuery
get model = get model =
let let
qStr = qStr =
Maybe.withDefault "" model.queryModel.input Maybe.withDefault "" model.queryModel.input
bm =
model.bookmark
in in
if isValid model then if isValid model then
Just Just
{ query = { bm
{ query = qStr | query = qStr
, name = Maybe.withDefault "" model.name , name = Maybe.withDefault "" model.name
} , personal = model.isPersonal
, location = model.location
} }
else else
@ -100,7 +105,7 @@ get model =
type Msg type Msg
= SetName String = SetName String
| QueryMsg Comp.PowerSearchInput.Msg | QueryMsg Comp.PowerSearchInput.Msg
| SetLocation Location | SetPersonal Bool
| NameExistsResp (Result Http.Error Bool) | NameExistsResp (Result Http.Error Bool)
| UpdateThrottle | UpdateThrottle
@ -109,12 +114,12 @@ update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
update flags msg model = update flags msg model =
let let
nameCheck1 name = nameCheck1 name =
Api.bookmarkNameExists flags model.location name NameExistsResp Api.bookmarkNameExists flags name NameExistsResp
nameCheck2 loc = nameCheck2 =
case model.name of case model.name of
Just n -> Just n ->
Api.bookmarkNameExists flags loc n NameExistsResp Api.bookmarkNameExists flags n NameExistsResp
Nothing -> Nothing ->
Cmd.none Cmd.none
@ -135,12 +140,12 @@ update flags msg model =
, throttleSub , throttleSub
) )
SetLocation loc -> SetPersonal flag ->
let let
( newThrottle, cmd ) = ( newThrottle, cmd ) =
Throttle.try (nameCheck2 loc) model.nameExistsThrottle Throttle.try nameCheck2 model.nameExistsThrottle
in in
( { model | location = loc, nameExistsThrottle = newThrottle }, cmd, throttleSub ) ( { model | isPersonal = flag, nameExistsThrottle = newThrottle }, cmd, throttleSub )
QueryMsg lm -> QueryMsg lm ->
let let
@ -224,8 +229,8 @@ view texts model =
[ label [ class "inline-flex items-center" ] [ label [ class "inline-flex items-center" ]
[ input [ input
[ type_ "radio" [ type_ "radio"
, checked (model.location == User) , checked model.isPersonal
, onCheck (\_ -> SetLocation User) , onCheck (\_ -> SetPersonal True)
, class S.radioInput , class S.radioInput
] ]
[] []
@ -235,9 +240,9 @@ view texts model =
, label [ class "inline-flex items-center" ] , label [ class "inline-flex items-center" ]
[ input [ input
[ type_ "radio" [ type_ "radio"
, checked (model.location == Collective) , checked (not model.isPersonal)
, class S.radioInput , class S.radioInput
, onCheck (\_ -> SetLocation Collective) , onCheck (\_ -> SetPersonal False)
] ]
[] []
, span [ class "ml-2" ] [ text texts.collectiveLocation ] , span [ class "ml-2" ] [ text texts.collectiveLocation ]

View File

@ -2,12 +2,12 @@ module Comp.BookmarkQueryManage exposing (..)
import Api import Api
import Api.Model.BasicResult exposing (BasicResult) import Api.Model.BasicResult exposing (BasicResult)
import Api.Model.BookmarkedQuery exposing (BookmarkedQuery)
import Comp.Basic as B import Comp.Basic as B
import Comp.BookmarkQueryForm import Comp.BookmarkQueryForm
import Data.BookmarkedQuery exposing (BookmarkedQueryDef)
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Html exposing (Html, div, text) import Html exposing (Html, div, text)
import Html.Attributes exposing (class, classList, href) import Html.Attributes exposing (class, href)
import Html.Events exposing (onClick) import Html.Events exposing (onClick)
import Http import Http
import Messages.Comp.BookmarkQueryManage exposing (Texts) import Messages.Comp.BookmarkQueryManage exposing (Texts)
@ -55,7 +55,7 @@ type Msg
type FormResult type FormResult
= Submitted BookmarkedQueryDef = Submitted BookmarkedQuery
| Cancelled | Cancelled
| Done | Done
| None | None
@ -117,7 +117,7 @@ update flags msg model =
{ empty | model = { model | loading = False, formState = FormStateError err } } { empty | model = { model | loading = False, formState = FormStateError err } }
save : Flags -> BookmarkedQueryDef -> Cmd Msg save : Flags -> BookmarkedQuery -> Cmd Msg
save flags model = save flags model =
Api.addBookmark flags model SaveResp Api.addBookmark flags model SaveResp

View File

@ -12,8 +12,8 @@ module Comp.BookmarkTable exposing
, view , view
) )
import Api.Model.BookmarkedQuery exposing (BookmarkedQuery)
import Comp.Basic as B import Comp.Basic as B
import Data.BookmarkedQuery exposing (BookmarkedQuery, Bookmarks)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Messages.Comp.BookmarkTable exposing (Texts) import Messages.Comp.BookmarkTable exposing (Texts)
@ -39,7 +39,7 @@ update msg =
--- View --- View
view : Texts -> Bookmarks -> Html Msg view : Texts -> List BookmarkedQuery -> Html Msg
view texts bms = view texts bms =
table [ class S.tableMain ] table [ class S.tableMain ]
[ thead [] [ thead []
@ -51,7 +51,7 @@ view texts bms =
] ]
] ]
, tbody [] , tbody []
(Data.BookmarkedQuery.map (renderBookmarkLine texts) bms) (List.map (renderBookmarkLine texts) bms)
] ]

View File

@ -43,7 +43,7 @@ import Comp.LinkTarget exposing (LinkTarget)
import Comp.MenuBar as MB import Comp.MenuBar as MB
import Comp.Tabs import Comp.Tabs
import Comp.TagSelect import Comp.TagSelect
import Data.BookmarkedQuery exposing (AllBookmarks) import Data.Bookmarks exposing (AllBookmarks)
import Data.CustomFieldChange exposing (CustomFieldValueCollect) import Data.CustomFieldChange exposing (CustomFieldValueCollect)
import Data.Direction exposing (Direction) import Data.Direction exposing (Direction)
import Data.DropdownStyle as DS import Data.DropdownStyle as DS
@ -146,7 +146,7 @@ init flags =
, customFieldModel = Comp.CustomFieldMultiInput.initWith [] , customFieldModel = Comp.CustomFieldMultiInput.initWith []
, customValues = Data.CustomFieldChange.emptyCollect , customValues = Data.CustomFieldChange.emptyCollect
, sourceModel = Nothing , sourceModel = Nothing
, allBookmarks = Comp.BookmarkChooser.init Data.BookmarkedQuery.allBookmarksEmpty , allBookmarks = Comp.BookmarkChooser.init Data.Bookmarks.empty
, selectedBookmarks = Comp.BookmarkChooser.emptySelection , selectedBookmarks = Comp.BookmarkChooser.emptySelection
, openTabs = Set.fromList [ "Tags", "Inbox" ] , openTabs = Set.fromList [ "Tags", "Inbox" ]
, searchMode = Data.SearchMode.Normal , searchMode = Data.SearchMode.Normal

View File

@ -1,138 +0,0 @@
module Data.BookmarkedQuery exposing
( AllBookmarks
, BookmarkedQuery
, BookmarkedQueryDef
, Bookmarks
, Location(..)
, add
, allBookmarksEmpty
, bookmarksDecoder
, bookmarksEncode
, emptyBookmarks
, exists
, filter
, map
, remove
)
import Api.Model.ShareDetail exposing (ShareDetail)
import Json.Decode as D
import Json.Encode as E
type Location
= User
| Collective
type alias BookmarkedQuery =
{ name : String
, query : String
}
bookmarkedQueryDecoder : D.Decoder BookmarkedQuery
bookmarkedQueryDecoder =
D.map2 BookmarkedQuery
(D.field "name" D.string)
(D.field "query" D.string)
bookmarkedQueryEncode : BookmarkedQuery -> E.Value
bookmarkedQueryEncode bq =
E.object
[ ( "name", E.string bq.name )
, ( "query", E.string bq.query )
]
type alias BookmarkedQueryDef =
{ query : BookmarkedQuery
, location : Location
}
type Bookmarks
= Bookmarks (List BookmarkedQuery)
map : (BookmarkedQuery -> a) -> Bookmarks -> List a
map f bms =
case bms of
Bookmarks items ->
List.map f items
filter : (BookmarkedQuery -> Bool) -> Bookmarks -> Bookmarks
filter f bms =
case bms of
Bookmarks items ->
Bookmarks <| List.filter f items
emptyBookmarks : Bookmarks
emptyBookmarks =
Bookmarks []
type alias AllBookmarks =
{ collective : Bookmarks
, user : Bookmarks
, shares : List ShareDetail
}
allBookmarksEmpty : AllBookmarks
allBookmarksEmpty =
AllBookmarks emptyBookmarks emptyBookmarks []
{-| Checks wether a bookmark of this name already exists.
-}
exists : String -> Bookmarks -> Bool
exists name bookmarks =
case bookmarks of
Bookmarks list ->
List.any (\b -> b.name == name) list
remove : String -> Bookmarks -> Bookmarks
remove name bookmarks =
case bookmarks of
Bookmarks list ->
Bookmarks <| List.filter (\b -> b.name /= name) list
sortByName : Bookmarks -> Bookmarks
sortByName bm =
case bm of
Bookmarks all ->
Bookmarks <| List.sortBy .name all
add : BookmarkedQuery -> Bookmarks -> Bookmarks
add query bookmarks =
case remove query.name bookmarks of
Bookmarks all ->
sortByName (Bookmarks (query :: all))
bookmarksDecoder : D.Decoder Bookmarks
bookmarksDecoder =
D.maybe
(D.field "bookmarks"
(D.list bookmarkedQueryDecoder
|> D.map Bookmarks
|> D.map sortByName
)
)
|> D.map (Maybe.withDefault emptyBookmarks)
bookmarksEncode : Bookmarks -> E.Value
bookmarksEncode bookmarks =
case bookmarks of
Bookmarks all ->
E.object
[ ( "bookmarks", E.list bookmarkedQueryEncode all )
]

View File

@ -0,0 +1,48 @@
module Data.Bookmarks exposing
( AllBookmarks
, Bookmarks
, bookmarksDecoder
, empty
, exists
, sort
)
import Api.Model.BookmarkedQuery exposing (BookmarkedQuery)
import Api.Model.ShareDetail exposing (ShareDetail)
import Json.Decode as D
type alias AllBookmarks =
{ bookmarks : List BookmarkedQuery
, shares : List ShareDetail
}
empty : AllBookmarks
empty =
AllBookmarks [] []
type alias Bookmarks =
List BookmarkedQuery
{-| Checks wether a bookmark of this name already exists.
-}
exists : String -> Bookmarks -> Bool
exists name bookmarks =
List.any (\b -> b.name == name) bookmarks
sort : Bookmarks -> Bookmarks
sort bms =
let
labelName b =
Maybe.withDefault b.name b.label
in
List.sortBy labelName bms
bookmarksDecoder : D.Decoder Bookmarks
bookmarksDecoder =
D.list Api.Model.BookmarkedQuery.decoder