mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-06 23:25:58 +00:00
Merge pull request #499 from eikek/search-improvements
Search improvements
This commit is contained in:
commit
9bd731e252
@ -184,7 +184,10 @@ val openapiScalaSettings = Seq(
|
|||||||
case "glob" =>
|
case "glob" =>
|
||||||
field => field.copy(typeDef = TypeDef("Glob", Imports("docspell.common.Glob")))
|
field => field.copy(typeDef = TypeDef("Glob", Imports("docspell.common.Glob")))
|
||||||
case "customfieldtype" =>
|
case "customfieldtype" =>
|
||||||
field => field.copy(typeDef = TypeDef("CustomFieldType", Imports("docspell.common.CustomFieldType")))
|
field =>
|
||||||
|
field.copy(typeDef =
|
||||||
|
TypeDef("CustomFieldType", Imports("docspell.common.CustomFieldType"))
|
||||||
|
)
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -465,6 +468,7 @@ val restserver = project
|
|||||||
Dependencies.circe ++
|
Dependencies.circe ++
|
||||||
Dependencies.pureconfig ++
|
Dependencies.pureconfig ++
|
||||||
Dependencies.yamusca ++
|
Dependencies.yamusca ++
|
||||||
|
Dependencies.kittens ++
|
||||||
Dependencies.webjars ++
|
Dependencies.webjars ++
|
||||||
Dependencies.loggingApi ++
|
Dependencies.loggingApi ++
|
||||||
Dependencies.logging.map(_ % Runtime),
|
Dependencies.logging.map(_ % Runtime),
|
||||||
@ -681,7 +685,7 @@ def packageTools(logger: Logger, dir: File, version: String): Seq[File] = {
|
|||||||
(dir ** "*")
|
(dir ** "*")
|
||||||
.filter(f => !excludes.exists(p => f.absolutePath.startsWith(p.absolutePath)))
|
.filter(f => !excludes.exists(p => f.absolutePath.startsWith(p.absolutePath)))
|
||||||
.pair(sbt.io.Path.relativeTo(dir))
|
.pair(sbt.io.Path.relativeTo(dir))
|
||||||
.map({case (f, name) => (f, s"docspell-tools-${version}/$name") })
|
.map({ case (f, name) => (f, s"docspell-tools-${version}/$name") })
|
||||||
|
|
||||||
IO.zip(
|
IO.zip(
|
||||||
Seq(
|
Seq(
|
||||||
|
@ -5033,7 +5033,8 @@ components:
|
|||||||
fullText:
|
fullText:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
A query searching the contents of documents.
|
A query searching the contents of documents. If only this
|
||||||
|
field is set, then a fulltext-only search is done.
|
||||||
corrOrg:
|
corrOrg:
|
||||||
type: string
|
type: string
|
||||||
format: ident
|
format: ident
|
||||||
|
@ -3,6 +3,7 @@ package docspell.restserver.routes
|
|||||||
import cats.data.NonEmptyList
|
import cats.data.NonEmptyList
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
import cats.Monoid
|
||||||
|
|
||||||
import docspell.backend.BackendApp
|
import docspell.backend.BackendApp
|
||||||
import docspell.backend.auth.AuthToken
|
import docspell.backend.auth.AuthToken
|
||||||
@ -10,7 +11,7 @@ import docspell.backend.ops.OCustomFields.{RemoveValue, SetValue}
|
|||||||
import docspell.backend.ops.OFulltext
|
import docspell.backend.ops.OFulltext
|
||||||
import docspell.backend.ops.OItemSearch.Batch
|
import docspell.backend.ops.OItemSearch.Batch
|
||||||
import docspell.common.syntax.all._
|
import docspell.common.syntax.all._
|
||||||
import docspell.common.{Ident, ItemState}
|
import docspell.common._
|
||||||
import docspell.restapi.model._
|
import docspell.restapi.model._
|
||||||
import docspell.restserver.Config
|
import docspell.restserver.Config
|
||||||
import docspell.restserver.conv.Conversions
|
import docspell.restserver.conv.Conversions
|
||||||
@ -51,8 +52,19 @@ object ItemRoutes {
|
|||||||
_ <- logger.ftrace(s"Got search mask: $mask")
|
_ <- logger.ftrace(s"Got search mask: $mask")
|
||||||
query = Conversions.mkQuery(mask, user.account)
|
query = Conversions.mkQuery(mask, user.account)
|
||||||
_ <- logger.ftrace(s"Running query: $query")
|
_ <- logger.ftrace(s"Running query: $query")
|
||||||
resp <- mask.fullText match {
|
resp <- mask match {
|
||||||
case Some(fq) if cfg.fullTextSearch.enabled =>
|
case SearchFulltextOnly(ftq) if cfg.fullTextSearch.enabled =>
|
||||||
|
val ftsIn = OFulltext.FtsInput(ftq.query)
|
||||||
|
for {
|
||||||
|
items <- backend.fulltext.findIndexOnly(cfg.maxNoteLength)(
|
||||||
|
ftsIn,
|
||||||
|
user.account,
|
||||||
|
Batch(mask.offset, mask.limit).restrictLimitTo(cfg.maxItemPageSize)
|
||||||
|
)
|
||||||
|
ok <- Ok(Conversions.mkItemListWithTagsFtsPlain(items))
|
||||||
|
} yield ok
|
||||||
|
|
||||||
|
case SearchWithFulltext(fq) if cfg.fullTextSearch.enabled =>
|
||||||
for {
|
for {
|
||||||
items <- backend.fulltext.findItems(cfg.maxNoteLength)(
|
items <- backend.fulltext.findItems(cfg.maxNoteLength)(
|
||||||
query,
|
query,
|
||||||
@ -61,6 +73,7 @@ object ItemRoutes {
|
|||||||
)
|
)
|
||||||
ok <- Ok(Conversions.mkItemListFts(items))
|
ok <- Ok(Conversions.mkItemListFts(items))
|
||||||
} yield ok
|
} yield ok
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
for {
|
for {
|
||||||
items <- backend.itemSearch.findItems(cfg.maxNoteLength)(
|
items <- backend.itemSearch.findItems(cfg.maxNoteLength)(
|
||||||
@ -78,8 +91,19 @@ object ItemRoutes {
|
|||||||
_ <- logger.ftrace(s"Got search mask: $mask")
|
_ <- logger.ftrace(s"Got search mask: $mask")
|
||||||
query = Conversions.mkQuery(mask, user.account)
|
query = Conversions.mkQuery(mask, user.account)
|
||||||
_ <- logger.ftrace(s"Running query: $query")
|
_ <- logger.ftrace(s"Running query: $query")
|
||||||
resp <- mask.fullText match {
|
resp <- mask match {
|
||||||
case Some(fq) if cfg.fullTextSearch.enabled =>
|
case SearchFulltextOnly(ftq) if cfg.fullTextSearch.enabled =>
|
||||||
|
val ftsIn = OFulltext.FtsInput(ftq.query)
|
||||||
|
for {
|
||||||
|
items <- backend.fulltext.findIndexOnly(cfg.maxNoteLength)(
|
||||||
|
ftsIn,
|
||||||
|
user.account,
|
||||||
|
Batch(mask.offset, mask.limit).restrictLimitTo(cfg.maxItemPageSize)
|
||||||
|
)
|
||||||
|
ok <- Ok(Conversions.mkItemListWithTagsFtsPlain(items))
|
||||||
|
} yield ok
|
||||||
|
|
||||||
|
case SearchWithFulltext(fq) if cfg.fullTextSearch.enabled =>
|
||||||
for {
|
for {
|
||||||
items <- backend.fulltext.findItemsWithTags(cfg.maxNoteLength)(
|
items <- backend.fulltext.findItemsWithTags(cfg.maxNoteLength)(
|
||||||
query,
|
query,
|
||||||
@ -390,4 +414,40 @@ object ItemRoutes {
|
|||||||
def notEmpty: Option[String] =
|
def notEmpty: Option[String] =
|
||||||
opt.map(_.trim).filter(_.nonEmpty)
|
opt.map(_.trim).filter(_.nonEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object SearchFulltextOnly {
|
||||||
|
implicit private val identMonoid: Monoid[Ident] =
|
||||||
|
Monoid.instance(Ident.unsafe(""), _ / _)
|
||||||
|
|
||||||
|
implicit private val timestampMonoid: Monoid[Timestamp] =
|
||||||
|
Monoid.instance(Timestamp.Epoch, (a, _) => a)
|
||||||
|
|
||||||
|
implicit private val directionMonoid: Monoid[Direction] =
|
||||||
|
Monoid.instance(Direction.Incoming, (a, _) => a)
|
||||||
|
|
||||||
|
implicit private val idListMonoid: Monoid[IdList] =
|
||||||
|
Monoid.instance(IdList(Nil), (a, b) => IdList(a.ids ++ b.ids))
|
||||||
|
|
||||||
|
implicit private val boolMonoid: Monoid[Boolean] =
|
||||||
|
Monoid.instance(false, _ || _)
|
||||||
|
|
||||||
|
private val itemSearchMonoid: Monoid[ItemSearch] =
|
||||||
|
cats.derived.semiauto.monoid
|
||||||
|
|
||||||
|
def unapply(m: ItemSearch): Option[ItemFtsSearch] =
|
||||||
|
m.fullText match {
|
||||||
|
case Some(fq) =>
|
||||||
|
val me = m.copy(fullText = None, offset = 0, limit = 0)
|
||||||
|
if (itemSearchMonoid.empty == me)
|
||||||
|
Some(ItemFtsSearch(m.offset, m.limit, fq))
|
||||||
|
else None
|
||||||
|
case _ =>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object SearchWithFulltext {
|
||||||
|
def unapply(m: ItemSearch): Option[String] =
|
||||||
|
m.fullText
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -335,10 +335,13 @@ personMatchesOrg model =
|
|||||||
Comp.Dropdown.getSelected model.corrOrgModel
|
Comp.Dropdown.getSelected model.corrOrgModel
|
||||||
|> List.head
|
|> List.head
|
||||||
|
|
||||||
persOrg =
|
pers =
|
||||||
Comp.Dropdown.getSelected model.corrPersonModel
|
Comp.Dropdown.getSelected model.corrPersonModel
|
||||||
|> List.head
|
|> List.head
|
||||||
|
|
||||||
|
persOrg =
|
||||||
|
pers
|
||||||
|> Maybe.andThen (\idref -> Dict.get idref.id model.allPersons)
|
|> Maybe.andThen (\idref -> Dict.get idref.id model.allPersons)
|
||||||
|> Maybe.andThen .organization
|
|> Maybe.andThen .organization
|
||||||
in
|
in
|
||||||
org == Nothing || org == persOrg
|
org == Nothing || pers == Nothing || org == persOrg
|
||||||
|
@ -2,8 +2,12 @@ module Comp.SearchMenu exposing
|
|||||||
( Model
|
( Model
|
||||||
, Msg(..)
|
, Msg(..)
|
||||||
, NextState
|
, NextState
|
||||||
|
, TextSearchModel
|
||||||
, getItemSearch
|
, getItemSearch
|
||||||
, init
|
, init
|
||||||
|
, isFulltextSearch
|
||||||
|
, isNamesSearch
|
||||||
|
, textSearchString
|
||||||
, update
|
, update
|
||||||
, updateDrop
|
, updateDrop
|
||||||
, view
|
, view
|
||||||
@ -66,18 +70,21 @@ type alias Model =
|
|||||||
, untilDueDateModel : DatePicker
|
, untilDueDateModel : DatePicker
|
||||||
, untilDueDate : Maybe Int
|
, untilDueDate : Maybe Int
|
||||||
, nameModel : Maybe String
|
, nameModel : Maybe String
|
||||||
, allNameModel : Maybe String
|
, textSearchModel : TextSearchModel
|
||||||
, fulltextModel : Maybe String
|
|
||||||
, datePickerInitialized : Bool
|
, datePickerInitialized : Bool
|
||||||
, showNameHelp : Bool
|
|
||||||
, customFieldModel : Comp.CustomFieldMultiInput.Model
|
, customFieldModel : Comp.CustomFieldMultiInput.Model
|
||||||
, customValues : CustomFieldValueCollect
|
, customValues : CustomFieldValueCollect
|
||||||
, sourceModel : Maybe String
|
, sourceModel : Maybe String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
init : Model
|
type TextSearchModel
|
||||||
init =
|
= Fulltext (Maybe String)
|
||||||
|
| Names (Maybe String)
|
||||||
|
|
||||||
|
|
||||||
|
init : Flags -> Model
|
||||||
|
init flags =
|
||||||
{ tagSelectModel = Comp.TagSelect.init Comp.TagSelect.emptySelection []
|
{ tagSelectModel = Comp.TagSelect.init Comp.TagSelect.emptySelection []
|
||||||
, tagSelection = Comp.TagSelect.emptySelection
|
, tagSelection = Comp.TagSelect.emptySelection
|
||||||
, directionModel =
|
, directionModel =
|
||||||
@ -124,16 +131,87 @@ init =
|
|||||||
, untilDueDateModel = Comp.DatePicker.emptyModel
|
, untilDueDateModel = Comp.DatePicker.emptyModel
|
||||||
, untilDueDate = Nothing
|
, untilDueDate = Nothing
|
||||||
, nameModel = Nothing
|
, nameModel = Nothing
|
||||||
, allNameModel = Nothing
|
, textSearchModel =
|
||||||
, fulltextModel = Nothing
|
if flags.config.fullTextSearchEnabled then
|
||||||
|
Fulltext Nothing
|
||||||
|
|
||||||
|
else
|
||||||
|
Names Nothing
|
||||||
, datePickerInitialized = False
|
, datePickerInitialized = False
|
||||||
, showNameHelp = False
|
|
||||||
, customFieldModel = Comp.CustomFieldMultiInput.initWith []
|
, customFieldModel = Comp.CustomFieldMultiInput.initWith []
|
||||||
, customValues = Data.CustomFieldChange.emptyCollect
|
, customValues = Data.CustomFieldChange.emptyCollect
|
||||||
, sourceModel = Nothing
|
, sourceModel = Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
updateTextSearch : String -> TextSearchModel -> TextSearchModel
|
||||||
|
updateTextSearch str model =
|
||||||
|
let
|
||||||
|
next =
|
||||||
|
Util.Maybe.fromString str
|
||||||
|
in
|
||||||
|
case model of
|
||||||
|
Fulltext _ ->
|
||||||
|
Fulltext next
|
||||||
|
|
||||||
|
Names _ ->
|
||||||
|
Names next
|
||||||
|
|
||||||
|
|
||||||
|
swapTextSearch : TextSearchModel -> TextSearchModel
|
||||||
|
swapTextSearch model =
|
||||||
|
case model of
|
||||||
|
Fulltext s ->
|
||||||
|
Names s
|
||||||
|
|
||||||
|
Names s ->
|
||||||
|
Fulltext s
|
||||||
|
|
||||||
|
|
||||||
|
textSearchValue : TextSearchModel -> { nameSearch : Maybe String, fullText : Maybe String }
|
||||||
|
textSearchValue model =
|
||||||
|
case model of
|
||||||
|
Fulltext s ->
|
||||||
|
{ nameSearch = Nothing
|
||||||
|
, fullText = s
|
||||||
|
}
|
||||||
|
|
||||||
|
Names s ->
|
||||||
|
{ nameSearch = s
|
||||||
|
, fullText = Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
textSearchString : TextSearchModel -> Maybe String
|
||||||
|
textSearchString model =
|
||||||
|
case model of
|
||||||
|
Fulltext s ->
|
||||||
|
s
|
||||||
|
|
||||||
|
Names s ->
|
||||||
|
s
|
||||||
|
|
||||||
|
|
||||||
|
isFulltextSearch : Model -> Bool
|
||||||
|
isFulltextSearch model =
|
||||||
|
case model.textSearchModel of
|
||||||
|
Fulltext _ ->
|
||||||
|
True
|
||||||
|
|
||||||
|
Names _ ->
|
||||||
|
False
|
||||||
|
|
||||||
|
|
||||||
|
isNamesSearch : Model -> Bool
|
||||||
|
isNamesSearch model =
|
||||||
|
case model.textSearchModel of
|
||||||
|
Fulltext _ ->
|
||||||
|
False
|
||||||
|
|
||||||
|
Names _ ->
|
||||||
|
True
|
||||||
|
|
||||||
|
|
||||||
getDirection : Model -> Maybe Direction
|
getDirection : Model -> Maybe Direction
|
||||||
getDirection model =
|
getDirection model =
|
||||||
let
|
let
|
||||||
@ -164,6 +242,9 @@ getItemSearch model =
|
|||||||
|
|
||||||
else
|
else
|
||||||
"*" ++ s ++ "*"
|
"*" ++ s ++ "*"
|
||||||
|
|
||||||
|
textSearch =
|
||||||
|
textSearchValue model.textSearchModel
|
||||||
in
|
in
|
||||||
{ e
|
{ e
|
||||||
| tagsInclude = model.tagSelection.includeTags |> List.map .tag |> List.map .id
|
| tagsInclude = model.tagSelection.includeTags |> List.map .tag |> List.map .id
|
||||||
@ -186,9 +267,9 @@ getItemSearch model =
|
|||||||
model.nameModel
|
model.nameModel
|
||||||
|> Maybe.map amendWildcards
|
|> Maybe.map amendWildcards
|
||||||
, allNames =
|
, allNames =
|
||||||
model.allNameModel
|
textSearch.nameSearch
|
||||||
|> Maybe.map amendWildcards
|
|> Maybe.map amendWildcards
|
||||||
, fullText = model.fulltextModel
|
, fullText = textSearch.fullText
|
||||||
, tagCategoriesInclude = model.tagSelection.includeCats |> List.map .name
|
, tagCategoriesInclude = model.tagSelection.includeCats |> List.map .name
|
||||||
, tagCategoriesExclude = model.tagSelection.excludeCats |> List.map .name
|
, tagCategoriesExclude = model.tagSelection.excludeCats |> List.map .name
|
||||||
, customValues = Data.CustomFieldChange.toFieldValues model.customValues
|
, customValues = Data.CustomFieldChange.toFieldValues model.customValues
|
||||||
@ -225,8 +306,13 @@ resetModel model =
|
|||||||
, fromDueDate = Nothing
|
, fromDueDate = Nothing
|
||||||
, untilDueDate = Nothing
|
, untilDueDate = Nothing
|
||||||
, nameModel = Nothing
|
, nameModel = Nothing
|
||||||
, allNameModel = Nothing
|
, textSearchModel =
|
||||||
, fulltextModel = Nothing
|
case model.textSearchModel of
|
||||||
|
Fulltext _ ->
|
||||||
|
Fulltext Nothing
|
||||||
|
|
||||||
|
Names _ ->
|
||||||
|
Names Nothing
|
||||||
, customFieldModel =
|
, customFieldModel =
|
||||||
Comp.CustomFieldMultiInput.reset
|
Comp.CustomFieldMultiInput.reset
|
||||||
model.customFieldModel
|
model.customFieldModel
|
||||||
@ -257,11 +343,12 @@ type Msg
|
|||||||
| GetEquipResp (Result Http.Error EquipmentList)
|
| GetEquipResp (Result Http.Error EquipmentList)
|
||||||
| GetPersonResp (Result Http.Error PersonList)
|
| GetPersonResp (Result Http.Error PersonList)
|
||||||
| SetName String
|
| SetName String
|
||||||
| SetAllName String
|
| SetTextSearch String
|
||||||
| SetFulltext String
|
| SwapTextSearch
|
||||||
|
| SetFulltextSearch
|
||||||
|
| SetNamesSearch
|
||||||
| ResetForm
|
| ResetForm
|
||||||
| KeyUpMsg (Maybe KeyCode)
|
| KeyUpMsg (Maybe KeyCode)
|
||||||
| ToggleNameHelp
|
|
||||||
| FolderSelectMsg Comp.FolderSelect.Msg
|
| FolderSelectMsg Comp.FolderSelect.Msg
|
||||||
| GetFolderResp (Result Http.Error FolderList)
|
| GetFolderResp (Result Http.Error FolderList)
|
||||||
| SetCorrOrg IdName
|
| SetCorrOrg IdName
|
||||||
@ -641,27 +728,59 @@ updateDrop ddm flags settings msg model =
|
|||||||
, dragDrop = DD.DragDropData ddm Nothing
|
, dragDrop = DD.DragDropData ddm Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
SetAllName str ->
|
SetTextSearch str ->
|
||||||
let
|
{ model = { model | textSearchModel = updateTextSearch str model.textSearchModel }
|
||||||
next =
|
|
||||||
Util.Maybe.fromString str
|
|
||||||
in
|
|
||||||
{ model = { model | allNameModel = next }
|
|
||||||
, cmd = Cmd.none
|
, cmd = Cmd.none
|
||||||
, stateChange = False
|
, stateChange = False
|
||||||
, dragDrop = DD.DragDropData ddm Nothing
|
, dragDrop = DD.DragDropData ddm Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
SetFulltext str ->
|
SwapTextSearch ->
|
||||||
let
|
if flags.config.fullTextSearchEnabled then
|
||||||
next =
|
{ model = { model | textSearchModel = swapTextSearch model.textSearchModel }
|
||||||
Util.Maybe.fromString str
|
, cmd = Cmd.none
|
||||||
in
|
, stateChange = False
|
||||||
{ model = { model | fulltextModel = next }
|
, dragDrop = DD.DragDropData ddm Nothing
|
||||||
, cmd = Cmd.none
|
}
|
||||||
, stateChange = False
|
|
||||||
, dragDrop = DD.DragDropData ddm Nothing
|
else
|
||||||
}
|
{ model = model
|
||||||
|
, cmd = Cmd.none
|
||||||
|
, stateChange = False
|
||||||
|
, dragDrop = DD.DragDropData ddm Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
SetFulltextSearch ->
|
||||||
|
case model.textSearchModel of
|
||||||
|
Fulltext _ ->
|
||||||
|
{ model = model
|
||||||
|
, cmd = Cmd.none
|
||||||
|
, stateChange = False
|
||||||
|
, dragDrop = DD.DragDropData ddm Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
Names s ->
|
||||||
|
{ model = { model | textSearchModel = Fulltext s }
|
||||||
|
, cmd = Cmd.none
|
||||||
|
, stateChange = False
|
||||||
|
, dragDrop = DD.DragDropData ddm Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
SetNamesSearch ->
|
||||||
|
case model.textSearchModel of
|
||||||
|
Fulltext s ->
|
||||||
|
{ model = { model | textSearchModel = Names s }
|
||||||
|
, cmd = Cmd.none
|
||||||
|
, stateChange = False
|
||||||
|
, dragDrop = DD.DragDropData ddm Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
Names _ ->
|
||||||
|
{ model = model
|
||||||
|
, cmd = Cmd.none
|
||||||
|
, stateChange = False
|
||||||
|
, dragDrop = DD.DragDropData ddm Nothing
|
||||||
|
}
|
||||||
|
|
||||||
KeyUpMsg (Just Enter) ->
|
KeyUpMsg (Just Enter) ->
|
||||||
{ model = model
|
{ model = model
|
||||||
@ -677,13 +796,6 @@ updateDrop ddm flags settings msg model =
|
|||||||
, dragDrop = DD.DragDropData ddm Nothing
|
, dragDrop = DD.DragDropData ddm Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
ToggleNameHelp ->
|
|
||||||
{ model = { model | showNameHelp = not model.showNameHelp }
|
|
||||||
, cmd = Cmd.none
|
|
||||||
, stateChange = False
|
|
||||||
, dragDrop = DD.DragDropData ddm Nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
GetFolderResp (Ok fs) ->
|
GetFolderResp (Ok fs) ->
|
||||||
let
|
let
|
||||||
model_ =
|
model_ =
|
||||||
@ -804,6 +916,54 @@ viewDrop ddd flags settings model =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
, div [ class segmentClass ]
|
||||||
|
[ div
|
||||||
|
[ class "field"
|
||||||
|
]
|
||||||
|
[ label []
|
||||||
|
[ text
|
||||||
|
(case model.textSearchModel of
|
||||||
|
Fulltext _ ->
|
||||||
|
"Fulltext Search"
|
||||||
|
|
||||||
|
Names _ ->
|
||||||
|
"Search in names"
|
||||||
|
)
|
||||||
|
, a
|
||||||
|
[ classList
|
||||||
|
[ ( "right-float", True )
|
||||||
|
, ( "invisible hidden", not flags.config.fullTextSearchEnabled )
|
||||||
|
]
|
||||||
|
, href "#"
|
||||||
|
, onClick SwapTextSearch
|
||||||
|
, title "Switch between text search modes"
|
||||||
|
]
|
||||||
|
[ i [ class "small grey exchange alternate icon" ] []
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, input
|
||||||
|
[ type_ "text"
|
||||||
|
, onInput SetTextSearch
|
||||||
|
, Util.Html.onKeyUpCode KeyUpMsg
|
||||||
|
, textSearchString model.textSearchModel |> Maybe.withDefault "" |> value
|
||||||
|
, case model.textSearchModel of
|
||||||
|
Fulltext _ ->
|
||||||
|
placeholder "Content search…"
|
||||||
|
|
||||||
|
Names _ ->
|
||||||
|
placeholder "Search in various names…"
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, span [ class "small-info" ]
|
||||||
|
[ case model.textSearchModel of
|
||||||
|
Fulltext _ ->
|
||||||
|
text "Fulltext search in document contents and notes."
|
||||||
|
|
||||||
|
Names _ ->
|
||||||
|
text "Looks in correspondents, concerned entities, item name and notes."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
, div
|
, div
|
||||||
[ classList
|
[ classList
|
||||||
[ ( segmentClass, True )
|
[ ( segmentClass, True )
|
||||||
@ -883,68 +1043,6 @@ viewDrop ddd flags settings model =
|
|||||||
model.customFieldModel
|
model.customFieldModel
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
, div [ class segmentClass ]
|
|
||||||
[ formHeader (Icons.searchIcon "") "Text Search"
|
|
||||||
, div
|
|
||||||
[ classList
|
|
||||||
[ ( "field", True )
|
|
||||||
, ( "invisible hidden", not flags.config.fullTextSearchEnabled )
|
|
||||||
]
|
|
||||||
]
|
|
||||||
[ label [] [ text "Fulltext Search" ]
|
|
||||||
, input
|
|
||||||
[ type_ "text"
|
|
||||||
, onInput SetFulltext
|
|
||||||
, Util.Html.onKeyUpCode KeyUpMsg
|
|
||||||
, model.fulltextModel |> Maybe.withDefault "" |> value
|
|
||||||
, placeholder "Fulltext search in results…"
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
, span [ class "small-info" ]
|
|
||||||
[ text "Fulltext search in document contents and notes."
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div [ class "field" ]
|
|
||||||
[ label []
|
|
||||||
[ text "Names"
|
|
||||||
, a
|
|
||||||
[ class "right-float"
|
|
||||||
, href "#"
|
|
||||||
, onClick ToggleNameHelp
|
|
||||||
]
|
|
||||||
[ i [ class "small grey help link icon" ] []
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, input
|
|
||||||
[ type_ "text"
|
|
||||||
, onInput SetAllName
|
|
||||||
, Util.Html.onKeyUpCode KeyUpMsg
|
|
||||||
, model.allNameModel |> Maybe.withDefault "" |> value
|
|
||||||
, placeholder "Search in various names…"
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
, span
|
|
||||||
[ classList
|
|
||||||
[ ( "small-info", True )
|
|
||||||
]
|
|
||||||
]
|
|
||||||
[ text "Looks in correspondents, concerned entities, item name and notes."
|
|
||||||
]
|
|
||||||
, p
|
|
||||||
[ classList
|
|
||||||
[ ( "small-info", True )
|
|
||||||
, ( "invisible hidden", not model.showNameHelp )
|
|
||||||
]
|
|
||||||
]
|
|
||||||
[ text "Use wildcards "
|
|
||||||
, code [] [ text "*" ]
|
|
||||||
, text " at beginning or end. They are added automatically on both sides "
|
|
||||||
, text "if not present in the search term and the term is not quoted. Press "
|
|
||||||
, em [] [ text "Enter" ]
|
|
||||||
, text " to start searching."
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, div
|
, div
|
||||||
[ classList
|
[ classList
|
||||||
[ ( segmentClass, True )
|
[ ( segmentClass, True )
|
||||||
|
@ -6,7 +6,6 @@ module Page.Home.Data exposing
|
|||||||
, SelectActionMode(..)
|
, SelectActionMode(..)
|
||||||
, SelectViewModel
|
, SelectViewModel
|
||||||
, ViewMode(..)
|
, ViewMode(..)
|
||||||
, defaultSearchType
|
|
||||||
, doSearchCmd
|
, doSearchCmd
|
||||||
, init
|
, init
|
||||||
, initSelectViewModel
|
, initSelectViewModel
|
||||||
@ -20,7 +19,6 @@ module Page.Home.Data exposing
|
|||||||
import Api
|
import Api
|
||||||
import Api.Model.BasicResult exposing (BasicResult)
|
import Api.Model.BasicResult exposing (BasicResult)
|
||||||
import Api.Model.ItemLightList exposing (ItemLightList)
|
import Api.Model.ItemLightList exposing (ItemLightList)
|
||||||
import Api.Model.ItemSearch
|
|
||||||
import Browser.Dom as Dom
|
import Browser.Dom as Dom
|
||||||
import Comp.FixedDropdown
|
import Comp.FixedDropdown
|
||||||
import Comp.ItemCardList
|
import Comp.ItemCardList
|
||||||
@ -52,7 +50,6 @@ type alias Model =
|
|||||||
, searchTypeDropdown : Comp.FixedDropdown.Model SearchType
|
, searchTypeDropdown : Comp.FixedDropdown.Model SearchType
|
||||||
, searchTypeDropdownValue : SearchType
|
, searchTypeDropdownValue : SearchType
|
||||||
, lastSearchType : SearchType
|
, lastSearchType : SearchType
|
||||||
, contentOnlySearch : Maybe String
|
|
||||||
, dragDropData : DD.DragDropData
|
, dragDropData : DD.DragDropData
|
||||||
, scrollToCard : Maybe String
|
, scrollToCard : Maybe String
|
||||||
}
|
}
|
||||||
@ -88,6 +85,9 @@ type ViewMode
|
|||||||
init : Flags -> ViewMode -> Model
|
init : Flags -> ViewMode -> Model
|
||||||
init flags viewMode =
|
init flags viewMode =
|
||||||
let
|
let
|
||||||
|
searchMenuModel =
|
||||||
|
Comp.SearchMenu.init flags
|
||||||
|
|
||||||
searchTypeOptions =
|
searchTypeOptions =
|
||||||
if flags.config.fullTextSearchEnabled then
|
if flags.config.fullTextSearchEnabled then
|
||||||
[ BasicSearch, ContentOnlySearch ]
|
[ BasicSearch, ContentOnlySearch ]
|
||||||
@ -95,7 +95,7 @@ init flags viewMode =
|
|||||||
else
|
else
|
||||||
[ BasicSearch ]
|
[ BasicSearch ]
|
||||||
in
|
in
|
||||||
{ searchMenuModel = Comp.SearchMenu.init
|
{ searchMenuModel = searchMenuModel
|
||||||
, itemListModel = Comp.ItemCardList.init
|
, itemListModel = Comp.ItemCardList.init
|
||||||
, searchInProgress = False
|
, searchInProgress = False
|
||||||
, searchOffset = 0
|
, searchOffset = 0
|
||||||
@ -105,9 +105,13 @@ init flags viewMode =
|
|||||||
, searchTypeDropdown =
|
, searchTypeDropdown =
|
||||||
Comp.FixedDropdown.initMap searchTypeString
|
Comp.FixedDropdown.initMap searchTypeString
|
||||||
searchTypeOptions
|
searchTypeOptions
|
||||||
, searchTypeDropdownValue = defaultSearchType flags
|
, searchTypeDropdownValue =
|
||||||
|
if Comp.SearchMenu.isFulltextSearch searchMenuModel then
|
||||||
|
ContentOnlySearch
|
||||||
|
|
||||||
|
else
|
||||||
|
BasicSearch
|
||||||
, lastSearchType = BasicSearch
|
, lastSearchType = BasicSearch
|
||||||
, contentOnlySearch = Nothing
|
|
||||||
, dragDropData =
|
, dragDropData =
|
||||||
DD.DragDropData DD.init Nothing
|
DD.DragDropData DD.init Nothing
|
||||||
, scrollToCard = Nothing
|
, scrollToCard = Nothing
|
||||||
@ -115,15 +119,6 @@ init flags viewMode =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
defaultSearchType : Flags -> SearchType
|
|
||||||
defaultSearchType flags =
|
|
||||||
if flags.config.fullTextSearchEnabled then
|
|
||||||
ContentOnlySearch
|
|
||||||
|
|
||||||
else
|
|
||||||
BasicSearch
|
|
||||||
|
|
||||||
|
|
||||||
menuCollapsed : Model -> Bool
|
menuCollapsed : Model -> Bool
|
||||||
menuCollapsed model =
|
menuCollapsed model =
|
||||||
case model.viewMode of
|
case model.viewMode of
|
||||||
@ -165,7 +160,6 @@ type Msg
|
|||||||
| SetBasicSearch String
|
| SetBasicSearch String
|
||||||
| SearchTypeMsg (Comp.FixedDropdown.Msg SearchType)
|
| SearchTypeMsg (Comp.FixedDropdown.Msg SearchType)
|
||||||
| KeyUpSearchbarMsg (Maybe KeyCode)
|
| KeyUpSearchbarMsg (Maybe KeyCode)
|
||||||
| SetContentOnly String
|
|
||||||
| ScrollResult (Result Dom.Error ())
|
| ScrollResult (Result Dom.Error ())
|
||||||
| ClearItemDetailId
|
| ClearItemDetailId
|
||||||
| SelectAllItems
|
| SelectAllItems
|
||||||
@ -227,12 +221,7 @@ itemNav id model =
|
|||||||
|
|
||||||
doSearchCmd : SearchParam -> Model -> Cmd Msg
|
doSearchCmd : SearchParam -> Model -> Cmd Msg
|
||||||
doSearchCmd param model =
|
doSearchCmd param model =
|
||||||
case param.searchType of
|
doSearchDefaultCmd param model
|
||||||
BasicSearch ->
|
|
||||||
doSearchDefaultCmd param model
|
|
||||||
|
|
||||||
ContentOnlySearch ->
|
|
||||||
doSearchIndexCmd param model
|
|
||||||
|
|
||||||
|
|
||||||
doSearchDefaultCmd : SearchParam -> Model -> Cmd Msg
|
doSearchDefaultCmd : SearchParam -> Model -> Cmd Msg
|
||||||
@ -254,36 +243,6 @@ doSearchDefaultCmd param model =
|
|||||||
Api.itemSearch param.flags mask ItemSearchAddResp
|
Api.itemSearch param.flags mask ItemSearchAddResp
|
||||||
|
|
||||||
|
|
||||||
doSearchIndexCmd : SearchParam -> Model -> Cmd Msg
|
|
||||||
doSearchIndexCmd param model =
|
|
||||||
case model.contentOnlySearch of
|
|
||||||
Just q ->
|
|
||||||
let
|
|
||||||
mask =
|
|
||||||
{ query = q
|
|
||||||
, limit = param.pageSize
|
|
||||||
, offset = param.offset
|
|
||||||
}
|
|
||||||
in
|
|
||||||
if param.offset == 0 then
|
|
||||||
Api.itemIndexSearch param.flags mask (ItemSearchResp param.scroll)
|
|
||||||
|
|
||||||
else
|
|
||||||
Api.itemIndexSearch param.flags mask ItemSearchAddResp
|
|
||||||
|
|
||||||
Nothing ->
|
|
||||||
-- If there is no fulltext query, render simply the most
|
|
||||||
-- current ones
|
|
||||||
let
|
|
||||||
emptyMask =
|
|
||||||
Api.Model.ItemSearch.empty
|
|
||||||
|
|
||||||
mask =
|
|
||||||
{ emptyMask | limit = param.pageSize }
|
|
||||||
in
|
|
||||||
Api.itemSearch param.flags mask (ItemSearchResp param.scroll)
|
|
||||||
|
|
||||||
|
|
||||||
resultsBelowLimit : UiSettings -> Model -> Bool
|
resultsBelowLimit : UiSettings -> Model -> Bool
|
||||||
resultsBelowLimit settings model =
|
resultsBelowLimit settings model =
|
||||||
let
|
let
|
||||||
|
@ -52,10 +52,7 @@ update mId key flags settings msg model =
|
|||||||
ResetSearch ->
|
ResetSearch ->
|
||||||
let
|
let
|
||||||
nm =
|
nm =
|
||||||
{ model
|
{ model | searchOffset = 0 }
|
||||||
| searchOffset = 0
|
|
||||||
, contentOnlySearch = Nothing
|
|
||||||
}
|
|
||||||
in
|
in
|
||||||
update mId key flags settings (SearchMenuMsg Comp.SearchMenu.ResetForm) nm
|
update mId key flags settings (SearchMenuMsg Comp.SearchMenu.ResetForm) nm
|
||||||
|
|
||||||
@ -76,6 +73,12 @@ update mId key flags settings msg model =
|
|||||||
{ model
|
{ model
|
||||||
| searchMenuModel = nextState.model
|
| searchMenuModel = nextState.model
|
||||||
, dragDropData = nextState.dragDrop
|
, dragDropData = nextState.dragDrop
|
||||||
|
, searchTypeDropdownValue =
|
||||||
|
if Comp.SearchMenu.isFulltextSearch nextState.model then
|
||||||
|
ContentOnlySearch
|
||||||
|
|
||||||
|
else
|
||||||
|
BasicSearch
|
||||||
}
|
}
|
||||||
|
|
||||||
( m2, c2, s2 ) =
|
( m2, c2, s2 ) =
|
||||||
@ -261,21 +264,10 @@ update mId key flags settings msg model =
|
|||||||
SetBasicSearch str ->
|
SetBasicSearch str ->
|
||||||
let
|
let
|
||||||
smMsg =
|
smMsg =
|
||||||
case model.searchTypeDropdownValue of
|
SearchMenuMsg (Comp.SearchMenu.SetTextSearch str)
|
||||||
BasicSearch ->
|
|
||||||
SearchMenuMsg (Comp.SearchMenu.SetAllName str)
|
|
||||||
|
|
||||||
ContentOnlySearch ->
|
|
||||||
SetContentOnly str
|
|
||||||
in
|
in
|
||||||
update mId key flags settings smMsg model
|
update mId key flags settings smMsg model
|
||||||
|
|
||||||
SetContentOnly str ->
|
|
||||||
withSub
|
|
||||||
( { model | contentOnlySearch = Util.Maybe.fromString str }
|
|
||||||
, Cmd.none
|
|
||||||
)
|
|
||||||
|
|
||||||
SearchTypeMsg lm ->
|
SearchTypeMsg lm ->
|
||||||
let
|
let
|
||||||
( sm, mv ) =
|
( sm, mv ) =
|
||||||
@ -293,23 +285,17 @@ update mId key flags settings msg model =
|
|||||||
next =
|
next =
|
||||||
case mvChange of
|
case mvChange of
|
||||||
Just BasicSearch ->
|
Just BasicSearch ->
|
||||||
Just
|
Just Comp.SearchMenu.SetNamesSearch
|
||||||
( { m0 | contentOnlySearch = Nothing }
|
|
||||||
, Maybe.withDefault "" model.contentOnlySearch
|
|
||||||
)
|
|
||||||
|
|
||||||
Just ContentOnlySearch ->
|
Just ContentOnlySearch ->
|
||||||
Just
|
Just Comp.SearchMenu.SetFulltextSearch
|
||||||
( { m0 | contentOnlySearch = model.searchMenuModel.allNameModel }
|
|
||||||
, ""
|
|
||||||
)
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
Nothing
|
Nothing
|
||||||
in
|
in
|
||||||
case next of
|
case next of
|
||||||
Just ( m_, nstr ) ->
|
Just lm_ ->
|
||||||
update mId key flags settings (SearchMenuMsg (Comp.SearchMenu.SetAllName nstr)) m_
|
update mId key flags settings (SearchMenuMsg lm_) m0
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
withSub ( m0, Cmd.none )
|
withSub ( m0, Cmd.none )
|
||||||
|
@ -285,12 +285,8 @@ viewSearchBar flags model =
|
|||||||
(searchTypeString model.searchTypeDropdownValue)
|
(searchTypeString model.searchTypeDropdownValue)
|
||||||
|
|
||||||
searchInput =
|
searchInput =
|
||||||
case model.searchTypeDropdownValue of
|
Comp.SearchMenu.textSearchString
|
||||||
BasicSearch ->
|
model.searchMenuModel.textSearchModel
|
||||||
model.searchMenuModel.allNameModel
|
|
||||||
|
|
||||||
ContentOnlySearch ->
|
|
||||||
model.contentOnlySearch
|
|
||||||
|
|
||||||
searchTypeClass =
|
searchTypeClass =
|
||||||
if flags.config.fullTextSearchEnabled then
|
if flags.config.fullTextSearchEnabled then
|
||||||
@ -328,7 +324,7 @@ viewSearchBar flags model =
|
|||||||
, href "#"
|
, href "#"
|
||||||
, onClick (DoSearch model.searchTypeDropdownValue)
|
, onClick (DoSearch model.searchTypeDropdownValue)
|
||||||
]
|
]
|
||||||
(if hasMoreSearch model && model.searchTypeDropdownValue == BasicSearch then
|
(if hasMoreSearch model then
|
||||||
[ i [ class "icons search-corner-icons" ]
|
[ i [ class "icons search-corner-icons" ]
|
||||||
[ i [ class "tiny blue circle icon" ] []
|
[ i [ class "tiny blue circle icon" ] []
|
||||||
]
|
]
|
||||||
@ -339,7 +335,14 @@ viewSearchBar flags model =
|
|||||||
)
|
)
|
||||||
, input
|
, input
|
||||||
[ type_ "text"
|
[ type_ "text"
|
||||||
, placeholder "Quick Search …"
|
, placeholder
|
||||||
|
(case model.searchTypeDropdownValue of
|
||||||
|
ContentOnlySearch ->
|
||||||
|
"Content search…"
|
||||||
|
|
||||||
|
BasicSearch ->
|
||||||
|
"Search in names…"
|
||||||
|
)
|
||||||
, onInput SetBasicSearch
|
, onInput SetBasicSearch
|
||||||
, Util.Html.onKeyUpCode KeyUpSearchbarMsg
|
, Util.Html.onKeyUpCode KeyUpSearchbarMsg
|
||||||
, Maybe.map value searchInput
|
, Maybe.map value searchInput
|
||||||
@ -381,12 +384,7 @@ hasMoreSearch model =
|
|||||||
Comp.SearchMenu.getItemSearch model.searchMenuModel
|
Comp.SearchMenu.getItemSearch model.searchMenuModel
|
||||||
|
|
||||||
is_ =
|
is_ =
|
||||||
case model.lastSearchType of
|
{ is | allNames = Nothing, fullText = Nothing }
|
||||||
BasicSearch ->
|
|
||||||
{ is | allNames = Nothing }
|
|
||||||
|
|
||||||
ContentOnlySearch ->
|
|
||||||
Api.Model.ItemSearch.empty
|
|
||||||
in
|
in
|
||||||
is_ /= Api.Model.ItemSearch.empty
|
is_ /= Api.Model.ItemSearch.empty
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ object Dependencies {
|
|||||||
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 KittensVersion = "2.2.0"
|
||||||
val LevigoJbig2Version = "2.0"
|
val LevigoJbig2Version = "2.0"
|
||||||
val Log4sVersion = "1.9.0"
|
val Log4sVersion = "1.9.0"
|
||||||
val LogbackVersion = "1.2.3"
|
val LogbackVersion = "1.2.3"
|
||||||
@ -41,6 +42,11 @@ object Dependencies {
|
|||||||
val JQueryVersion = "3.5.1"
|
val JQueryVersion = "3.5.1"
|
||||||
val ViewerJSVersion = "0.5.8"
|
val ViewerJSVersion = "0.5.8"
|
||||||
|
|
||||||
|
|
||||||
|
val kittens = Seq(
|
||||||
|
"org.typelevel" %% "kittens" % KittensVersion
|
||||||
|
)
|
||||||
|
|
||||||
val calevCore = Seq(
|
val calevCore = Seq(
|
||||||
"com.github.eikek" %% "calev-core" % CalevVersion
|
"com.github.eikek" %% "calev-core" % CalevVersion
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user