mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-03-25 16:45:05 +00:00
Editing tags for multiple items
This commit is contained in:
parent
5735a47199
commit
7ad37c8d26
@ -1,5 +1,6 @@
|
||||
package docspell.backend.ops
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
import cats.data.OptionT
|
||||
import cats.effect.{Effect, Resource}
|
||||
import cats.implicits._
|
||||
@ -13,21 +14,38 @@ import docspell.store.queue.JobQueue
|
||||
import docspell.store.records._
|
||||
import docspell.store.{AddResult, Store}
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
import org.log4s.getLogger
|
||||
|
||||
trait OItem[F[_]] {
|
||||
|
||||
/** Sets the given tags (removing all existing ones). */
|
||||
def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[AddResult]
|
||||
def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[UpdateResult]
|
||||
|
||||
/** Sets tags for multiple items. The tags of the items will be
|
||||
* replaced with the given ones. Same as `setTags` but for multiple
|
||||
* items.
|
||||
*/
|
||||
def setTagsMultipleItems(
|
||||
items: NonEmptyList[Ident],
|
||||
tags: List[Ident],
|
||||
collective: Ident
|
||||
): F[UpdateResult]
|
||||
|
||||
/** Create a new tag and add it to the item. */
|
||||
def addNewTag(item: Ident, tag: RTag): F[AddResult]
|
||||
|
||||
/** Apply all tags to the given item. Tags must exist, but can be IDs or names. */
|
||||
/** Apply all tags to the given item. Tags must exist, but can be IDs
|
||||
* or names. Existing tags on the item are left unchanged.
|
||||
*/
|
||||
def linkTags(item: Ident, tags: List[String], collective: Ident): F[UpdateResult]
|
||||
|
||||
def linkTagsMultipleItems(
|
||||
items: NonEmptyList[Ident],
|
||||
tags: List[String],
|
||||
collective: Ident
|
||||
): F[UpdateResult]
|
||||
|
||||
/** Toggles tags of the given item. Tags must exist, but can be IDs or names. */
|
||||
def toggleTags(item: Ident, tags: List[String], collective: Ident): F[UpdateResult]
|
||||
|
||||
@ -55,7 +73,14 @@ trait OItem[F[_]] {
|
||||
|
||||
def setName(item: Ident, name: String, collective: Ident): F[AddResult]
|
||||
|
||||
def setState(item: Ident, state: ItemState, collective: Ident): F[AddResult]
|
||||
def setState(item: Ident, state: ItemState, collective: Ident): F[AddResult] =
|
||||
setStates(NonEmptyList.of(item), state, collective)
|
||||
|
||||
def setStates(
|
||||
item: NonEmptyList[Ident],
|
||||
state: ItemState,
|
||||
collective: Ident
|
||||
): F[AddResult]
|
||||
|
||||
def setItemDate(item: Ident, date: Option[Timestamp], collective: Ident): F[AddResult]
|
||||
|
||||
@ -130,21 +155,30 @@ object OItem {
|
||||
item: Ident,
|
||||
tags: List[String],
|
||||
collective: Ident
|
||||
): F[UpdateResult] =
|
||||
linkTagsMultipleItems(NonEmptyList.of(item), tags, collective)
|
||||
|
||||
def linkTagsMultipleItems(
|
||||
items: NonEmptyList[Ident],
|
||||
tags: List[String],
|
||||
collective: Ident
|
||||
): F[UpdateResult] =
|
||||
tags.distinct match {
|
||||
case Nil => UpdateResult.success.pure[F]
|
||||
case kws =>
|
||||
val db =
|
||||
case ws =>
|
||||
store.transact {
|
||||
(for {
|
||||
_ <- OptionT(RItem.checkByIdAndCollective(item, collective))
|
||||
given <- OptionT.liftF(RTag.findAllByNameOrId(kws, collective))
|
||||
exist <- OptionT.liftF(RTagItem.findAllIn(item, given.map(_.tagId)))
|
||||
itemIds <- OptionT
|
||||
.liftF(RItem.filterItems(items, collective))
|
||||
.filter(_.nonEmpty)
|
||||
given <- OptionT.liftF(RTag.findAllByNameOrId(ws, collective))
|
||||
_ <- OptionT.liftF(
|
||||
RTagItem.setAllTags(item, given.map(_.tagId).diff(exist.map(_.tagId)))
|
||||
itemIds.traverse(item =>
|
||||
RTagItem.appendTags(item, given.map(_.tagId).toList)
|
||||
)
|
||||
)
|
||||
} yield UpdateResult.success).getOrElse(UpdateResult.notFound)
|
||||
|
||||
store.transact(db)
|
||||
}
|
||||
}
|
||||
|
||||
def toggleTags(
|
||||
@ -169,20 +203,23 @@ object OItem {
|
||||
store.transact(db)
|
||||
}
|
||||
|
||||
def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[AddResult] = {
|
||||
val db = for {
|
||||
cid <- RItem.getCollective(item)
|
||||
nd <-
|
||||
if (cid.contains(collective)) RTagItem.deleteItemTags(item)
|
||||
else 0.pure[ConnectionIO]
|
||||
ni <-
|
||||
if (tagIds.nonEmpty && cid.contains(collective))
|
||||
RTagItem.insertItemTags(item, tagIds)
|
||||
else 0.pure[ConnectionIO]
|
||||
} yield nd + ni
|
||||
def setTags(
|
||||
item: Ident,
|
||||
tagIds: List[Ident],
|
||||
collective: Ident
|
||||
): F[UpdateResult] =
|
||||
setTagsMultipleItems(NonEmptyList.of(item), tagIds, collective)
|
||||
|
||||
store.transact(db).attempt.map(AddResult.fromUpdate)
|
||||
}
|
||||
def setTagsMultipleItems(
|
||||
items: NonEmptyList[Ident],
|
||||
tags: List[Ident],
|
||||
collective: Ident
|
||||
): F[UpdateResult] =
|
||||
UpdateResult.fromUpdate(store.transact(for {
|
||||
k <- RTagItem.deleteItemTags(items, collective)
|
||||
res <- items.traverse(i => RTagItem.setAllTags(i, tags))
|
||||
n = res.fold
|
||||
} yield k + n))
|
||||
|
||||
def addNewTag(item: Ident, tag: RTag): F[AddResult] =
|
||||
(for {
|
||||
@ -192,7 +229,7 @@ object OItem {
|
||||
_ <- addres match {
|
||||
case AddResult.Success =>
|
||||
OptionT.liftF(
|
||||
store.transact(RTagItem.insertItemTags(item, List(tag.tagId)))
|
||||
store.transact(RTagItem.setAllTags(item, List(tag.tagId)))
|
||||
)
|
||||
case AddResult.EntityExists(_) =>
|
||||
OptionT.pure[F](0)
|
||||
@ -371,9 +408,13 @@ object OItem {
|
||||
onSuccessIgnoreError(fts.updateItemName(logger, item, collective, name))
|
||||
)
|
||||
|
||||
def setState(item: Ident, state: ItemState, collective: Ident): F[AddResult] =
|
||||
def setStates(
|
||||
items: NonEmptyList[Ident],
|
||||
state: ItemState,
|
||||
collective: Ident
|
||||
): F[AddResult] =
|
||||
store
|
||||
.transact(RItem.updateStateForCollective(item, state, collective))
|
||||
.transact(RItem.updateStateForCollective(items, state, collective))
|
||||
.attempt
|
||||
.map(AddResult.fromUpdate)
|
||||
|
||||
|
@ -1927,7 +1927,10 @@ paths:
|
||||
- Item (Multi Edit)
|
||||
summary: Add tags to multiple items
|
||||
description: |
|
||||
Add the given tags to all given items.
|
||||
Add the given tags to all given items. The tags that are
|
||||
currently attached to the items are not changed. If there are
|
||||
new tags in the given list, then they are added. Otherwise,
|
||||
the item is left unchanged.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
requestBody:
|
||||
@ -1948,7 +1951,7 @@ paths:
|
||||
summary: Sets tags to multiple items
|
||||
description: |
|
||||
Sets the given tags to all given items. If the tag list is
|
||||
empty, then tags are removed from the items.
|
||||
empty, then all tags are removed from the items.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
requestBody:
|
||||
|
@ -73,6 +73,7 @@ object RestServer {
|
||||
"collective" -> CollectiveRoutes(restApp.backend, token),
|
||||
"queue" -> JobQueueRoutes(restApp.backend, token),
|
||||
"item" -> ItemRoutes(cfg, restApp.backend, token),
|
||||
"items" -> ItemMultiRoutes(restApp.backend, token),
|
||||
"attachment" -> AttachmentRoutes(restApp.backend, token),
|
||||
"upload" -> UploadRoutes.secured(restApp.backend, cfg, token),
|
||||
"checkfile" -> CheckFileRoutes.secured(restApp.backend, token),
|
||||
|
@ -0,0 +1,189 @@
|
||||
package docspell.restserver.routes
|
||||
|
||||
import cats.ApplicativeError
|
||||
import cats.MonadError
|
||||
import cats.data.NonEmptyList
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.backend.BackendApp
|
||||
import docspell.backend.auth.AuthToken
|
||||
import docspell.common.{Ident, ItemState}
|
||||
import docspell.restapi.model._
|
||||
import docspell.restserver.conv.Conversions
|
||||
|
||||
import io.circe.DecodingFailure
|
||||
import org.http4s.HttpRoutes
|
||||
import org.http4s.circe.CirceEntityDecoder._
|
||||
import org.http4s.circe.CirceEntityEncoder._
|
||||
import org.http4s.dsl.Http4sDsl
|
||||
|
||||
object ItemMultiRoutes {
|
||||
// private[this] val logger = getLogger
|
||||
|
||||
def apply[F[_]: Effect](
|
||||
backend: BackendApp[F],
|
||||
user: AuthToken
|
||||
): HttpRoutes[F] = {
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of {
|
||||
case req @ PUT -> Root / "confirm" =>
|
||||
for {
|
||||
json <- req.as[IdList]
|
||||
data <- readIds[F](json.ids)
|
||||
res <- backend.item.setStates(
|
||||
data,
|
||||
ItemState.Confirmed,
|
||||
user.account.collective
|
||||
)
|
||||
resp <- Ok(Conversions.basicResult(res, "Item data confirmed"))
|
||||
} yield resp
|
||||
|
||||
case req @ PUT -> Root / "unconfirm" =>
|
||||
for {
|
||||
json <- req.as[IdList]
|
||||
data <- readIds[F](json.ids)
|
||||
res <- backend.item.setStates(
|
||||
data,
|
||||
ItemState.Created,
|
||||
user.account.collective
|
||||
)
|
||||
resp <- Ok(Conversions.basicResult(res, "Item back to created."))
|
||||
} yield resp
|
||||
|
||||
case req @ PUT -> Root / "tags" =>
|
||||
for {
|
||||
json <- req.as[ItemsAndRefs]
|
||||
items <- readIds[F](json.items)
|
||||
tags <- json.refs.traverse(readId[F])
|
||||
res <- backend.item.setTagsMultipleItems(items, tags, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Tags updated"))
|
||||
} yield resp
|
||||
|
||||
case req @ POST -> Root / "tags" =>
|
||||
for {
|
||||
json <- req.as[ItemsAndRefs]
|
||||
items <- readIds[F](json.items)
|
||||
res <- backend.item.linkTagsMultipleItems(
|
||||
items,
|
||||
json.refs,
|
||||
user.account.collective
|
||||
)
|
||||
resp <- Ok(Conversions.basicResult(res, "Tags added."))
|
||||
} yield resp
|
||||
|
||||
// case req @ PUT -> Root / "direction" =>
|
||||
// for {
|
||||
// dir <- req.as[DirectionValue]
|
||||
// res <- backend.item.setDirection(id, dir.direction, user.account.collective)
|
||||
// resp <- Ok(Conversions.basicResult(res, "Direction updated"))
|
||||
// } yield resp
|
||||
|
||||
// case req @ PUT -> Root / "folder" =>
|
||||
// for {
|
||||
// idref <- req.as[OptionalId]
|
||||
// res <- backend.item.setFolder(id, idref.id, user.account.collective)
|
||||
// resp <- Ok(Conversions.basicResult(res, "Folder updated"))
|
||||
// } yield resp
|
||||
|
||||
// case req @ PUT -> Root / "corrOrg" =>
|
||||
// for {
|
||||
// idref <- req.as[OptionalId]
|
||||
// res <- backend.item.setCorrOrg(id, idref.id, user.account.collective)
|
||||
// resp <- Ok(Conversions.basicResult(res, "Correspondent organization updated"))
|
||||
// } yield resp
|
||||
|
||||
// case req @ PUT -> Root / "corrPerson" =>
|
||||
// for {
|
||||
// idref <- req.as[OptionalId]
|
||||
// res <- backend.item.setCorrPerson(id, idref.id, user.account.collective)
|
||||
// resp <- Ok(Conversions.basicResult(res, "Correspondent person updated"))
|
||||
// } yield resp
|
||||
|
||||
// case req @ PUT -> Root / "concPerson" =>
|
||||
// for {
|
||||
// idref <- req.as[OptionalId]
|
||||
// res <- backend.item.setConcPerson(id, idref.id, user.account.collective)
|
||||
// resp <- Ok(Conversions.basicResult(res, "Concerned person updated"))
|
||||
// } yield resp
|
||||
|
||||
// case req @ PUT -> Root / "concEquipment" =>
|
||||
// for {
|
||||
// idref <- req.as[OptionalId]
|
||||
// res <- backend.item.setConcEquip(id, idref.id, user.account.collective)
|
||||
// resp <- Ok(Conversions.basicResult(res, "Concerned equipment updated"))
|
||||
// } yield resp
|
||||
|
||||
// case req @ PUT -> Root / "name" =>
|
||||
// for {
|
||||
// text <- req.as[OptionalText]
|
||||
// res <- backend.item.setName(
|
||||
// id,
|
||||
// text.text.notEmpty.getOrElse(""),
|
||||
// user.account.collective
|
||||
// )
|
||||
// resp <- Ok(Conversions.basicResult(res, "Name updated"))
|
||||
// } yield resp
|
||||
|
||||
// case req @ PUT -> Root / "duedate" =>
|
||||
// for {
|
||||
// date <- req.as[OptionalDate]
|
||||
// _ <- logger.fdebug(s"Setting item due date to ${date.date}")
|
||||
// res <- backend.item.setItemDueDate(id, date.date, user.account.collective)
|
||||
// resp <- Ok(Conversions.basicResult(res, "Item due date updated"))
|
||||
// } yield resp
|
||||
|
||||
// case req @ PUT -> Root / "date" =>
|
||||
// for {
|
||||
// date <- req.as[OptionalDate]
|
||||
// _ <- logger.fdebug(s"Setting item date to ${date.date}")
|
||||
// res <- backend.item.setItemDate(id, date.date, user.account.collective)
|
||||
// resp <- Ok(Conversions.basicResult(res, "Item date updated"))
|
||||
// } yield resp
|
||||
|
||||
// case req @ POST -> Root / "reprocess" =>
|
||||
// for {
|
||||
// data <- req.as[IdList]
|
||||
// ids = data.ids.flatMap(s => Ident.fromString(s).toOption)
|
||||
// _ <- logger.fdebug(s"Re-process item ${id.id}")
|
||||
// res <- backend.item.reprocess(id, ids, user.account, true)
|
||||
// resp <- Ok(Conversions.basicResult(res, "Re-process task submitted."))
|
||||
// } yield resp
|
||||
|
||||
// case POST -> Root / "deleteAll" =>
|
||||
// for {
|
||||
// n <- backend.item.deleteItem(id, user.account.collective)
|
||||
// res = BasicResult(n > 0, if (n > 0) "Item deleted" else "Item deletion failed.")
|
||||
// resp <- Ok(res)
|
||||
// } yield resp
|
||||
}
|
||||
}
|
||||
|
||||
implicit final class OptionString(opt: Option[String]) {
|
||||
def notEmpty: Option[String] =
|
||||
opt.map(_.trim).filter(_.nonEmpty)
|
||||
}
|
||||
|
||||
private def readId[F[_]](
|
||||
id: String
|
||||
)(implicit F: ApplicativeError[F, Throwable]): F[Ident] =
|
||||
Ident
|
||||
.fromString(id)
|
||||
.fold(
|
||||
err => F.raiseError(DecodingFailure(err, Nil)),
|
||||
F.pure
|
||||
)
|
||||
|
||||
private def readIds[F[_]](ids: List[String])(implicit
|
||||
F: MonadError[F, Throwable]
|
||||
): F[NonEmptyList[Ident]] =
|
||||
ids.traverse(readId[F]).map(NonEmptyList.fromList).flatMap {
|
||||
case Some(nel) => nel.pure[F]
|
||||
case None =>
|
||||
F.raiseError(
|
||||
DecodingFailure("Empty list found, at least one element required", Nil)
|
||||
)
|
||||
}
|
||||
}
|
@ -57,7 +57,12 @@ case class Column(name: String, ns: String = "", alias: String = "") {
|
||||
f ++ fr"IN (" ++ commas(values) ++ fr")"
|
||||
|
||||
def isIn[A: Put](values: NonEmptyList[A]): Fragment =
|
||||
isIn(values.map(a => sql"$a").toList)
|
||||
values.tail match {
|
||||
case Nil =>
|
||||
is(values.head)
|
||||
case _ =>
|
||||
isIn(values.map(a => sql"$a").toList)
|
||||
}
|
||||
|
||||
def isLowerIn[A: Put](values: NonEmptyList[A]): Fragment =
|
||||
fr"lower(" ++ f ++ fr") IN (" ++ commas(values.map(a => sql"$a").toList) ++ fr")"
|
||||
|
@ -132,7 +132,7 @@ object RItem {
|
||||
} yield n
|
||||
|
||||
def updateStateForCollective(
|
||||
itemId: Ident,
|
||||
itemIds: NonEmptyList[Ident],
|
||||
itemState: ItemState,
|
||||
coll: Ident
|
||||
): ConnectionIO[Int] =
|
||||
@ -140,7 +140,7 @@ object RItem {
|
||||
t <- currentTime
|
||||
n <- updateRow(
|
||||
table,
|
||||
and(id.is(itemId), cid.is(coll)),
|
||||
and(id.isIn(itemIds), cid.is(coll)),
|
||||
commas(state.setTo(itemState), updated.setTo(t))
|
||||
).update.run
|
||||
} yield n
|
||||
@ -324,4 +324,10 @@ object RItem {
|
||||
val empty: Option[Ident] = None
|
||||
updateRow(table, folder.is(folderId), folder.setTo(empty)).update.run
|
||||
}
|
||||
|
||||
def filterItemsFragment(items: NonEmptyList[Ident], coll: Ident): Fragment =
|
||||
selectSimple(Seq(id), table, and(cid.is(coll), id.isIn(items)))
|
||||
|
||||
def filterItems(items: NonEmptyList[Ident], coll: Ident): ConnectionIO[Vector[Ident]] =
|
||||
filterItemsFragment(items, coll).query[Ident].to[Vector]
|
||||
}
|
||||
|
@ -30,18 +30,17 @@ object RTagItem {
|
||||
def deleteItemTags(item: Ident): ConnectionIO[Int] =
|
||||
deleteFrom(table, itemId.is(item)).update.run
|
||||
|
||||
def deleteItemTags(items: NonEmptyList[Ident], cid: Ident): ConnectionIO[Int] = {
|
||||
val itemsFiltered =
|
||||
RItem.filterItemsFragment(items, cid)
|
||||
val sql = fr"DELETE FROM" ++ table ++ fr"WHERE" ++ itemId.isIn(itemsFiltered)
|
||||
|
||||
sql.update.run
|
||||
}
|
||||
|
||||
def deleteTag(tid: Ident): ConnectionIO[Int] =
|
||||
deleteFrom(table, tagId.is(tid)).update.run
|
||||
|
||||
def insertItemTags(item: Ident, tags: Seq[Ident]): ConnectionIO[Int] =
|
||||
for {
|
||||
tagValues <- tags.toList.traverse(id =>
|
||||
Ident.randomId[ConnectionIO].map(rid => RTagItem(rid, item, id))
|
||||
)
|
||||
tagFrag = tagValues.map(v => fr"${v.tagItemId},${v.itemId},${v.tagId}")
|
||||
ins <- insertRows(table, all, tagFrag).update.run
|
||||
} yield ins
|
||||
|
||||
def findByItem(item: Ident): ConnectionIO[Vector[RTagItem]] =
|
||||
selectSimple(all, table, itemId.is(item)).query[RTagItem].to[Vector]
|
||||
|
||||
@ -76,4 +75,12 @@ object RTagItem {
|
||||
entities.map(v => fr"${v.tagItemId},${v.itemId},${v.tagId}")
|
||||
).update.run
|
||||
} yield n
|
||||
|
||||
def appendTags(item: Ident, tags: List[Ident]): ConnectionIO[Int] =
|
||||
for {
|
||||
existing <- findByItem(item)
|
||||
toadd = tags.toSet.diff(existing.map(_.tagId).toSet)
|
||||
n <- setAllTags(item, toadd.toSeq)
|
||||
} yield n
|
||||
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ module Api exposing
|
||||
, addCorrPerson
|
||||
, addMember
|
||||
, addTag
|
||||
, addTagsMultiple
|
||||
, cancelJob
|
||||
, changeFolderName
|
||||
, changePassword
|
||||
@ -88,6 +89,7 @@ module Api exposing
|
||||
, setItemNotes
|
||||
, setJobPrio
|
||||
, setTags
|
||||
, setTagsMultiple
|
||||
, setUnconfirmed
|
||||
, startClassifier
|
||||
, startOnceNotifyDueItems
|
||||
@ -130,6 +132,7 @@ import Api.Model.ItemLightList exposing (ItemLightList)
|
||||
import Api.Model.ItemProposals exposing (ItemProposals)
|
||||
import Api.Model.ItemSearch exposing (ItemSearch)
|
||||
import Api.Model.ItemUploadMeta exposing (ItemUploadMeta)
|
||||
import Api.Model.ItemsAndRefs exposing (ItemsAndRefs)
|
||||
import Api.Model.JobPriority exposing (JobPriority)
|
||||
import Api.Model.JobQueueState exposing (JobQueueState)
|
||||
import Api.Model.MoveAttachment exposing (MoveAttachment)
|
||||
@ -1262,6 +1265,38 @@ getJobQueueStateTask flags =
|
||||
|
||||
|
||||
|
||||
--- Item (Mulit Edit)
|
||||
|
||||
|
||||
setTagsMultiple :
|
||||
Flags
|
||||
-> ItemsAndRefs
|
||||
-> (Result Http.Error BasicResult -> msg)
|
||||
-> Cmd msg
|
||||
setTagsMultiple flags data receive =
|
||||
Http2.authPut
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/items/tags"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Api.Model.ItemsAndRefs.encode data)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
|
||||
addTagsMultiple :
|
||||
Flags
|
||||
-> ItemsAndRefs
|
||||
-> (Result Http.Error BasicResult -> msg)
|
||||
-> Cmd msg
|
||||
addTagsMultiple flags data receive =
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/items/tags"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Api.Model.ItemsAndRefs.encode data)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Item
|
||||
|
||||
|
||||
|
@ -40,5 +40,12 @@ multiUpdate flags ids change receive =
|
||||
Set.toList ids
|
||||
in
|
||||
case change of
|
||||
TagChange tags ->
|
||||
let
|
||||
data =
|
||||
ItemsAndRefs items (List.map .id tags.items)
|
||||
in
|
||||
Api.setTagsMultiple flags data receive
|
||||
|
||||
_ ->
|
||||
Cmd.none
|
||||
|
@ -435,7 +435,10 @@ update mId key flags settings msg model =
|
||||
res.change
|
||||
MultiUpdateResp
|
||||
in
|
||||
( { model | viewMode = SelectView svm_ }, Cmd.batch [ cmd_, upCmd ], sub_ )
|
||||
( { model | viewMode = SelectView svm_ }
|
||||
, Cmd.batch [ cmd_, upCmd ]
|
||||
, sub_
|
||||
)
|
||||
|
||||
_ ->
|
||||
noSub ( model, Cmd.none )
|
||||
|
Loading…
x
Reference in New Issue
Block a user