diff --git a/Changelog.md b/Changelog.md index eeeeabf3..d75f54e1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,62 @@ # Changelog +## v0.14.0 + +*Soon* + +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, #412) +- Show/hide side menus via ui settings (#351) +- Adds two more scripts to the `tools/` section (thanks to + @totti4ever): + - one script to import data from paperless (#358, #359), and + - a script to check clean a directory from files that are already in + docspell (#403) +- Extend docker image to use newest ocrmypdf version (#393, thanks + @totti4ever) +- Fix bug that would stop processing when pdf conversion fails (#392, + #387) +- Fix bug to have a separate, configurable source identifier for the + integration upload endpoint (#389) +- Fixes ui bug to not highlight the last viewed item when searching + again. (#373) +- Fixes bug when saving multiple changes to the ui settings (#368) +- Fixes uniqueness check for equipments (#370) +- Fixes a bug when doing document classification where user input was + not correctly escaped for regexes (#356) +- Fixes debian packages to have both (joex + restserver) the same user + to make H2 work (#336) +- Fixes a bug when searching with multiple tags using MariaDB (#404) + +### REST Api Changes + +- Routes for managing multiple items: + - `/sec/items/deleteAll` + - `/sec/items/tags` + - `/sec/items/tagsremove` + - `/sec/items/name` + - `/sec/items/folder` + - `/sec/items/direction` + - `/sec/items/date` + - `/sec/items/duedate` + - `/sec/items/corrOrg` + - `/sec/items/corrPerson` + - `/sec/items/concPerson` + - `/sec/items/concEquipment` + - `/sec/items/confirm` + - `/sec/items/unconfirm` + - `/sec/items/reprocess` +- Adds another parameter to `ItemSearch` structure to enable searching + in a subset of items giving their ids. + +### Configuration Changes + +- new setting `….integration-endpoint.source-name` to define the + source name for files uploaded through this endpoint + ## v0.13.0 *Oct 19, 2020* diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala index fd3e5344..492d613a 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala @@ -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], diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index 2eabe73f..881511be 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -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) diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemMultiRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemMultiRoutes.scala index 3cb50f6e..7b1dd931 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemMultiRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemMultiRoutes.scala @@ -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] diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index 77ee3c20..6ce34ec1 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -10,6 +10,7 @@ module Api exposing , changeFolderName , changePassword , checkCalEvent + , confirmMultiple , createImapSettings , createMailSettings , createNewFolder @@ -74,6 +75,7 @@ module Api exposing , refreshSession , register , removeMember + , removeTagsMultiple , sendMail , setAttachmentName , setCollectiveSettings @@ -107,6 +109,7 @@ module Api exposing , startReIndex , submitNotifyDueItems , toggleTags + , unconfirmMultiple , updateNotifyDueItems , updateScanMailbox , upload @@ -1284,6 +1287,34 @@ getJobQueueStateTask flags = --- Item (Mulit Edit) +confirmMultiple : + Flags + -> Set String + -> (Result Http.Error BasicResult -> msg) + -> Cmd msg +confirmMultiple flags ids receive = + Http2.authPut + { url = flags.config.baseUrl ++ "/api/v1/sec/items/confirm" + , account = getAccount flags + , body = Http.jsonBody (Api.Model.IdList.encode (IdList (Set.toList ids))) + , expect = Http.expectJson receive Api.Model.BasicResult.decoder + } + + +unconfirmMultiple : + Flags + -> Set String + -> (Result Http.Error BasicResult -> msg) + -> Cmd msg +unconfirmMultiple flags ids receive = + Http2.authPut + { url = flags.config.baseUrl ++ "/api/v1/sec/items/unconfirm" + , account = getAccount flags + , body = Http.jsonBody (Api.Model.IdList.encode (IdList (Set.toList ids))) + , expect = Http.expectJson receive Api.Model.BasicResult.decoder + } + + setTagsMultiple : Flags -> ItemsAndRefs @@ -1312,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 diff --git a/modules/webapp/src/main/elm/Comp/ItemDetail/EditMenu.elm b/modules/webapp/src/main/elm/Comp/ItemDetail/EditMenu.elm index 561f1ef8..161b5871 100644 --- a/modules/webapp/src/main/elm/Comp/ItemDetail/EditMenu.elm +++ b/modules/webapp/src/main/elm/Comp/ItemDetail/EditMenu.elm @@ -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 } @@ -81,6 +88,8 @@ type Msg | UpdateThrottle | RemoveDueDate | RemoveDate + | ConfirmMsg Bool + | ToggleTagEditMode | FolderDropdownMsg (Comp.Dropdown.Msg IdName) | TagDropdownMsg (Comp.Dropdown.Msg Tag) | DirDropdownMsg (Comp.Dropdown.Msg Direction) @@ -145,6 +154,7 @@ init = , dueDate = Nothing , dueDatePicker = Comp.DatePicker.emptyModel , modalEdit = Nothing + , tagEditMode = AddTags } @@ -201,6 +211,9 @@ resultNone model = update : Flags -> Msg -> Model -> UpdateResult update flags msg model = case msg of + ConfirmMsg flag -> + resultNoCmd (ConfirmChange flag) model + TagDropdownMsg m -> let ( m2, _ ) = @@ -209,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 = @@ -550,16 +592,66 @@ 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" ] - [ optional [ Data.Fields.Tag ] <| + [ div [ class "field" ] + [ div + [ class "ui fluid buttons" + ] + [ button + [ class "ui primary button" + , onClick (ConfirmMsg True) + ] + [ text "Confirm" + ] + , div [ class "or" ] [] + , button + [ class "ui secondary button" + , onClick (ConfirmMsg False) + ] + [ text "Unconfirm" + ] + ] + ] + , optional [ Data.Fields.Tag ] <| div [ class "field" ] [ 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" ] diff --git a/modules/webapp/src/main/elm/Comp/ItemDetail/FormChange.elm b/modules/webapp/src/main/elm/Comp/ItemDetail/FormChange.elm index 7d9c1c85..b7521ba4 100644 --- a/modules/webapp/src/main/elm/Comp/ItemDetail/FormChange.elm +++ b/modules/webapp/src/main/elm/Comp/ItemDetail/FormChange.elm @@ -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) @@ -30,6 +32,7 @@ type FormChange | ItemDateChange (Maybe Int) | DueDateChange (Maybe Int) | NameChange String + | ConfirmChange Bool multiUpdate : @@ -44,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 = @@ -114,5 +131,12 @@ multiUpdate flags ids change receive = in Api.setConcEquipmentMultiple flags data receive + ConfirmChange flag -> + if flag then + Api.confirmMultiple flags ids receive + + else + Api.unconfirmMultiple flags ids receive + NoFormChange -> Cmd.none diff --git a/modules/webapp/src/main/elm/Page/Home/View.elm b/modules/webapp/src/main/elm/Page/Home/View.elm index 3519f61f..bfc10bf7 100644 --- a/modules/webapp/src/main/elm/Page/Home/View.elm +++ b/modules/webapp/src/main/elm/Page/Home/View.elm @@ -59,7 +59,7 @@ view flags settings model = , onClick ToggleSearchMenu , title "Hide menu" ] - [ i [ class "ui angle left icon" ] [] + [ i [ class "chevron left icon" ] [] ] , div [ class "right floated menu" ] [ a @@ -303,13 +303,9 @@ viewSearchBar flags model = [ class "search-menu-toggle ui blue icon button" , onClick ToggleSearchMenu , href "#" - , if model.searchTypeForm == ContentOnlySearch then - title "Search menu disabled" - - else - title "Open search menu" + , title "Open search menu" ] - [ i [ class "angle right icon" ] [] + [ i [ class "filter icon" ] [] ] , div [ class "item" ] [ div [ class "ui left icon right action input" ] diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 353a2ab1..349795b3 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -14,14 +14,14 @@ object Dependencies { val EmilVersion = "0.6.3" val FastparseVersion = "2.1.3" val FlexmarkVersion = "0.62.2" - val FlywayVersion = "7.1.0" + val FlywayVersion = "7.1.1" val Fs2Version = "2.4.4" val H2Version = "1.4.200" val Http4sVersion = "0.21.8" val Icu4jVersion = "68.1" val JsoupVersion = "1.13.1" val KindProjectorVersion = "0.10.3" - val Log4sVersion = "1.8.2" + val Log4sVersion = "1.9.0" val LogbackVersion = "1.2.3" val MariaDbVersion = "2.7.0" val MiniTestVersion = "2.8.2" @@ -34,7 +34,7 @@ object Dependencies { val StanfordNlpVersion = "4.0.0" val TikaVersion = "1.24.1" val YamuscaVersion = "0.7.0" - val SwaggerUIVersion = "3.36.0" + val SwaggerUIVersion = "3.36.1" val SemanticUIVersion = "2.4.1" val TwelveMonkeysVersion = "3.6" val JQueryVersion = "3.5.1" diff --git a/tools/consumedir-cleaner/consumedir-cleaner.sh b/tools/consumedir-cleaner/consumedir-cleaner.sh new file mode 100755 index 00000000..1e8c2574 --- /dev/null +++ b/tools/consumedir-cleaner/consumedir-cleaner.sh @@ -0,0 +1,179 @@ +#!/usr/bin/env bash + +echo "##################### START #####################" + +echo " Docspell Consumedir Cleaner - v0.1 beta" +echo " by totti4ever" && echo +echo " $(date)" +echo +echo "#################################################" +echo && echo + +jq --version > /dev/null +if [ $? -ne 0 ]; then + echo "please install 'jq'" + exit -4 +fi + +ds_url=${1%/} +ds_user_param=$2 +ds_user=${ds_user_param#*/} +ds_collective=${ds_user_param%%/*} +ds_password=$3 +ds_consumedir_path=${4%/} +ds_archive_path=$ds_consumedir_path/_archive/$ds_collective + + +if [ $# -ne 4 ]; then + echo "FATAL Exactly four parameters needed" + exit -3 +elif [ "$1" == "" ] || [ "$2" == "" ] || [ "$3" == "" ] || [ "$4" == "" ]; then + echo "FATAL Parameter missing" + echo " ds_url: $ds_url" + echo " ds_user: $ds_user" + echo " ds_password: $ds_password" + echo " ds_consumedir_path: $ds_consumedir_path" + exit -2 +elif [ "$ds_collective" == "_archive" ]; then + echo "FATAL collective name '_archive' is not supported by this script" + exit -1 +fi + + +############# FUNCTIONS +function curl_call() { + curl_cmd="$1 -H 'X-Docspell-Auth: $ds_token'" + curl_result=$(eval $curl_cmd) + curl_code=$? + + if [ "$curl_result" == '"Authentication failed."' ] || [ "$curl_result" == 'Response timed out' ]; then + printf "\nNew login required ($curl_result)... " + login + printf "%${#len_resultset}s" " "; printf " .." + curl_call $1 + + elif [ "$curl_result" == "Bad Gateway" ] || [ "$curl_result" == '404 page not found' ]; then + echo "FATAL Connection to server failed" + exit -1 + fi +} + + +function login() { + curl_call "curl -s -X POST -d '{\"account\": \"$ds_collective/$ds_user\", \"password\": \"$ds_password\"}' ${ds_url}/api/v1/open/auth/login" + + curl_status=$(echo $curl_result | jq -r ".success") + + if [ "$curl_status" == "true" ]; then + ds_token=$(echo $curl_result | jq -r ".token") + echo "Login successfull ( Token: $ds_token )" + + else + echo "FATAL Login not succesfull" + exit 1 + + fi +} + +############# END + +echo "Settings:" +if [ "$DS_CC_REMOVE" == "true" ]; then + echo " ### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ###" + echo " - DELETE files? YES" + echo " when already existing in Docspell. This cannot be undone!" + echo " ### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ###" +else + echo " - DELETE files? no" + echo " moving already uploaded files to archive" +fi +echo +if [ "$DS_CC_UPLOAD_MISSING" == true ]; then + echo " - UPLOAD files? YES" + echo " files not existing in Docspell will be uploaded and will be re-checked in the next run." +else + echo " - UPLOAD files? no" + echo " files not existing in Docspell will NOT be uploaded and stay where they are." +fi +echo && echo +echo "Press 'ctrl+c' to cancel" +for ((i=9;i>=0;i--)); do + printf "\r waiting $i seconds " + sleep 1s +done +echo && echo + +# login, get token +login + +echo "Scanning folder for collective '$ds_collective' ($ds_consumedir_path/$ds_collective)" +echo && echo + +while read -r line +do + tmp_filepath=$line + + if [ "$tmp_filepath" == "" ]; then + echo "no files found" && echo + exit 0 #no results + elif [ ! -f "$tmp_filepath" ]; then + echo "FATAL no access to file: $tmp_filepath" + exit 3 + fi + + echo "Checking '$tmp_filepath'" + printf "%${#len_resultset}s" " "; printf " " + + # check for checksum + tmp_checksum=$(sha256sum "$tmp_filepath" | awk '{print $1}') + + curl_call "curl -s -X GET '$ds_url/api/v1/sec/checkfile/$tmp_checksum'" + curl_status=$(echo $curl_result | jq -r ".exists") + + if [ $curl_code -ne 0 ]; then + # error + echo "ERROR $curl_result // $curl_status" + + # file exists in Docspell + elif [ "$curl_status" == "true" ]; then + item_name=$(echo $curl_result | jq -r ".items[0].name") + item_id=$(echo $curl_result | jq -r ".items[0].id") + echo "File already exists: '$item_name (ID: $item_id)'" + + printf "%${#len_resultset}s" " "; printf " " + if [ "$DS_CC_REMOVE" == "true" ]; then + echo "... removing file" + rm "$tmp_filepath" + else + created=$(echo $curl_result | jq -r ".items[0].created") + cur_dir="$ds_archive_path/$(date -d @$(echo "($created+500)/1000" | bc) +%Y-%m +)" + echo "... moving to archive by month added ('$cur_dir')" + mkdir -p "$cur_dir" + mv "$tmp_filepath" "$cur_dir/" + fi + + # file does not exist in Docspell + else + + echo "Files does not exist, yet" + if [ "$DS_CC_UPLOAD_MISSING" == true ]; then + printf "%${#len_resultset}s" " "; printf " " + printf "...uploading file.." + curl_call "curl -s -X POST '$ds_url/api/v1/sec/upload/item' -H 'Content-Type: multipart/form-data' -F 'file=@$tmp_filepath'" + curl_status=$(echo $curl_result | jq -r ".success") + if [ "$curl_status" == "true" ]; then + echo ". done" + else + echo -e "\nERROR $curl_result" + fi + fi + fi + + echo +done \ + <<< $(find $ds_consumedir_path/$ds_collective -type f) + + +echo ################# DONE ################# +date diff --git a/website/elm/GetStarted.elm b/website/elm/GetStarted.elm index 13c7314f..dd5dee4a 100644 --- a/website/elm/GetStarted.elm +++ b/website/elm/GetStarted.elm @@ -87,6 +87,13 @@ The directory contains a file `docspell.conf` that you can ] , text "." ] + , li [] + [ text "Chat on " + , a [ href "https://gitter.im/eikek/docspell" ] + [ text "Gitter" + ] + , text " for questions and feedback." + ] ] ] ] diff --git a/website/elm/Main.elm b/website/elm/Main.elm index 22828d2e..fa578431 100644 --- a/website/elm/Main.elm +++ b/website/elm/Main.elm @@ -173,13 +173,12 @@ footHero model = [ text " • " ] , span [] - [ text "© 2020 " - ] - , a - [ href "https://github.com/eikek" - , target "_blank" - ] - [ text "@eikek" + [ a + [ href "https://gitter.im/eikek/docspell" + , target "_blank" + ] + [ text "Chat on Gitter" + ] ] ] ] diff --git a/website/elm/Search.elm b/website/elm/Search.elm index e62c732c..c9204b9d 100644 --- a/website/elm/Search.elm +++ b/website/elm/Search.elm @@ -104,8 +104,8 @@ view model = [ class "form" , onSubmit SubmitSearch ] - [ div [ class "dropdown field is-active is-fullwidth" ] - [ div [ class "control has-icons-right is-fullwidth" ] + [ div [ class "dropdown field is-active is-fullwidth has-addons" ] + [ div [ class "control is-fullwidth" ] [ input [ class "input" , type_ "text" @@ -114,7 +114,13 @@ view model = , value model.searchInput ] [] - , span [ class "icon is-right is-small" ] + ] + , div [ class "control" ] + [ button + [ class "button is-primary" + , href "#" + , onClick SubmitSearch + ] [ img [ src "/icons/search-20.svg" ] [] ] ] diff --git a/website/site/content/docs/features/_index.md b/website/site/content/docs/features/_index.md index 17b5e997..57026f11 100644 --- a/website/site/content/docs/features/_index.md +++ b/website/site/content/docs/features/_index.md @@ -38,6 +38,7 @@ description = "A list of features and limitations." with due dates - [Read your mailboxes](@/docs/webapp/scanmailbox.md) via IMAP to import mails into docspell +- [Edit multiple items](@/docs/webapp/multiedit.md) at once - REST server and document processing are separate applications which can be scaled-out independently - Everything stored in a SQL database: PostgreSQL, MariaDB or H2 diff --git a/website/site/content/docs/tools/consumedir-cleaner.md b/website/site/content/docs/tools/consumedir-cleaner.md new file mode 100644 index 00000000..fc1a756e --- /dev/null +++ b/website/site/content/docs/tools/consumedir-cleaner.md @@ -0,0 +1,52 @@ ++++ +title = "Directory Cleaner" +description = "Clean directories from files in docspell" +weight = 32 ++++ + +# Introduction + +This script is made for cleaning up the consumption directory used for +the consumedir service (as it is provided as docker container)which +are copied or moved there. + + + +## How it works + +- Checks for every file (in the collective's folder of the given user + name) if it already exists in the collective (using Docspell's API). +- If so, by default those files are moved to an archive folder just + besides the collective's consumption folders named _archive. The + archive's files are organized into monthly subfolders by the date + they've been added to Docspell + - If set, those files can also be deleted instead of being moved to + the archive. There is no undo function provided for this, so be + careful. +- If a file is found which does not exist in the collective, by + default nothing happens, so that file would be found in every run + and just ignored + - If set, those files can also be uploaded to Docspell. Depending on + the setting for files already existing these files would either be + deleted or moved to the archive in the next run. + +## Usage (parameters / settings) + +Copy the script to your machine and run it with the following +parameters: + +1. URL of Docspell, including http(s) +2. Username for Docspell, possibly including Collective (if other name + as user) +3. Password for Docspell +4. Path to the directory which files shall be checked against + existence in Docspell + +Additionally, environment variables can be used to alter the behavior: + +- `DS_CC_REMOVE` + - `true` – delete files which already exist in the collective + - `false` (default) - move them to the archive (see above) +- `DS_CC_UPLOAD_MISSING` + - `true` - uploads files which do not exist in the collective + - `false` (default) - ignore them and do nothing diff --git a/website/site/content/docs/tools/paperless-import.md b/website/site/content/docs/tools/paperless-import.md new file mode 100644 index 00000000..c1f02d29 --- /dev/null +++ b/website/site/content/docs/tools/paperless-import.md @@ -0,0 +1,43 @@ ++++ +title = "Paperless Import" +description = "Import your data from paperless." +weight = 35 ++++ + +# Introduction + +Coming from +[paperless](https://github.com/the-paperless-project/paperless/), the +script in `tools/import-paperless` can be used to get started by +importing your data from paperless into docspell. + + + +The script imports the files and also tags and correspondents. + +# Usage + +Copy the script to the machine where paperless is running. Run it with +the following arguments: + +1. URL of Docspell, including http(s) +2. Username for Docspell, possibly including Collective (if other name as user) +3. Password for Docspell +4. Path to Paperless' database file (`db.sqlite3`). When using Paperless with docker, it is in the mapped directory `/usr/src/paperless/data` +5. Path to Paperless' document base directory. When using Paperless with docker, it is the mapped directory `/usr/src/paperless/media/documents/origin/` + +Some settings can be changed inside the script, right at the top: + +* `LIMIT="LIMIT 0"` (default: inactive): For testing purposes, limits + the number of tags and correspondents read from Paperless (this will + most likely lead to warnings when processing the documents) +* `LIMIT_DOC="LIMIT 5"` (default: inactive): For testing purposes, + limits the number of documents and document-to-tag relations read + from Paperless +* `SKIP_EXISTING_DOCS=true` (default: true): Won't touch already + existing documents. If set to `false` documents, which exist + already, won't be uploaded again, but the tags, correspondent, date + and title from Paperless will be applied. + + **Warning** In case you already had set these information in Docspell, + they will be overwritten! diff --git a/website/site/content/docs/webapp/multiedit-03.png b/website/site/content/docs/webapp/multiedit-03.png index db97f9aa..ee3b2684 100644 Binary files a/website/site/content/docs/webapp/multiedit-03.png and b/website/site/content/docs/webapp/multiedit-03.png differ diff --git a/website/site/content/docs/webapp/multiedit-04.png b/website/site/content/docs/webapp/multiedit-04.png index 9e14b1ae..8b165e12 100644 Binary files a/website/site/content/docs/webapp/multiedit-04.png and b/website/site/content/docs/webapp/multiedit-04.png differ diff --git a/website/site/templates/footer.html b/website/site/templates/footer.html index 163d9458..c6e85f49 100644 --- a/website/site/templates/footer.html +++ b/website/site/templates/footer.html @@ -12,10 +12,7 @@ - © 2020 - - @eikek - + Chat on Gitter