mirror of
				https://github.com/TheAnachronism/docspell.git
				synced 2025-11-03 18:00:11 +00:00 
			
		
		
		
	Merge pull request #415 from eikek/fix-multi-tag-edit
Fix multi tag edit
This commit is contained in:
		@@ -8,7 +8,7 @@ This release contains many bug fixes, thank you all so much for
 | 
			
		||||
helping out! There is also a new feature and some more scripts in
 | 
			
		||||
tools.
 | 
			
		||||
 | 
			
		||||
- Edit/delete multiple items at once (#253)
 | 
			
		||||
- Edit/delete multiple items at once (#253, #412)
 | 
			
		||||
- Show/hide side menus via ui settings (#351)
 | 
			
		||||
- Adds two more scripts to the `tools/` section (thanks to
 | 
			
		||||
  @totti4ever):
 | 
			
		||||
@@ -36,6 +36,7 @@ tools.
 | 
			
		||||
- Routes for managing multiple items:
 | 
			
		||||
  - `/sec/items/deleteAll`
 | 
			
		||||
  - `/sec/items/tags`
 | 
			
		||||
  - `/sec/items/tagsremove`
 | 
			
		||||
  - `/sec/items/name`
 | 
			
		||||
  - `/sec/items/folder`
 | 
			
		||||
  - `/sec/items/direction`
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,12 @@ trait OItem[F[_]] {
 | 
			
		||||
      collective: Ident
 | 
			
		||||
  ): F[UpdateResult]
 | 
			
		||||
 | 
			
		||||
  def removeTagsMultipleItems(
 | 
			
		||||
      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]
 | 
			
		||||
 | 
			
		||||
@@ -225,6 +231,29 @@ object OItem {
 | 
			
		||||
              }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
        def removeTagsMultipleItems(
 | 
			
		||||
            items: NonEmptyList[Ident],
 | 
			
		||||
            tags: List[String],
 | 
			
		||||
            collective: Ident
 | 
			
		||||
        ): F[UpdateResult] =
 | 
			
		||||
          tags.distinct match {
 | 
			
		||||
            case Nil => UpdateResult.success.pure[F]
 | 
			
		||||
            case ws =>
 | 
			
		||||
              store.transact {
 | 
			
		||||
                (for {
 | 
			
		||||
                  itemIds <- OptionT
 | 
			
		||||
                    .liftF(RItem.filterItems(items, collective))
 | 
			
		||||
                    .filter(_.nonEmpty)
 | 
			
		||||
                  given <- OptionT.liftF(RTag.findAllByNameOrId(ws, collective))
 | 
			
		||||
                  _ <- OptionT.liftF(
 | 
			
		||||
                    itemIds.traverse(item =>
 | 
			
		||||
                      RTagItem.removeAllTags(item, given.map(_.tagId).toList)
 | 
			
		||||
                    )
 | 
			
		||||
                  )
 | 
			
		||||
                } yield UpdateResult.success).getOrElse(UpdateResult.notFound)
 | 
			
		||||
              }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
        def toggleTags(
 | 
			
		||||
            item: Ident,
 | 
			
		||||
            tags: List[String],
 | 
			
		||||
 
 | 
			
		||||
@@ -1945,6 +1945,29 @@ paths:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
                $ref: "#/components/schemas/BasicResult"
 | 
			
		||||
 | 
			
		||||
  /sec/items/tagsremove:
 | 
			
		||||
    post:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Item (Multi Edit)
 | 
			
		||||
      summary: Remove tags from multiple items
 | 
			
		||||
      description: |
 | 
			
		||||
        Remove the given tags from all given items.
 | 
			
		||||
      security:
 | 
			
		||||
        - authTokenHeader: []
 | 
			
		||||
      requestBody:
 | 
			
		||||
        content:
 | 
			
		||||
          application/json:
 | 
			
		||||
            schema:
 | 
			
		||||
              $ref: "#/components/schemas/ItemsAndRefs"
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          description: Ok
 | 
			
		||||
          content:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
                $ref: "#/components/schemas/BasicResult"
 | 
			
		||||
 | 
			
		||||
    put:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Item (Multi Edit)
 | 
			
		||||
 
 | 
			
		||||
@@ -73,6 +73,18 @@ object ItemMultiRoutes {
 | 
			
		||||
          resp <- Ok(Conversions.basicResult(res, "Tags added."))
 | 
			
		||||
        } yield resp
 | 
			
		||||
 | 
			
		||||
      case req @ POST -> Root / "tagsremove" =>
 | 
			
		||||
        for {
 | 
			
		||||
          json  <- req.as[ItemsAndRefs]
 | 
			
		||||
          items <- readIds[F](json.items)
 | 
			
		||||
          res <- backend.item.removeTagsMultipleItems(
 | 
			
		||||
            items,
 | 
			
		||||
            json.refs,
 | 
			
		||||
            user.account.collective
 | 
			
		||||
          )
 | 
			
		||||
          resp <- Ok(Conversions.basicResult(res, "Tags removed"))
 | 
			
		||||
        } yield resp
 | 
			
		||||
 | 
			
		||||
      case req @ PUT -> Root / "name" =>
 | 
			
		||||
        for {
 | 
			
		||||
          json  <- req.as[ItemsAndName]
 | 
			
		||||
 
 | 
			
		||||
@@ -75,6 +75,7 @@ module Api exposing
 | 
			
		||||
    , refreshSession
 | 
			
		||||
    , register
 | 
			
		||||
    , removeMember
 | 
			
		||||
    , removeTagsMultiple
 | 
			
		||||
    , sendMail
 | 
			
		||||
    , setAttachmentName
 | 
			
		||||
    , setCollectiveSettings
 | 
			
		||||
@@ -1342,6 +1343,20 @@ addTagsMultiple flags data receive =
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
removeTagsMultiple :
 | 
			
		||||
    Flags
 | 
			
		||||
    -> ItemsAndRefs
 | 
			
		||||
    -> (Result Http.Error BasicResult -> msg)
 | 
			
		||||
    -> Cmd msg
 | 
			
		||||
removeTagsMultiple flags data receive =
 | 
			
		||||
    Http2.authPost
 | 
			
		||||
        { url = flags.config.baseUrl ++ "/api/v1/sec/items/tagsremove"
 | 
			
		||||
        , account = getAccount flags
 | 
			
		||||
        , body = Http.jsonBody (Api.Model.ItemsAndRefs.encode data)
 | 
			
		||||
        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
setNameMultiple :
 | 
			
		||||
    Flags
 | 
			
		||||
    -> ItemsAndName
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,12 @@ type SaveNameState
 | 
			
		||||
    | SaveFailed
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type TagEditMode
 | 
			
		||||
    = AddTags
 | 
			
		||||
    | RemoveTags
 | 
			
		||||
    | ReplaceTags
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type alias Model =
 | 
			
		||||
    { tagModel : Comp.Dropdown.Model Tag
 | 
			
		||||
    , nameModel : String
 | 
			
		||||
@@ -70,6 +76,7 @@ type alias Model =
 | 
			
		||||
    , concPersonModel : Comp.Dropdown.Model IdName
 | 
			
		||||
    , concEquipModel : Comp.Dropdown.Model IdName
 | 
			
		||||
    , modalEdit : Maybe Comp.DetailEdit.Model
 | 
			
		||||
    , tagEditMode : TagEditMode
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -82,6 +89,7 @@ type Msg
 | 
			
		||||
    | RemoveDueDate
 | 
			
		||||
    | RemoveDate
 | 
			
		||||
    | ConfirmMsg Bool
 | 
			
		||||
    | ToggleTagEditMode
 | 
			
		||||
    | FolderDropdownMsg (Comp.Dropdown.Msg IdName)
 | 
			
		||||
    | TagDropdownMsg (Comp.Dropdown.Msg Tag)
 | 
			
		||||
    | DirDropdownMsg (Comp.Dropdown.Msg Direction)
 | 
			
		||||
@@ -146,6 +154,7 @@ init =
 | 
			
		||||
    , dueDate = Nothing
 | 
			
		||||
    , dueDatePicker = Comp.DatePicker.emptyModel
 | 
			
		||||
    , modalEdit = Nothing
 | 
			
		||||
    , tagEditMode = AddTags
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -213,19 +222,48 @@ update flags msg model =
 | 
			
		||||
                newModel =
 | 
			
		||||
                    { model | tagModel = m2 }
 | 
			
		||||
 | 
			
		||||
                mkChange list =
 | 
			
		||||
                    case model.tagEditMode of
 | 
			
		||||
                        AddTags ->
 | 
			
		||||
                            AddTagChange list
 | 
			
		||||
 | 
			
		||||
                        RemoveTags ->
 | 
			
		||||
                            RemoveTagChange list
 | 
			
		||||
 | 
			
		||||
                        ReplaceTags ->
 | 
			
		||||
                            ReplaceTagChange list
 | 
			
		||||
 | 
			
		||||
                change =
 | 
			
		||||
                    if isDropdownChangeMsg m then
 | 
			
		||||
                        Comp.Dropdown.getSelected newModel.tagModel
 | 
			
		||||
                            |> Util.List.distinct
 | 
			
		||||
                            |> List.map (\t -> IdName t.id t.name)
 | 
			
		||||
                            |> ReferenceList
 | 
			
		||||
                            |> TagChange
 | 
			
		||||
                            |> mkChange
 | 
			
		||||
 | 
			
		||||
                    else
 | 
			
		||||
                        NoFormChange
 | 
			
		||||
            in
 | 
			
		||||
            resultNoCmd change newModel
 | 
			
		||||
 | 
			
		||||
        ToggleTagEditMode ->
 | 
			
		||||
            let
 | 
			
		||||
                ( m2, _ ) =
 | 
			
		||||
                    Comp.Dropdown.update (Comp.Dropdown.SetSelection []) model.tagModel
 | 
			
		||||
 | 
			
		||||
                newModel =
 | 
			
		||||
                    { model | tagModel = m2 }
 | 
			
		||||
            in
 | 
			
		||||
            case model.tagEditMode of
 | 
			
		||||
                AddTags ->
 | 
			
		||||
                    resultNone { newModel | tagEditMode = RemoveTags }
 | 
			
		||||
 | 
			
		||||
                RemoveTags ->
 | 
			
		||||
                    resultNone { newModel | tagEditMode = ReplaceTags }
 | 
			
		||||
 | 
			
		||||
                ReplaceTags ->
 | 
			
		||||
                    resultNone { newModel | tagEditMode = AddTags }
 | 
			
		||||
 | 
			
		||||
        GetTagsResp (Ok tags) ->
 | 
			
		||||
            let
 | 
			
		||||
                tagList =
 | 
			
		||||
@@ -554,6 +592,28 @@ renderEditForm cfg settings model =
 | 
			
		||||
 | 
			
		||||
            else
 | 
			
		||||
                span [ class "invisible hidden" ] []
 | 
			
		||||
 | 
			
		||||
        tagModeIcon =
 | 
			
		||||
            case model.tagEditMode of
 | 
			
		||||
                AddTags ->
 | 
			
		||||
                    i [ class "grey plus link icon" ] []
 | 
			
		||||
 | 
			
		||||
                RemoveTags ->
 | 
			
		||||
                    i [ class "grey eraser link icon" ] []
 | 
			
		||||
 | 
			
		||||
                ReplaceTags ->
 | 
			
		||||
                    i [ class "grey redo alternate link icon" ] []
 | 
			
		||||
 | 
			
		||||
        tagModeMsg =
 | 
			
		||||
            case model.tagEditMode of
 | 
			
		||||
                AddTags ->
 | 
			
		||||
                    "Tags chosen here are *added* to all selected items."
 | 
			
		||||
 | 
			
		||||
                RemoveTags ->
 | 
			
		||||
                    "Tags chosen here are *removed* from all selected items."
 | 
			
		||||
 | 
			
		||||
                ReplaceTags ->
 | 
			
		||||
                    "Tags chosen here *replace* those on selected items."
 | 
			
		||||
    in
 | 
			
		||||
    div [ class cfg.menuClass ]
 | 
			
		||||
        [ div [ class "ui form warning" ]
 | 
			
		||||
@@ -581,8 +641,17 @@ renderEditForm cfg settings model =
 | 
			
		||||
                    [ label []
 | 
			
		||||
                        [ Icons.tagsIcon "grey"
 | 
			
		||||
                        , text "Tags"
 | 
			
		||||
                        , a
 | 
			
		||||
                            [ class "right-float"
 | 
			
		||||
                            , href "#"
 | 
			
		||||
                            , title "Change tag edit mode"
 | 
			
		||||
                            , onClick ToggleTagEditMode
 | 
			
		||||
                            ]
 | 
			
		||||
                            [ tagModeIcon
 | 
			
		||||
                            ]
 | 
			
		||||
                        ]
 | 
			
		||||
                    , Html.map TagDropdownMsg (Comp.Dropdown.view settings model.tagModel)
 | 
			
		||||
                    , Markdown.toHtml [ class "small-info" ] tagModeMsg
 | 
			
		||||
                    ]
 | 
			
		||||
            , div [ class " field" ]
 | 
			
		||||
                [ label [] [ text "Name" ]
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,9 @@ import Set exposing (Set)
 | 
			
		||||
 | 
			
		||||
type FormChange
 | 
			
		||||
    = NoFormChange
 | 
			
		||||
    | TagChange ReferenceList
 | 
			
		||||
    | AddTagChange ReferenceList
 | 
			
		||||
    | ReplaceTagChange ReferenceList
 | 
			
		||||
    | RemoveTagChange ReferenceList
 | 
			
		||||
    | FolderChange (Maybe IdName)
 | 
			
		||||
    | DirectionChange Direction
 | 
			
		||||
    | OrgChange (Maybe IdName)
 | 
			
		||||
@@ -45,13 +47,27 @@ multiUpdate flags ids change receive =
 | 
			
		||||
            Set.toList ids
 | 
			
		||||
    in
 | 
			
		||||
    case change of
 | 
			
		||||
        TagChange tags ->
 | 
			
		||||
        ReplaceTagChange tags ->
 | 
			
		||||
            let
 | 
			
		||||
                data =
 | 
			
		||||
                    ItemsAndRefs items (List.map .id tags.items)
 | 
			
		||||
            in
 | 
			
		||||
            Api.setTagsMultiple flags data receive
 | 
			
		||||
 | 
			
		||||
        AddTagChange tags ->
 | 
			
		||||
            let
 | 
			
		||||
                data =
 | 
			
		||||
                    ItemsAndRefs items (List.map .id tags.items)
 | 
			
		||||
            in
 | 
			
		||||
            Api.addTagsMultiple flags data receive
 | 
			
		||||
 | 
			
		||||
        RemoveTagChange tags ->
 | 
			
		||||
            let
 | 
			
		||||
                data =
 | 
			
		||||
                    ItemsAndRefs items (List.map .id tags.items)
 | 
			
		||||
            in
 | 
			
		||||
            Api.removeTagsMultiple flags data receive
 | 
			
		||||
 | 
			
		||||
        NameChange name ->
 | 
			
		||||
            let
 | 
			
		||||
                data =
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user