From 9415f72ec0bec578f12a04befac20f9374c26350 Mon Sep 17 00:00:00 2001 From: eikek Date: Sun, 9 Jan 2022 23:50:34 +0100 Subject: [PATCH] Instead of client only, make bookmarks a server aware feature Makes it much more useful --- .../scala/docspell/backend/BackendApp.scala | 3 + .../backend/ops/OQueryBookmarks.scala | 83 +++++++++++ .../src/main/resources/docspell-openapi.yml | 118 +++++++++++++++ .../docspell/restserver/RestServer.scala | 3 +- .../restserver/routes/BookmarkRoutes.scala | 58 ++++++++ .../migration/h2/V1.31.0__query_bookmark.sql | 12 ++ .../mariadb/V1.31.0__query_bookmark.sql | 12 ++ .../postgresql/V1.31.0__query_bookmark.sql | 12 ++ .../store/records/RQueryBookmark.scala | 108 ++++++++++++++ .../scala/docspell/store/records/RUser.scala | 9 ++ modules/webapp/src/main/elm/Api.elm | 130 ++++++----------- .../src/main/elm/Comp/BookmarkChooser.elm | 68 ++++----- .../src/main/elm/Comp/BookmarkManage.elm | 88 +++++------ .../src/main/elm/Comp/BookmarkQueryForm.elm | 55 +++---- .../src/main/elm/Comp/BookmarkQueryManage.elm | 8 +- .../src/main/elm/Comp/BookmarkTable.elm | 6 +- .../webapp/src/main/elm/Comp/SearchMenu.elm | 4 +- .../src/main/elm/Data/BookmarkedQuery.elm | 138 ------------------ .../webapp/src/main/elm/Data/Bookmarks.elm | 48 ++++++ 19 files changed, 618 insertions(+), 345 deletions(-) create mode 100644 modules/backend/src/main/scala/docspell/backend/ops/OQueryBookmarks.scala create mode 100644 modules/restserver/src/main/scala/docspell/restserver/routes/BookmarkRoutes.scala create mode 100644 modules/store/src/main/resources/db/migration/h2/V1.31.0__query_bookmark.sql create mode 100644 modules/store/src/main/resources/db/migration/mariadb/V1.31.0__query_bookmark.sql create mode 100644 modules/store/src/main/resources/db/migration/postgresql/V1.31.0__query_bookmark.sql create mode 100644 modules/store/src/main/scala/docspell/store/records/RQueryBookmark.scala delete mode 100644 modules/webapp/src/main/elm/Data/BookmarkedQuery.elm create mode 100644 modules/webapp/src/main/elm/Data/Bookmarks.elm diff --git a/modules/backend/src/main/scala/docspell/backend/BackendApp.scala b/modules/backend/src/main/scala/docspell/backend/BackendApp.scala index 7bcaec3d..c18e72e8 100644 --- a/modules/backend/src/main/scala/docspell/backend/BackendApp.scala +++ b/modules/backend/src/main/scala/docspell/backend/BackendApp.scala @@ -49,6 +49,7 @@ trait BackendApp[F[_]] { def pubSub: PubSubT[F] def events: EventExchange[F] def notification: ONotification[F] + def bookmarks: OQueryBookmarks[F] } object BackendApp { @@ -89,6 +90,7 @@ object BackendApp { OShare(store, itemSearchImpl, simpleSearchImpl, javaEmil) ) notifyImpl <- ONotification(store, notificationMod) + bookmarksImpl <- OQueryBookmarks(store) } yield new BackendApp[F] { val pubSub = pubSubT val login = loginImpl @@ -115,5 +117,6 @@ object BackendApp { val share = shareImpl val events = notificationMod val notification = notifyImpl + val bookmarks = bookmarksImpl } } diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OQueryBookmarks.scala b/modules/backend/src/main/scala/docspell/backend/ops/OQueryBookmarks.scala new file mode 100644 index 00000000..134b0d46 --- /dev/null +++ b/modules/backend/src/main/scala/docspell/backend/ops/OQueryBookmarks.scala @@ -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(()) + + }) +} diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index bfa802a7..894eb995 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -1880,6 +1880,98 @@ paths: application/json: 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}: parameters: @@ -5314,6 +5406,32 @@ paths: components: 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: description: | A generic string value diff --git a/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala b/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala index aaecc0dd..dd36286e 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala @@ -172,7 +172,8 @@ object RestServer { "folder" -> FolderRoutes(restApp.backend, token), "customfield" -> CustomFieldRoutes(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]( diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/BookmarkRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/BookmarkRoutes.scala new file mode 100644 index 00000000..24b6eb05 --- /dev/null +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/BookmarkRoutes.scala @@ -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) + } +} diff --git a/modules/store/src/main/resources/db/migration/h2/V1.31.0__query_bookmark.sql b/modules/store/src/main/resources/db/migration/h2/V1.31.0__query_bookmark.sql new file mode 100644 index 00000000..f90c8e67 --- /dev/null +++ b/modules/store/src/main/resources/db/migration/h2/V1.31.0__query_bookmark.sql @@ -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") +) diff --git a/modules/store/src/main/resources/db/migration/mariadb/V1.31.0__query_bookmark.sql b/modules/store/src/main/resources/db/migration/mariadb/V1.31.0__query_bookmark.sql new file mode 100644 index 00000000..de37bcab --- /dev/null +++ b/modules/store/src/main/resources/db/migration/mariadb/V1.31.0__query_bookmark.sql @@ -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`) +) diff --git a/modules/store/src/main/resources/db/migration/postgresql/V1.31.0__query_bookmark.sql b/modules/store/src/main/resources/db/migration/postgresql/V1.31.0__query_bookmark.sql new file mode 100644 index 00000000..f90c8e67 --- /dev/null +++ b/modules/store/src/main/resources/db/migration/postgresql/V1.31.0__query_bookmark.sql @@ -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") +) diff --git a/modules/store/src/main/scala/docspell/store/records/RQueryBookmark.scala b/modules/store/src/main/scala/docspell/store/records/RQueryBookmark.scala new file mode 100644 index 00000000..1a486db3 --- /dev/null +++ b/modules/store/src/main/scala/docspell/store/records/RQueryBookmark.scala @@ -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] + } +} diff --git a/modules/store/src/main/scala/docspell/store/records/RUser.scala b/modules/store/src/main/scala/docspell/store/records/RUser.scala index af0c2224..9ddd9467 100644 --- a/modules/store/src/main/scala/docspell/store/records/RUser.scala +++ b/modules/store/src/main/scala/docspell/store/records/RUser.scala @@ -14,6 +14,8 @@ import docspell.store.qb._ import doobie._ import doobie.implicits._ +import cats.data.OptionT +import cats.effect.Sync case class RUser( uid: Ident, @@ -150,6 +152,13 @@ object RUser { .query[Ident] .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] = { val t = Table(None) def stmt(now: Timestamp) = diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index deb588c0..d99c60c3 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -124,7 +124,6 @@ module Api exposing , restoreAllItems , restoreItem , sampleEvent - , saveBookmarks , saveClientSettings , searchShare , searchShareStats @@ -188,6 +187,7 @@ module Api exposing import Api.Model.AttachmentMeta exposing (AttachmentMeta) import Api.Model.AuthResult exposing (AuthResult) import Api.Model.BasicResult exposing (BasicResult) +import Api.Model.BookmarkedQuery exposing (BookmarkedQuery) import Api.Model.CalEventCheck exposing (CalEventCheck) import Api.Model.CalEventCheckResult exposing (CalEventCheckResult) 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.UserPass exposing (UserPass) 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.CustomFieldOrder exposing (CustomFieldOrder) import Data.EquipmentOrder exposing (EquipmentOrder) @@ -2295,46 +2295,29 @@ saveClientSettings flags settings receive = --- Query Bookmarks -type alias BookmarkLocation = - Data.BookmarkedQuery.Location +bookmarkUri : Flags -> String +bookmarkUri flags = + flags.config.baseUrl ++ "/api/v1/sec/querybookmark" -bookmarkLocationUri : Flags -> BookmarkLocation -> String -bookmarkLocationUri flags loc = - 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 = +getBookmarksTask : Flags -> Task.Task Http.Error Bookmarks +getBookmarksTask flags = Http2.authTask { method = "GET" - , url = bookmarkLocationUri flags loc + , url = bookmarkUri flags , account = getAccount flags , body = Http.emptyBody - , resolver = Http2.jsonResolver Data.BookmarkedQuery.bookmarksDecoder + , resolver = Http2.jsonResolver Data.Bookmarks.bookmarksDecoder , headers = [] , 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 receive = let - coll = - getBookmarksTask flags Data.BookmarkedQuery.Collective - - user = - getBookmarksTask flags Data.BookmarkedQuery.User + bms = + getBookmarksTask flags shares = getSharesTask flags "" False @@ -2342,86 +2325,57 @@ getBookmarks flags receive = activeShare s = s.enabled && s.name /= Nothing - combine bc bu bs = - AllBookmarks bc bu (List.filter activeShare bs.items) + combine bm bs = + AllBookmarks (Data.Bookmarks.sort bm) (List.filter activeShare bs.items) in - Task.map3 combine coll user shares + Task.map2 combine bms shares |> Task.attempt receive -saveBookmarksTask : Flags -> BookmarkLocation -> Bookmarks -> Task.Task Http.Error BasicResult -saveBookmarksTask flags loc bookmarks = - Http2.authTask - { method = "PUT" - , url = bookmarkLocationUri flags loc +addBookmark : Flags -> BookmarkedQuery -> (Result Http.Error BasicResult -> msg) -> Cmd msg +addBookmark flags model receive = + Http2.authPost + { url = bookmarkUri flags , account = getAccount flags - , body = Http.jsonBody (Data.BookmarkedQuery.bookmarksEncode bookmarks) - , resolver = Http2.jsonResolver Api.Model.BasicResult.decoder - , headers = [] - , timeout = Nothing + , body = Http.jsonBody (Api.Model.BookmarkedQuery.encode model) + , expect = Http.expectJson receive Api.Model.BasicResult.decoder } -saveBookmarks : Flags -> Bookmarks -> BookmarkLocation -> (Result Http.Error BasicResult -> msg) -> Cmd msg -saveBookmarks flags bookmarks loc receive = - Task.attempt receive (saveBookmarksTask flags loc bookmarks) +updateBookmark : Flags -> BookmarkedQuery -> (Result Http.Error BasicResult -> msg) -> Cmd msg +updateBookmark flags model receive = + 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 -addBookmark flags model receive = +bookmarkNameExistsTask : Flags -> String -> Task.Task Http.Error Bool +bookmarkNameExistsTask flags name = let load = - getBookmarksTask flags model.location - - 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 + getBookmarksTask flags exists current = - Data.BookmarkedQuery.exists name current + Data.Bookmarks.exists name current in Task.map exists load -bookmarkNameExists : Flags -> BookmarkLocation -> String -> (Result Http.Error Bool -> msg) -> Cmd msg -bookmarkNameExists flags loc name receive = - bookmarkNameExistsTask flags loc name |> Task.attempt receive +bookmarkNameExists : Flags -> String -> (Result Http.Error Bool -> msg) -> Cmd msg +bookmarkNameExists flags name receive = + bookmarkNameExistsTask flags name |> Task.attempt receive -deleteBookmark : Flags -> BookmarkLocation -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg -deleteBookmark flags loc name receive = - let - load = - getBookmarksTask flags loc - - remove current = - Data.BookmarkedQuery.remove name current - |> saveBookmarksTask flags loc - in - Task.andThen remove load |> Task.attempt receive +deleteBookmark : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg +deleteBookmark flags id receive = + Http2.authDelete + { url = bookmarkUri flags ++ "/" ++ id + , account = getAccount flags + , expect = Http.expectJson receive Api.Model.BasicResult.decoder + } diff --git a/modules/webapp/src/main/elm/Comp/BookmarkChooser.elm b/modules/webapp/src/main/elm/Comp/BookmarkChooser.elm index 23dd1f36..64fb0044 100644 --- a/modules/webapp/src/main/elm/Comp/BookmarkChooser.elm +++ b/modules/webapp/src/main/elm/Comp/BookmarkChooser.elm @@ -11,8 +11,9 @@ module Comp.BookmarkChooser exposing , view ) +import Api.Model.BookmarkedQuery exposing (BookmarkedQuery) import Api.Model.ShareDetail exposing (ShareDetail) -import Data.BookmarkedQuery exposing (AllBookmarks, BookmarkedQuery) +import Data.Bookmarks exposing (AllBookmarks) import Data.Icons as Icons import Html exposing (Html, a, div, i, label, span, text) import Html.Attributes exposing (class, classList, href) @@ -34,19 +35,18 @@ init all = isEmpty : Model -> Bool isEmpty model = - model.all == Data.BookmarkedQuery.allBookmarksEmpty + model.all == Data.Bookmarks.empty type alias Selection = - { user : Set String - , collective : Set String + { bookmarks : Set String , shares : Set String } emptySelection : Selection emptySelection = - { user = Set.empty, collective = Set.empty, shares = Set.empty } + { bookmarks = Set.empty, shares = Set.empty } isEmptySelection : Selection -> Bool @@ -55,8 +55,7 @@ isEmptySelection sel = type Kind - = User - | Collective + = Bookmark | Share @@ -68,14 +67,13 @@ getQueries : Model -> Selection -> List BookmarkedQuery getQueries model sel = let member set bm = - Set.member bm.name set + Set.member bm.id set filterBookmarks f bms = - Data.BookmarkedQuery.filter f bms |> Data.BookmarkedQuery.map identity + List.filter f bms |> List.map identity in List.concat - [ filterBookmarks (member sel.user) model.all.user - , filterBookmarks (member sel.collective) model.all.collective + [ filterBookmarks (member sel.bookmarks) model.all.bookmarks , List.map shareToBookmark model.all.shares |> List.filter (member sel.shares) ] @@ -96,16 +94,13 @@ update msg model current = Set.insert name set in case msg of - Toggle kind name -> + Toggle kind id -> case kind of - User -> - ( model, { current | user = toggle name current.user } ) - - Collective -> - ( model, { current | collective = toggle name current.collective } ) + Bookmark -> + ( model, { current | bookmarks = toggle id current.bookmarks } ) 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 = + let + ( user, coll ) = + List.partition .personal model.all.bookmarks + in div [ class "flex flex-col" ] - [ userBookmarks texts model selection - , collBookmarks texts model selection + [ userBookmarks texts user selection + , collBookmarks texts coll 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 = div [ class "mb-2" - , classList [ ( "hidden", Data.BookmarkedQuery.emptyBookmarks == model.all.user ) ] + , classList [ ( "hidden", model == [] ) ] ] [ titleDiv texts.userLabel , 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 = div [ class "mb-2" - , classList [ ( "hidden", Data.BookmarkedQuery.emptyBookmarks == model.all.collective ) ] + , classList [ ( "hidden", [] == model ) ] ] [ titleDiv texts.collectiveLabel , 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 [ class "flex flex-row items-center rounded px-1 py-1 hover:bg-blue-100 dark:hover:bg-slate-600" , 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" ] [] else @@ -187,14 +186,11 @@ mkItem icon sel kind bm = isSelected : Selection -> Kind -> String -> Bool -isSelected sel kind name = - Set.member name <| +isSelected sel kind id = + Set.member id <| case kind of - User -> - sel.user - - Collective -> - sel.collective + Bookmark -> + sel.bookmarks Share -> sel.shares @@ -202,4 +198,4 @@ isSelected sel kind name = shareToBookmark : ShareDetail -> BookmarkedQuery shareToBookmark share = - BookmarkedQuery (Maybe.withDefault "-" share.name) share.query + BookmarkedQuery share.id (Maybe.withDefault "-" share.name) share.name share.query False 0 diff --git a/modules/webapp/src/main/elm/Comp/BookmarkManage.elm b/modules/webapp/src/main/elm/Comp/BookmarkManage.elm index 7e5f27b5..bbec3aa7 100644 --- a/modules/webapp/src/main/elm/Comp/BookmarkManage.elm +++ b/modules/webapp/src/main/elm/Comp/BookmarkManage.elm @@ -14,7 +14,7 @@ import Comp.BookmarkQueryForm import Comp.BookmarkTable import Comp.ItemDetail.Model exposing (Msg(..)) import Comp.MenuBar as MB -import Data.BookmarkedQuery exposing (AllBookmarks) +import Data.Bookmarks exposing (AllBookmarks) import Data.Flags exposing (Flags) import Data.UiSettings exposing (UiSettings) import Html exposing (..) @@ -43,16 +43,10 @@ type DeleteConfirm | DeleteConfirmOn -type alias FormData = - { model : Comp.BookmarkQueryForm.Model - , oldName : Maybe String - } - - type alias Model = { viewMode : ViewMode , bookmarks : AllBookmarks - , formData : FormData + , formModel : Comp.BookmarkQueryForm.Model , loading : Bool , formError : FormError , deleteConfirm : DeleteConfirm @@ -66,11 +60,8 @@ init flags = Comp.BookmarkQueryForm.init in ( { viewMode = Table - , bookmarks = Data.BookmarkedQuery.allBookmarksEmpty - , formData = - { model = fm - , oldName = Nothing - } + , bookmarks = Data.Bookmarks.empty + , formModel = fm , loading = False , formError = FormErrorNone , deleteConfirm = DeleteConfirmOff @@ -84,14 +75,14 @@ init flags = type Msg = LoadBookmarks - | TableMsg Data.BookmarkedQuery.Location Comp.BookmarkTable.Msg + | TableMsg Comp.BookmarkTable.Msg | FormMsg Comp.BookmarkQueryForm.Msg | InitNewBookmark | SetViewMode ViewMode | Submit | RequestDelete | CancelDelete - | DeleteBookmarkNow Data.BookmarkedQuery.Location String + | DeleteBookmarkNow String | LoadBookmarksResp (Result Http.Error AllBookmarks) | AddBookmarkResp (Result Http.Error BasicResult) | UpdateBookmarkResp (Result Http.Error BasicResult) @@ -119,8 +110,7 @@ update flags msg model = { model | viewMode = Form , formError = FormErrorNone - , formData = - { model = bm, oldName = Nothing } + , formModel = bm } in ( nm, Cmd.map FormMsg bc, Sub.none ) @@ -138,14 +128,14 @@ update flags msg model = FormMsg lm -> let ( fm, fc, fs ) = - Comp.BookmarkQueryForm.update flags lm model.formData.model + Comp.BookmarkQueryForm.update flags lm model.formModel in - ( { model | formData = { model = fm, oldName = model.formData.oldName }, formError = FormErrorNone } + ( { model | formModel = fm, formError = FormErrorNone } , Cmd.map FormMsg fc , Sub.map FormMsg fs ) - TableMsg loc lm -> + TableMsg lm -> let action = Comp.BookmarkTable.update lm @@ -154,15 +144,12 @@ update flags msg model = Comp.BookmarkTable.Edit bookmark -> let ( bm, bc ) = - Comp.BookmarkQueryForm.initWith - { query = bookmark - , location = loc - } + Comp.BookmarkQueryForm.initWith bookmark in ( { model | viewMode = Form , formError = FormErrorNone - , formData = { model = bm, oldName = Just bookmark.name } + , formModel = bm } , Cmd.map FormMsg bc , Sub.none @@ -174,9 +161,9 @@ update flags msg model = CancelDelete -> ( { model | deleteConfirm = DeleteConfirmOff }, Cmd.none, Sub.none ) - DeleteBookmarkNow loc name -> + DeleteBookmarkNow id -> ( { model | deleteConfirm = DeleteConfirmOff, loading = True } - , Api.deleteBookmark flags loc name DeleteBookmarkResp + , Api.deleteBookmark flags id DeleteBookmarkResp , Sub.none ) @@ -196,14 +183,13 @@ update flags msg model = ( { model | loading = False, formError = FormErrorHttp err }, Cmd.none, Sub.none ) Submit -> - case Comp.BookmarkQueryForm.get model.formData.model of + case Comp.BookmarkQueryForm.get model.formModel of Just data -> - case model.formData.oldName of - Just prevName -> - ( { model | loading = True }, Api.updateBookmark flags prevName data AddBookmarkResp, Sub.none ) + if data.id /= "" then + ( { model | loading = True }, Api.updateBookmark flags data AddBookmarkResp, Sub.none ) - Nothing -> - ( { model | loading = True }, Api.addBookmark flags data AddBookmarkResp, Sub.none ) + else + ( { model | loading = True }, Api.addBookmark flags data AddBookmarkResp, Sub.none ) Nothing -> ( { model | formError = FormErrorInvalid }, Cmd.none, Sub.none ) @@ -254,6 +240,10 @@ view texts settings flags model = viewTable : Texts -> Model -> Html Msg viewTable texts model = + let + ( user, coll ) = + List.partition .personal model.bookmarks.bookmarks + in div [ class "flex flex-col" ] [ MB.view { start = @@ -268,17 +258,23 @@ viewTable texts model = ] , rootClasses = "mb-4" } - , div [ class "flex flex-col" ] + , div + [ class "flex flex-col" + , classList [ ( "hidden", user == [] ) ] + ] [ h3 [ class S.header3 ] [ text texts.userBookmarks ] - , Html.map (TableMsg Data.BookmarkedQuery.User) - (Comp.BookmarkTable.view texts.bookmarkTable model.bookmarks.user) + , Html.map TableMsg + (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 ] [ text texts.collectiveBookmarks ] - , Html.map (TableMsg Data.BookmarkedQuery.Collective) - (Comp.BookmarkTable.view texts.bookmarkTable model.bookmarks.collective) + , Html.map TableMsg + (Comp.BookmarkTable.view texts.bookmarkTable coll) ] , B.loadingDimmer { label = "" @@ -291,10 +287,10 @@ viewForm : Texts -> UiSettings -> Flags -> Model -> Html Msg viewForm texts _ _ model = let newBookmark = - model.formData.oldName == Nothing + model.formModel.bookmark.id == "" isValid = - Comp.BookmarkQueryForm.get model.formData.model /= Nothing + Comp.BookmarkQueryForm.get model.formModel /= Nothing in div [] [ Html.form [] @@ -305,7 +301,7 @@ viewForm texts _ _ model = else h1 [ class S.header2 ] - [ text (Maybe.withDefault "" model.formData.model.name) + [ text (Maybe.withDefault "" model.formModel.name) ] , MB.view { start = @@ -360,7 +356,7 @@ viewForm texts _ _ model = text m ] , div [] - [ Html.map FormMsg (Comp.BookmarkQueryForm.view texts.bookmarkForm model.formData.model) + [ Html.map FormMsg (Comp.BookmarkQueryForm.view texts.bookmarkForm model.formModel) ] , B.loadingDimmer { active = model.loading @@ -378,11 +374,7 @@ viewForm texts _ _ model = { label = texts.basics.yes , icon = "fa fa-check" , disabled = False - , handler = - onClick - (DeleteBookmarkNow model.formData.model.location - (Maybe.withDefault "" model.formData.model.name) - ) + , handler = onClick (DeleteBookmarkNow model.formModel.bookmark.id) , attrs = [ href "#" ] } , B.secondaryButton diff --git a/modules/webapp/src/main/elm/Comp/BookmarkQueryForm.elm b/modules/webapp/src/main/elm/Comp/BookmarkQueryForm.elm index cba42c88..e0251cc5 100644 --- a/modules/webapp/src/main/elm/Comp/BookmarkQueryForm.elm +++ b/modules/webapp/src/main/elm/Comp/BookmarkQueryForm.elm @@ -8,9 +8,9 @@ module Comp.BookmarkQueryForm exposing (Model, Msg, get, init, initQuery, initWith, update, view) import Api +import Api.Model.BookmarkedQuery exposing (BookmarkedQuery) import Comp.Basic as B import Comp.PowerSearchInput -import Data.BookmarkedQuery exposing (BookmarkedQueryDef, Location(..)) import Data.Flags exposing (Flags) import Html exposing (..) import Html.Attributes exposing (..) @@ -24,10 +24,11 @@ import Util.Maybe type alias Model = - { name : Maybe String + { bookmark : BookmarkedQuery + , name : Maybe String , nameExists : Bool , queryModel : Comp.PowerSearchInput.Model - , location : Location + , isPersonal : Bool , nameExistsThrottle : Throttle Msg } @@ -40,10 +41,11 @@ initQuery q = (Comp.PowerSearchInput.setSearchString q) Comp.PowerSearchInput.init in - ( { name = Nothing + ( { bookmark = Api.Model.BookmarkedQuery.empty + , name = Nothing , nameExists = False , queryModel = res.model - , location = User + , isPersonal = True , nameExistsThrottle = Throttle.create 1 } , Cmd.batch @@ -57,15 +59,16 @@ init = initQuery "" -initWith : BookmarkedQueryDef -> ( Model, Cmd Msg ) +initWith : BookmarkedQuery -> ( Model, Cmd Msg ) initWith bm = let ( m, c ) = - initQuery bm.query.query + initQuery bm.query in ( { m - | name = Just bm.query.name - , location = bm.location + | name = Just bm.name + , isPersonal = bm.personal + , bookmark = bm } , c ) @@ -78,19 +81,21 @@ isValid model = /= Nothing -get : Model -> Maybe BookmarkedQueryDef +get : Model -> Maybe BookmarkedQuery get model = let qStr = Maybe.withDefault "" model.queryModel.input + + bm = + model.bookmark in if isValid model then Just - { query = - { query = qStr + { bm + | query = qStr , name = Maybe.withDefault "" model.name - } - , location = model.location + , personal = model.isPersonal } else @@ -100,7 +105,7 @@ get model = type Msg = SetName String | QueryMsg Comp.PowerSearchInput.Msg - | SetLocation Location + | SetPersonal Bool | NameExistsResp (Result Http.Error Bool) | UpdateThrottle @@ -109,12 +114,12 @@ update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg ) update flags msg model = let nameCheck1 name = - Api.bookmarkNameExists flags model.location name NameExistsResp + Api.bookmarkNameExists flags name NameExistsResp - nameCheck2 loc = + nameCheck2 = case model.name of Just n -> - Api.bookmarkNameExists flags loc n NameExistsResp + Api.bookmarkNameExists flags n NameExistsResp Nothing -> Cmd.none @@ -135,12 +140,12 @@ update flags msg model = , throttleSub ) - SetLocation loc -> + SetPersonal flag -> let ( newThrottle, cmd ) = - Throttle.try (nameCheck2 loc) model.nameExistsThrottle + Throttle.try nameCheck2 model.nameExistsThrottle in - ( { model | location = loc, nameExistsThrottle = newThrottle }, cmd, throttleSub ) + ( { model | isPersonal = flag, nameExistsThrottle = newThrottle }, cmd, throttleSub ) QueryMsg lm -> let @@ -224,8 +229,8 @@ view texts model = [ label [ class "inline-flex items-center" ] [ input [ type_ "radio" - , checked (model.location == User) - , onCheck (\_ -> SetLocation User) + , checked model.isPersonal + , onCheck (\_ -> SetPersonal True) , class S.radioInput ] [] @@ -235,9 +240,9 @@ view texts model = , label [ class "inline-flex items-center" ] [ input [ type_ "radio" - , checked (model.location == Collective) + , checked (not model.isPersonal) , class S.radioInput - , onCheck (\_ -> SetLocation Collective) + , onCheck (\_ -> SetPersonal False) ] [] , span [ class "ml-2" ] [ text texts.collectiveLocation ] diff --git a/modules/webapp/src/main/elm/Comp/BookmarkQueryManage.elm b/modules/webapp/src/main/elm/Comp/BookmarkQueryManage.elm index 0b641d18..1661f8b9 100644 --- a/modules/webapp/src/main/elm/Comp/BookmarkQueryManage.elm +++ b/modules/webapp/src/main/elm/Comp/BookmarkQueryManage.elm @@ -2,12 +2,12 @@ module Comp.BookmarkQueryManage exposing (..) import Api import Api.Model.BasicResult exposing (BasicResult) +import Api.Model.BookmarkedQuery exposing (BookmarkedQuery) import Comp.Basic as B import Comp.BookmarkQueryForm -import Data.BookmarkedQuery exposing (BookmarkedQueryDef) import Data.Flags exposing (Flags) 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 Http import Messages.Comp.BookmarkQueryManage exposing (Texts) @@ -55,7 +55,7 @@ type Msg type FormResult - = Submitted BookmarkedQueryDef + = Submitted BookmarkedQuery | Cancelled | Done | None @@ -117,7 +117,7 @@ update flags msg model = { empty | model = { model | loading = False, formState = FormStateError err } } -save : Flags -> BookmarkedQueryDef -> Cmd Msg +save : Flags -> BookmarkedQuery -> Cmd Msg save flags model = Api.addBookmark flags model SaveResp diff --git a/modules/webapp/src/main/elm/Comp/BookmarkTable.elm b/modules/webapp/src/main/elm/Comp/BookmarkTable.elm index 7d6954a5..c29f4c2a 100644 --- a/modules/webapp/src/main/elm/Comp/BookmarkTable.elm +++ b/modules/webapp/src/main/elm/Comp/BookmarkTable.elm @@ -12,8 +12,8 @@ module Comp.BookmarkTable exposing , view ) +import Api.Model.BookmarkedQuery exposing (BookmarkedQuery) import Comp.Basic as B -import Data.BookmarkedQuery exposing (BookmarkedQuery, Bookmarks) import Html exposing (..) import Html.Attributes exposing (..) import Messages.Comp.BookmarkTable exposing (Texts) @@ -39,7 +39,7 @@ update msg = --- View -view : Texts -> Bookmarks -> Html Msg +view : Texts -> List BookmarkedQuery -> Html Msg view texts bms = table [ class S.tableMain ] [ thead [] @@ -51,7 +51,7 @@ view texts bms = ] ] , tbody [] - (Data.BookmarkedQuery.map (renderBookmarkLine texts) bms) + (List.map (renderBookmarkLine texts) bms) ] diff --git a/modules/webapp/src/main/elm/Comp/SearchMenu.elm b/modules/webapp/src/main/elm/Comp/SearchMenu.elm index 4256aa48..aa52bc4c 100644 --- a/modules/webapp/src/main/elm/Comp/SearchMenu.elm +++ b/modules/webapp/src/main/elm/Comp/SearchMenu.elm @@ -43,7 +43,7 @@ import Comp.LinkTarget exposing (LinkTarget) import Comp.MenuBar as MB import Comp.Tabs import Comp.TagSelect -import Data.BookmarkedQuery exposing (AllBookmarks) +import Data.Bookmarks exposing (AllBookmarks) import Data.CustomFieldChange exposing (CustomFieldValueCollect) import Data.Direction exposing (Direction) import Data.DropdownStyle as DS @@ -146,7 +146,7 @@ init flags = , customFieldModel = Comp.CustomFieldMultiInput.initWith [] , customValues = Data.CustomFieldChange.emptyCollect , sourceModel = Nothing - , allBookmarks = Comp.BookmarkChooser.init Data.BookmarkedQuery.allBookmarksEmpty + , allBookmarks = Comp.BookmarkChooser.init Data.Bookmarks.empty , selectedBookmarks = Comp.BookmarkChooser.emptySelection , openTabs = Set.fromList [ "Tags", "Inbox" ] , searchMode = Data.SearchMode.Normal diff --git a/modules/webapp/src/main/elm/Data/BookmarkedQuery.elm b/modules/webapp/src/main/elm/Data/BookmarkedQuery.elm deleted file mode 100644 index ea5e8358..00000000 --- a/modules/webapp/src/main/elm/Data/BookmarkedQuery.elm +++ /dev/null @@ -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 ) - ] diff --git a/modules/webapp/src/main/elm/Data/Bookmarks.elm b/modules/webapp/src/main/elm/Data/Bookmarks.elm new file mode 100644 index 00000000..a1fdb08f --- /dev/null +++ b/modules/webapp/src/main/elm/Data/Bookmarks.elm @@ -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