mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-03-29 02:05:07 +00:00
Merge branch 'master' of github.com:eikek/docspell into pr-fix-ocrmypdf
This commit is contained in:
commit
d5f0d47e80
57
Changelog.md
57
Changelog.md
@ -1,5 +1,62 @@
|
|||||||
# Changelog
|
# 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
|
## v0.13.0
|
||||||
|
|
||||||
*Oct 19, 2020*
|
*Oct 19, 2020*
|
||||||
|
@ -46,6 +46,12 @@ trait OItem[F[_]] {
|
|||||||
collective: Ident
|
collective: Ident
|
||||||
): F[UpdateResult]
|
): 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. */
|
/** 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]
|
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(
|
def toggleTags(
|
||||||
item: Ident,
|
item: Ident,
|
||||||
tags: List[String],
|
tags: List[String],
|
||||||
|
@ -1945,6 +1945,29 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/BasicResult"
|
$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:
|
put:
|
||||||
tags:
|
tags:
|
||||||
- Item (Multi Edit)
|
- Item (Multi Edit)
|
||||||
|
@ -73,6 +73,18 @@ object ItemMultiRoutes {
|
|||||||
resp <- Ok(Conversions.basicResult(res, "Tags added."))
|
resp <- Ok(Conversions.basicResult(res, "Tags added."))
|
||||||
} yield resp
|
} 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" =>
|
case req @ PUT -> Root / "name" =>
|
||||||
for {
|
for {
|
||||||
json <- req.as[ItemsAndName]
|
json <- req.as[ItemsAndName]
|
||||||
|
@ -10,6 +10,7 @@ module Api exposing
|
|||||||
, changeFolderName
|
, changeFolderName
|
||||||
, changePassword
|
, changePassword
|
||||||
, checkCalEvent
|
, checkCalEvent
|
||||||
|
, confirmMultiple
|
||||||
, createImapSettings
|
, createImapSettings
|
||||||
, createMailSettings
|
, createMailSettings
|
||||||
, createNewFolder
|
, createNewFolder
|
||||||
@ -74,6 +75,7 @@ module Api exposing
|
|||||||
, refreshSession
|
, refreshSession
|
||||||
, register
|
, register
|
||||||
, removeMember
|
, removeMember
|
||||||
|
, removeTagsMultiple
|
||||||
, sendMail
|
, sendMail
|
||||||
, setAttachmentName
|
, setAttachmentName
|
||||||
, setCollectiveSettings
|
, setCollectiveSettings
|
||||||
@ -107,6 +109,7 @@ module Api exposing
|
|||||||
, startReIndex
|
, startReIndex
|
||||||
, submitNotifyDueItems
|
, submitNotifyDueItems
|
||||||
, toggleTags
|
, toggleTags
|
||||||
|
, unconfirmMultiple
|
||||||
, updateNotifyDueItems
|
, updateNotifyDueItems
|
||||||
, updateScanMailbox
|
, updateScanMailbox
|
||||||
, upload
|
, upload
|
||||||
@ -1284,6 +1287,34 @@ getJobQueueStateTask flags =
|
|||||||
--- Item (Mulit Edit)
|
--- 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 :
|
setTagsMultiple :
|
||||||
Flags
|
Flags
|
||||||
-> ItemsAndRefs
|
-> 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 :
|
setNameMultiple :
|
||||||
Flags
|
Flags
|
||||||
-> ItemsAndName
|
-> ItemsAndName
|
||||||
|
@ -53,6 +53,12 @@ type SaveNameState
|
|||||||
| SaveFailed
|
| SaveFailed
|
||||||
|
|
||||||
|
|
||||||
|
type TagEditMode
|
||||||
|
= AddTags
|
||||||
|
| RemoveTags
|
||||||
|
| ReplaceTags
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ tagModel : Comp.Dropdown.Model Tag
|
{ tagModel : Comp.Dropdown.Model Tag
|
||||||
, nameModel : String
|
, nameModel : String
|
||||||
@ -70,6 +76,7 @@ type alias Model =
|
|||||||
, concPersonModel : Comp.Dropdown.Model IdName
|
, concPersonModel : Comp.Dropdown.Model IdName
|
||||||
, concEquipModel : Comp.Dropdown.Model IdName
|
, concEquipModel : Comp.Dropdown.Model IdName
|
||||||
, modalEdit : Maybe Comp.DetailEdit.Model
|
, modalEdit : Maybe Comp.DetailEdit.Model
|
||||||
|
, tagEditMode : TagEditMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -81,6 +88,8 @@ type Msg
|
|||||||
| UpdateThrottle
|
| UpdateThrottle
|
||||||
| RemoveDueDate
|
| RemoveDueDate
|
||||||
| RemoveDate
|
| RemoveDate
|
||||||
|
| ConfirmMsg Bool
|
||||||
|
| ToggleTagEditMode
|
||||||
| FolderDropdownMsg (Comp.Dropdown.Msg IdName)
|
| FolderDropdownMsg (Comp.Dropdown.Msg IdName)
|
||||||
| TagDropdownMsg (Comp.Dropdown.Msg Tag)
|
| TagDropdownMsg (Comp.Dropdown.Msg Tag)
|
||||||
| DirDropdownMsg (Comp.Dropdown.Msg Direction)
|
| DirDropdownMsg (Comp.Dropdown.Msg Direction)
|
||||||
@ -145,6 +154,7 @@ init =
|
|||||||
, dueDate = Nothing
|
, dueDate = Nothing
|
||||||
, dueDatePicker = Comp.DatePicker.emptyModel
|
, dueDatePicker = Comp.DatePicker.emptyModel
|
||||||
, modalEdit = Nothing
|
, modalEdit = Nothing
|
||||||
|
, tagEditMode = AddTags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -201,6 +211,9 @@ resultNone model =
|
|||||||
update : Flags -> Msg -> Model -> UpdateResult
|
update : Flags -> Msg -> Model -> UpdateResult
|
||||||
update flags msg model =
|
update flags msg model =
|
||||||
case msg of
|
case msg of
|
||||||
|
ConfirmMsg flag ->
|
||||||
|
resultNoCmd (ConfirmChange flag) model
|
||||||
|
|
||||||
TagDropdownMsg m ->
|
TagDropdownMsg m ->
|
||||||
let
|
let
|
||||||
( m2, _ ) =
|
( m2, _ ) =
|
||||||
@ -209,19 +222,48 @@ update flags msg model =
|
|||||||
newModel =
|
newModel =
|
||||||
{ model | tagModel = m2 }
|
{ model | tagModel = m2 }
|
||||||
|
|
||||||
|
mkChange list =
|
||||||
|
case model.tagEditMode of
|
||||||
|
AddTags ->
|
||||||
|
AddTagChange list
|
||||||
|
|
||||||
|
RemoveTags ->
|
||||||
|
RemoveTagChange list
|
||||||
|
|
||||||
|
ReplaceTags ->
|
||||||
|
ReplaceTagChange list
|
||||||
|
|
||||||
change =
|
change =
|
||||||
if isDropdownChangeMsg m then
|
if isDropdownChangeMsg m then
|
||||||
Comp.Dropdown.getSelected newModel.tagModel
|
Comp.Dropdown.getSelected newModel.tagModel
|
||||||
|> Util.List.distinct
|
|> Util.List.distinct
|
||||||
|> List.map (\t -> IdName t.id t.name)
|
|> List.map (\t -> IdName t.id t.name)
|
||||||
|> ReferenceList
|
|> ReferenceList
|
||||||
|> TagChange
|
|> mkChange
|
||||||
|
|
||||||
else
|
else
|
||||||
NoFormChange
|
NoFormChange
|
||||||
in
|
in
|
||||||
resultNoCmd change newModel
|
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) ->
|
GetTagsResp (Ok tags) ->
|
||||||
let
|
let
|
||||||
tagList =
|
tagList =
|
||||||
@ -550,16 +592,66 @@ renderEditForm cfg settings model =
|
|||||||
|
|
||||||
else
|
else
|
||||||
span [ class "invisible hidden" ] []
|
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
|
in
|
||||||
div [ class cfg.menuClass ]
|
div [ class cfg.menuClass ]
|
||||||
[ div [ class "ui form warning" ]
|
[ 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" ]
|
div [ class "field" ]
|
||||||
[ label []
|
[ label []
|
||||||
[ Icons.tagsIcon "grey"
|
[ Icons.tagsIcon "grey"
|
||||||
, text "Tags"
|
, text "Tags"
|
||||||
|
, a
|
||||||
|
[ class "right-float"
|
||||||
|
, href "#"
|
||||||
|
, title "Change tag edit mode"
|
||||||
|
, onClick ToggleTagEditMode
|
||||||
|
]
|
||||||
|
[ tagModeIcon
|
||||||
|
]
|
||||||
]
|
]
|
||||||
, Html.map TagDropdownMsg (Comp.Dropdown.view settings model.tagModel)
|
, Html.map TagDropdownMsg (Comp.Dropdown.view settings model.tagModel)
|
||||||
|
, Markdown.toHtml [ class "small-info" ] tagModeMsg
|
||||||
]
|
]
|
||||||
, div [ class " field" ]
|
, div [ class " field" ]
|
||||||
[ label [] [ text "Name" ]
|
[ label [] [ text "Name" ]
|
||||||
|
@ -20,7 +20,9 @@ import Set exposing (Set)
|
|||||||
|
|
||||||
type FormChange
|
type FormChange
|
||||||
= NoFormChange
|
= NoFormChange
|
||||||
| TagChange ReferenceList
|
| AddTagChange ReferenceList
|
||||||
|
| ReplaceTagChange ReferenceList
|
||||||
|
| RemoveTagChange ReferenceList
|
||||||
| FolderChange (Maybe IdName)
|
| FolderChange (Maybe IdName)
|
||||||
| DirectionChange Direction
|
| DirectionChange Direction
|
||||||
| OrgChange (Maybe IdName)
|
| OrgChange (Maybe IdName)
|
||||||
@ -30,6 +32,7 @@ type FormChange
|
|||||||
| ItemDateChange (Maybe Int)
|
| ItemDateChange (Maybe Int)
|
||||||
| DueDateChange (Maybe Int)
|
| DueDateChange (Maybe Int)
|
||||||
| NameChange String
|
| NameChange String
|
||||||
|
| ConfirmChange Bool
|
||||||
|
|
||||||
|
|
||||||
multiUpdate :
|
multiUpdate :
|
||||||
@ -44,13 +47,27 @@ multiUpdate flags ids change receive =
|
|||||||
Set.toList ids
|
Set.toList ids
|
||||||
in
|
in
|
||||||
case change of
|
case change of
|
||||||
TagChange tags ->
|
ReplaceTagChange tags ->
|
||||||
let
|
let
|
||||||
data =
|
data =
|
||||||
ItemsAndRefs items (List.map .id tags.items)
|
ItemsAndRefs items (List.map .id tags.items)
|
||||||
in
|
in
|
||||||
Api.setTagsMultiple flags data receive
|
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 ->
|
NameChange name ->
|
||||||
let
|
let
|
||||||
data =
|
data =
|
||||||
@ -114,5 +131,12 @@ multiUpdate flags ids change receive =
|
|||||||
in
|
in
|
||||||
Api.setConcEquipmentMultiple flags data receive
|
Api.setConcEquipmentMultiple flags data receive
|
||||||
|
|
||||||
|
ConfirmChange flag ->
|
||||||
|
if flag then
|
||||||
|
Api.confirmMultiple flags ids receive
|
||||||
|
|
||||||
|
else
|
||||||
|
Api.unconfirmMultiple flags ids receive
|
||||||
|
|
||||||
NoFormChange ->
|
NoFormChange ->
|
||||||
Cmd.none
|
Cmd.none
|
||||||
|
@ -59,7 +59,7 @@ view flags settings model =
|
|||||||
, onClick ToggleSearchMenu
|
, onClick ToggleSearchMenu
|
||||||
, title "Hide menu"
|
, title "Hide menu"
|
||||||
]
|
]
|
||||||
[ i [ class "ui angle left icon" ] []
|
[ i [ class "chevron left icon" ] []
|
||||||
]
|
]
|
||||||
, div [ class "right floated menu" ]
|
, div [ class "right floated menu" ]
|
||||||
[ a
|
[ a
|
||||||
@ -303,13 +303,9 @@ viewSearchBar flags model =
|
|||||||
[ class "search-menu-toggle ui blue icon button"
|
[ class "search-menu-toggle ui blue icon button"
|
||||||
, onClick ToggleSearchMenu
|
, onClick ToggleSearchMenu
|
||||||
, href "#"
|
, href "#"
|
||||||
, if model.searchTypeForm == ContentOnlySearch then
|
, title "Open search menu"
|
||||||
title "Search menu disabled"
|
|
||||||
|
|
||||||
else
|
|
||||||
title "Open search menu"
|
|
||||||
]
|
]
|
||||||
[ i [ class "angle right icon" ] []
|
[ i [ class "filter icon" ] []
|
||||||
]
|
]
|
||||||
, div [ class "item" ]
|
, div [ class "item" ]
|
||||||
[ div [ class "ui left icon right action input" ]
|
[ div [ class "ui left icon right action input" ]
|
||||||
|
@ -14,14 +14,14 @@ object Dependencies {
|
|||||||
val EmilVersion = "0.6.3"
|
val EmilVersion = "0.6.3"
|
||||||
val FastparseVersion = "2.1.3"
|
val FastparseVersion = "2.1.3"
|
||||||
val FlexmarkVersion = "0.62.2"
|
val FlexmarkVersion = "0.62.2"
|
||||||
val FlywayVersion = "7.1.0"
|
val FlywayVersion = "7.1.1"
|
||||||
val Fs2Version = "2.4.4"
|
val Fs2Version = "2.4.4"
|
||||||
val H2Version = "1.4.200"
|
val H2Version = "1.4.200"
|
||||||
val Http4sVersion = "0.21.8"
|
val Http4sVersion = "0.21.8"
|
||||||
val Icu4jVersion = "68.1"
|
val Icu4jVersion = "68.1"
|
||||||
val JsoupVersion = "1.13.1"
|
val JsoupVersion = "1.13.1"
|
||||||
val KindProjectorVersion = "0.10.3"
|
val KindProjectorVersion = "0.10.3"
|
||||||
val Log4sVersion = "1.8.2"
|
val Log4sVersion = "1.9.0"
|
||||||
val LogbackVersion = "1.2.3"
|
val LogbackVersion = "1.2.3"
|
||||||
val MariaDbVersion = "2.7.0"
|
val MariaDbVersion = "2.7.0"
|
||||||
val MiniTestVersion = "2.8.2"
|
val MiniTestVersion = "2.8.2"
|
||||||
@ -34,7 +34,7 @@ object Dependencies {
|
|||||||
val StanfordNlpVersion = "4.0.0"
|
val StanfordNlpVersion = "4.0.0"
|
||||||
val TikaVersion = "1.24.1"
|
val TikaVersion = "1.24.1"
|
||||||
val YamuscaVersion = "0.7.0"
|
val YamuscaVersion = "0.7.0"
|
||||||
val SwaggerUIVersion = "3.36.0"
|
val SwaggerUIVersion = "3.36.1"
|
||||||
val SemanticUIVersion = "2.4.1"
|
val SemanticUIVersion = "2.4.1"
|
||||||
val TwelveMonkeysVersion = "3.6"
|
val TwelveMonkeysVersion = "3.6"
|
||||||
val JQueryVersion = "3.5.1"
|
val JQueryVersion = "3.5.1"
|
||||||
|
179
tools/consumedir-cleaner/consumedir-cleaner.sh
Executable file
179
tools/consumedir-cleaner/consumedir-cleaner.sh
Executable file
@ -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
|
@ -87,6 +87,13 @@ The directory contains a file `docspell.conf` that you can
|
|||||||
]
|
]
|
||||||
, text "."
|
, text "."
|
||||||
]
|
]
|
||||||
|
, li []
|
||||||
|
[ text "Chat on "
|
||||||
|
, a [ href "https://gitter.im/eikek/docspell" ]
|
||||||
|
[ text "Gitter"
|
||||||
|
]
|
||||||
|
, text " for questions and feedback."
|
||||||
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -173,13 +173,12 @@ footHero model =
|
|||||||
[ text " • "
|
[ text " • "
|
||||||
]
|
]
|
||||||
, span []
|
, span []
|
||||||
[ text "© 2020 "
|
[ a
|
||||||
]
|
[ href "https://gitter.im/eikek/docspell"
|
||||||
, a
|
, target "_blank"
|
||||||
[ href "https://github.com/eikek"
|
]
|
||||||
, target "_blank"
|
[ text "Chat on Gitter"
|
||||||
]
|
]
|
||||||
[ text "@eikek"
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -104,8 +104,8 @@ view model =
|
|||||||
[ class "form"
|
[ class "form"
|
||||||
, onSubmit SubmitSearch
|
, onSubmit SubmitSearch
|
||||||
]
|
]
|
||||||
[ div [ class "dropdown field is-active is-fullwidth" ]
|
[ div [ class "dropdown field is-active is-fullwidth has-addons" ]
|
||||||
[ div [ class "control has-icons-right is-fullwidth" ]
|
[ div [ class "control is-fullwidth" ]
|
||||||
[ input
|
[ input
|
||||||
[ class "input"
|
[ class "input"
|
||||||
, type_ "text"
|
, type_ "text"
|
||||||
@ -114,7 +114,13 @@ view model =
|
|||||||
, value model.searchInput
|
, 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" ] []
|
[ img [ src "/icons/search-20.svg" ] []
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -38,6 +38,7 @@ description = "A list of features and limitations."
|
|||||||
with due dates
|
with due dates
|
||||||
- [Read your mailboxes](@/docs/webapp/scanmailbox.md) via IMAP to
|
- [Read your mailboxes](@/docs/webapp/scanmailbox.md) via IMAP to
|
||||||
import mails into docspell
|
import mails into docspell
|
||||||
|
- [Edit multiple items](@/docs/webapp/multiedit.md) at once
|
||||||
- REST server and document processing are separate applications which
|
- REST server and document processing are separate applications which
|
||||||
can be scaled-out independently
|
can be scaled-out independently
|
||||||
- Everything stored in a SQL database: PostgreSQL, MariaDB or H2
|
- Everything stored in a SQL database: PostgreSQL, MariaDB or H2
|
||||||
|
52
website/site/content/docs/tools/consumedir-cleaner.md
Normal file
52
website/site/content/docs/tools/consumedir-cleaner.md
Normal file
@ -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.
|
||||||
|
|
||||||
|
<https://github.com/eikek/docspell/tree/master/tools/consumedir-cleaner>
|
||||||
|
|
||||||
|
## 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
|
43
website/site/content/docs/tools/paperless-import.md
Normal file
43
website/site/content/docs/tools/paperless-import.md
Normal file
@ -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.
|
||||||
|
|
||||||
|
<https://github.com/eikek/docspell/tree/master/tools/import-paperless>
|
||||||
|
|
||||||
|
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!
|
Binary file not shown.
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 143 KiB |
Binary file not shown.
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 139 KiB |
@ -12,10 +12,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<span class="ml-1 mr-1">•</span>
|
<span class="ml-1 mr-1">•</span>
|
||||||
<span>
|
<span>
|
||||||
© 2020
|
<a href="https://gitter.im/eikek/docspell">Chat on Gitter</a>
|
||||||
<a href="https://github.com/eikek" target="_blank">
|
|
||||||
@eikek
|
|
||||||
</a>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user