mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-05 22:55:58 +00:00
commit
17e072ef6e
@ -15,20 +15,20 @@ import docspell.store.records.RJob
|
|||||||
|
|
||||||
trait OFulltext[F[_]] {
|
trait OFulltext[F[_]] {
|
||||||
|
|
||||||
def findItems(
|
def findItems(maxNoteLen: Int)(
|
||||||
q: Query,
|
q: Query,
|
||||||
fts: OFulltext.FtsInput,
|
fts: OFulltext.FtsInput,
|
||||||
batch: Batch
|
batch: Batch
|
||||||
): F[Vector[OFulltext.FtsItem]]
|
): F[Vector[OFulltext.FtsItem]]
|
||||||
|
|
||||||
/** Same as `findItems` but does more queries per item to find all tags. */
|
/** Same as `findItems` but does more queries per item to find all tags. */
|
||||||
def findItemsWithTags(
|
def findItemsWithTags(maxNoteLen: Int)(
|
||||||
q: Query,
|
q: Query,
|
||||||
fts: OFulltext.FtsInput,
|
fts: OFulltext.FtsInput,
|
||||||
batch: Batch
|
batch: Batch
|
||||||
): F[Vector[OFulltext.FtsItemWithTags]]
|
): F[Vector[OFulltext.FtsItemWithTags]]
|
||||||
|
|
||||||
def findIndexOnly(
|
def findIndexOnly(maxNoteLen: Int)(
|
||||||
fts: OFulltext.FtsInput,
|
fts: OFulltext.FtsInput,
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
batch: Batch
|
batch: Batch
|
||||||
@ -92,7 +92,7 @@ object OFulltext {
|
|||||||
else queue.insertIfNew(job) *> joex.notifyAllNodes
|
else queue.insertIfNew(job) *> joex.notifyAllNodes
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
def findIndexOnly(
|
def findIndexOnly(maxNoteLen: Int)(
|
||||||
ftsQ: OFulltext.FtsInput,
|
ftsQ: OFulltext.FtsInput,
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
batch: Batch
|
batch: Batch
|
||||||
@ -120,7 +120,7 @@ object OFulltext {
|
|||||||
.transact(
|
.transact(
|
||||||
QItem.findItemsWithTags(
|
QItem.findItemsWithTags(
|
||||||
account.collective,
|
account.collective,
|
||||||
QItem.findSelectedItems(QItem.Query.empty(account), select)
|
QItem.findSelectedItems(QItem.Query.empty(account), maxNoteLen, select)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.take(batch.limit.toLong)
|
.take(batch.limit.toLong)
|
||||||
@ -133,15 +133,23 @@ object OFulltext {
|
|||||||
} yield res
|
} yield res
|
||||||
}
|
}
|
||||||
|
|
||||||
def findItems(q: Query, ftsQ: FtsInput, batch: Batch): F[Vector[FtsItem]] =
|
def findItems(
|
||||||
findItemsFts(q, ftsQ, batch.first, itemSearch.findItems, convertFtsData[ListItem])
|
maxNoteLen: Int
|
||||||
|
)(q: Query, ftsQ: FtsInput, batch: Batch): F[Vector[FtsItem]] =
|
||||||
|
findItemsFts(
|
||||||
|
q,
|
||||||
|
ftsQ,
|
||||||
|
batch.first,
|
||||||
|
itemSearch.findItems(maxNoteLen),
|
||||||
|
convertFtsData[ListItem]
|
||||||
|
)
|
||||||
.drop(batch.offset.toLong)
|
.drop(batch.offset.toLong)
|
||||||
.take(batch.limit.toLong)
|
.take(batch.limit.toLong)
|
||||||
.map({ case (li, fd) => FtsItem(li, fd) })
|
.map({ case (li, fd) => FtsItem(li, fd) })
|
||||||
.compile
|
.compile
|
||||||
.toVector
|
.toVector
|
||||||
|
|
||||||
def findItemsWithTags(
|
def findItemsWithTags(maxNoteLen: Int)(
|
||||||
q: Query,
|
q: Query,
|
||||||
ftsQ: FtsInput,
|
ftsQ: FtsInput,
|
||||||
batch: Batch
|
batch: Batch
|
||||||
@ -150,7 +158,7 @@ object OFulltext {
|
|||||||
q,
|
q,
|
||||||
ftsQ,
|
ftsQ,
|
||||||
batch.first,
|
batch.first,
|
||||||
itemSearch.findItemsWithTags,
|
itemSearch.findItemsWithTags(maxNoteLen),
|
||||||
convertFtsData[ListItemWithTags]
|
convertFtsData[ListItemWithTags]
|
||||||
)
|
)
|
||||||
.drop(batch.offset.toLong)
|
.drop(batch.offset.toLong)
|
||||||
|
@ -17,10 +17,12 @@ import doobie.implicits._
|
|||||||
trait OItemSearch[F[_]] {
|
trait OItemSearch[F[_]] {
|
||||||
def findItem(id: Ident, collective: Ident): F[Option[ItemData]]
|
def findItem(id: Ident, collective: Ident): F[Option[ItemData]]
|
||||||
|
|
||||||
def findItems(q: Query, batch: Batch): F[Vector[ListItem]]
|
def findItems(maxNoteLen: Int)(q: Query, batch: Batch): F[Vector[ListItem]]
|
||||||
|
|
||||||
/** Same as `findItems` but does more queries per item to find all tags. */
|
/** Same as `findItems` but does more queries per item to find all tags. */
|
||||||
def findItemsWithTags(q: Query, batch: Batch): F[Vector[ListItemWithTags]]
|
def findItemsWithTags(
|
||||||
|
maxNoteLen: Int
|
||||||
|
)(q: Query, batch: Batch): F[Vector[ListItemWithTags]]
|
||||||
|
|
||||||
def findAttachment(id: Ident, collective: Ident): F[Option[AttachmentData[F]]]
|
def findAttachment(id: Ident, collective: Ident): F[Option[AttachmentData[F]]]
|
||||||
|
|
||||||
@ -97,14 +99,16 @@ object OItemSearch {
|
|||||||
.transact(QItem.findItem(id))
|
.transact(QItem.findItem(id))
|
||||||
.map(opt => opt.flatMap(_.filterCollective(collective)))
|
.map(opt => opt.flatMap(_.filterCollective(collective)))
|
||||||
|
|
||||||
def findItems(q: Query, batch: Batch): F[Vector[ListItem]] =
|
def findItems(maxNoteLen: Int)(q: Query, batch: Batch): F[Vector[ListItem]] =
|
||||||
store
|
store
|
||||||
.transact(QItem.findItems(q, batch).take(batch.limit.toLong))
|
.transact(QItem.findItems(q, maxNoteLen, batch).take(batch.limit.toLong))
|
||||||
.compile
|
.compile
|
||||||
.toVector
|
.toVector
|
||||||
|
|
||||||
def findItemsWithTags(q: Query, batch: Batch): F[Vector[ListItemWithTags]] = {
|
def findItemsWithTags(
|
||||||
val search = QItem.findItems(q, batch)
|
maxNoteLen: Int
|
||||||
|
)(q: Query, batch: Batch): F[Vector[ListItemWithTags]] = {
|
||||||
|
val search = QItem.findItems(q, maxNoteLen: Int, batch)
|
||||||
store
|
store
|
||||||
.transact(
|
.transact(
|
||||||
QItem.findItemsWithTags(q.account.collective, search).take(batch.limit.toLong)
|
QItem.findItemsWithTags(q.account.collective, search).take(batch.limit.toLong)
|
||||||
|
@ -82,7 +82,7 @@ object NotifyDueItemsTask {
|
|||||||
)
|
)
|
||||||
res <-
|
res <-
|
||||||
ctx.store
|
ctx.store
|
||||||
.transact(QItem.findItems(q, Batch.limit(maxItems)).take(maxItems.toLong))
|
.transact(QItem.findItems(q, 0, Batch.limit(maxItems)).take(maxItems.toLong))
|
||||||
.compile
|
.compile
|
||||||
.toVector
|
.toVector
|
||||||
} yield res
|
} yield res
|
||||||
|
@ -3860,6 +3860,10 @@ components:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/Tag"
|
$ref: "#/components/schemas/Tag"
|
||||||
|
notes:
|
||||||
|
description: |
|
||||||
|
Some prefix of the item notes.
|
||||||
|
type: string
|
||||||
highlighting:
|
highlighting:
|
||||||
description: |
|
description: |
|
||||||
Optional contextual information of a search query. Each
|
Optional contextual information of a search query. Each
|
||||||
|
@ -24,6 +24,12 @@ docspell.server {
|
|||||||
# depending on the available resources.
|
# depending on the available resources.
|
||||||
max-item-page-size = 200
|
max-item-page-size = 200
|
||||||
|
|
||||||
|
# The number of characters to return for each item notes when
|
||||||
|
# searching. Item notes may be very long, when returning them with
|
||||||
|
# all the results from a search, they add quite some data to return.
|
||||||
|
# In order to keep this low, a limit can be defined here.
|
||||||
|
max-note-length = 180
|
||||||
|
|
||||||
# Authentication.
|
# Authentication.
|
||||||
auth {
|
auth {
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ case class Config(
|
|||||||
auth: Login.Config,
|
auth: Login.Config,
|
||||||
integrationEndpoint: Config.IntegrationEndpoint,
|
integrationEndpoint: Config.IntegrationEndpoint,
|
||||||
maxItemPageSize: Int,
|
maxItemPageSize: Int,
|
||||||
|
maxNoteLength: Int,
|
||||||
fullTextSearch: Config.FullTextSearch
|
fullTextSearch: Config.FullTextSearch
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -197,6 +197,7 @@ trait Conversions {
|
|||||||
i.folder.map(mkIdName),
|
i.folder.map(mkIdName),
|
||||||
i.fileCount,
|
i.fileCount,
|
||||||
Nil,
|
Nil,
|
||||||
|
i.notes,
|
||||||
Nil
|
Nil
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ object ItemRoutes {
|
|||||||
resp <- mask.fullText match {
|
resp <- mask.fullText match {
|
||||||
case Some(fq) if cfg.fullTextSearch.enabled =>
|
case Some(fq) if cfg.fullTextSearch.enabled =>
|
||||||
for {
|
for {
|
||||||
items <- backend.fulltext.findItems(
|
items <- backend.fulltext.findItems(cfg.maxNoteLength)(
|
||||||
query,
|
query,
|
||||||
OFulltext.FtsInput(fq),
|
OFulltext.FtsInput(fq),
|
||||||
Batch(mask.offset, mask.limit).restrictLimitTo(cfg.maxItemPageSize)
|
Batch(mask.offset, mask.limit).restrictLimitTo(cfg.maxItemPageSize)
|
||||||
@ -49,7 +49,7 @@ object ItemRoutes {
|
|||||||
} yield ok
|
} yield ok
|
||||||
case _ =>
|
case _ =>
|
||||||
for {
|
for {
|
||||||
items <- backend.itemSearch.findItems(
|
items <- backend.itemSearch.findItems(cfg.maxNoteLength)(
|
||||||
query,
|
query,
|
||||||
Batch(mask.offset, mask.limit).restrictLimitTo(cfg.maxItemPageSize)
|
Batch(mask.offset, mask.limit).restrictLimitTo(cfg.maxItemPageSize)
|
||||||
)
|
)
|
||||||
@ -67,7 +67,7 @@ object ItemRoutes {
|
|||||||
resp <- mask.fullText match {
|
resp <- mask.fullText match {
|
||||||
case Some(fq) if cfg.fullTextSearch.enabled =>
|
case Some(fq) if cfg.fullTextSearch.enabled =>
|
||||||
for {
|
for {
|
||||||
items <- backend.fulltext.findItemsWithTags(
|
items <- backend.fulltext.findItemsWithTags(cfg.maxNoteLength)(
|
||||||
query,
|
query,
|
||||||
OFulltext.FtsInput(fq),
|
OFulltext.FtsInput(fq),
|
||||||
Batch(mask.offset, mask.limit).restrictLimitTo(cfg.maxItemPageSize)
|
Batch(mask.offset, mask.limit).restrictLimitTo(cfg.maxItemPageSize)
|
||||||
@ -76,7 +76,7 @@ object ItemRoutes {
|
|||||||
} yield ok
|
} yield ok
|
||||||
case _ =>
|
case _ =>
|
||||||
for {
|
for {
|
||||||
items <- backend.itemSearch.findItemsWithTags(
|
items <- backend.itemSearch.findItemsWithTags(cfg.maxNoteLength)(
|
||||||
query,
|
query,
|
||||||
Batch(mask.offset, mask.limit).restrictLimitTo(cfg.maxItemPageSize)
|
Batch(mask.offset, mask.limit).restrictLimitTo(cfg.maxItemPageSize)
|
||||||
)
|
)
|
||||||
@ -92,7 +92,7 @@ object ItemRoutes {
|
|||||||
case q if q.length > 1 =>
|
case q if q.length > 1 =>
|
||||||
val ftsIn = OFulltext.FtsInput(q)
|
val ftsIn = OFulltext.FtsInput(q)
|
||||||
for {
|
for {
|
||||||
items <- backend.fulltext.findIndexOnly(
|
items <- backend.fulltext.findIndexOnly(cfg.maxNoteLength)(
|
||||||
ftsIn,
|
ftsIn,
|
||||||
user.account,
|
user.account,
|
||||||
Batch(mask.offset, mask.limit).restrictLimitTo(cfg.maxItemPageSize)
|
Batch(mask.offset, mask.limit).restrictLimitTo(cfg.maxItemPageSize)
|
||||||
|
@ -15,7 +15,9 @@ case class Flags(
|
|||||||
signupMode: SignupConfig.Mode,
|
signupMode: SignupConfig.Mode,
|
||||||
docspellAssetPath: String,
|
docspellAssetPath: String,
|
||||||
integrationEnabled: Boolean,
|
integrationEnabled: Boolean,
|
||||||
fullTextSearchEnabled: Boolean
|
fullTextSearchEnabled: Boolean,
|
||||||
|
maxPageSize: Int,
|
||||||
|
maxNoteLength: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
object Flags {
|
object Flags {
|
||||||
@ -26,7 +28,9 @@ object Flags {
|
|||||||
cfg.backend.signup.mode,
|
cfg.backend.signup.mode,
|
||||||
s"/app/assets/docspell-webapp/${BuildInfo.version}",
|
s"/app/assets/docspell-webapp/${BuildInfo.version}",
|
||||||
cfg.integrationEndpoint.enabled,
|
cfg.integrationEndpoint.enabled,
|
||||||
cfg.fullTextSearch.enabled
|
cfg.fullTextSearch.enabled,
|
||||||
|
cfg.maxItemPageSize,
|
||||||
|
cfg.maxNoteLength
|
||||||
)
|
)
|
||||||
|
|
||||||
implicit val jsonEncoder: Encoder[Flags] =
|
implicit val jsonEncoder: Encoder[Flags] =
|
||||||
|
@ -121,4 +121,8 @@ case class Column(name: String, ns: String = "", alias: String = "") {
|
|||||||
|
|
||||||
def decrement[A: Put](a: A): Fragment =
|
def decrement[A: Put](a: A): Fragment =
|
||||||
f ++ fr"=" ++ f ++ fr"- $a"
|
f ++ fr"=" ++ f ++ fr"- $a"
|
||||||
|
|
||||||
|
def substring(from: Int, many: Int): Fragment =
|
||||||
|
if (many <= 0 || from < 0) fr"${""}"
|
||||||
|
else fr"SUBSTRING(" ++ f ++ fr"FROM $from FOR $many)"
|
||||||
}
|
}
|
||||||
|
@ -156,7 +156,8 @@ object QItem {
|
|||||||
corrPerson: Option[IdRef],
|
corrPerson: Option[IdRef],
|
||||||
concPerson: Option[IdRef],
|
concPerson: Option[IdRef],
|
||||||
concEquip: Option[IdRef],
|
concEquip: Option[IdRef],
|
||||||
folder: Option[IdRef]
|
folder: Option[IdRef],
|
||||||
|
notes: Option[String]
|
||||||
)
|
)
|
||||||
|
|
||||||
case class Query(
|
case class Query(
|
||||||
@ -228,6 +229,7 @@ object QItem {
|
|||||||
private def findItemsBase(
|
private def findItemsBase(
|
||||||
q: Query,
|
q: Query,
|
||||||
distinct: Boolean,
|
distinct: Boolean,
|
||||||
|
noteMaxLen: Int,
|
||||||
moreCols: Seq[Fragment],
|
moreCols: Seq[Fragment],
|
||||||
ctes: (String, Fragment)*
|
ctes: (String, Fragment)*
|
||||||
): Fragment = {
|
): Fragment = {
|
||||||
@ -264,6 +266,9 @@ object QItem {
|
|||||||
EC.name.prefix("e1").f,
|
EC.name.prefix("e1").f,
|
||||||
FC.id.prefix("f1").f,
|
FC.id.prefix("f1").f,
|
||||||
FC.name.prefix("f1").f,
|
FC.name.prefix("f1").f,
|
||||||
|
// sql uses 1 for first character
|
||||||
|
IC.notes.prefix("i").substring(1, noteMaxLen),
|
||||||
|
// last column is only for sorting
|
||||||
q.orderAsc match {
|
q.orderAsc match {
|
||||||
case Some(co) =>
|
case Some(co) =>
|
||||||
coalesce(co(IC).prefix("i").f, IC.created.prefix("i").f)
|
coalesce(co(IC).prefix("i").f, IC.created.prefix("i").f)
|
||||||
@ -307,14 +312,16 @@ object QItem {
|
|||||||
fr"LEFT JOIN folders f1 ON" ++ IC.folder.prefix("i").is(FC.id.prefix("f1"))
|
fr"LEFT JOIN folders f1 ON" ++ IC.folder.prefix("i").is(FC.id.prefix("f1"))
|
||||||
}
|
}
|
||||||
|
|
||||||
def findItems(q: Query, batch: Batch): Stream[ConnectionIO, ListItem] = {
|
def findItems(
|
||||||
|
q: Query,
|
||||||
|
maxNoteLen: Int,
|
||||||
|
batch: Batch
|
||||||
|
): Stream[ConnectionIO, ListItem] = {
|
||||||
val IC = RItem.Columns
|
val IC = RItem.Columns
|
||||||
val PC = RPerson.Columns
|
val PC = RPerson.Columns
|
||||||
val OC = ROrganization.Columns
|
val OC = ROrganization.Columns
|
||||||
val EC = REquipment.Columns
|
val EC = REquipment.Columns
|
||||||
|
|
||||||
val query = findItemsBase(q, true, Seq.empty)
|
|
||||||
|
|
||||||
// inclusive tags are AND-ed
|
// inclusive tags are AND-ed
|
||||||
val tagSelectsIncl = q.tagsInclude
|
val tagSelectsIncl = q.tagsInclude
|
||||||
.map(tid =>
|
.map(tid =>
|
||||||
@ -404,6 +411,7 @@ object QItem {
|
|||||||
if (batch == Batch.all) Fragment.empty
|
if (batch == Batch.all) Fragment.empty
|
||||||
else fr"LIMIT ${batch.limit} OFFSET ${batch.offset}"
|
else fr"LIMIT ${batch.limit} OFFSET ${batch.offset}"
|
||||||
|
|
||||||
|
val query = findItemsBase(q, true, maxNoteLen, Seq.empty)
|
||||||
val frag =
|
val frag =
|
||||||
query ++ fr"WHERE" ++ cond ++ order ++ limitOffset
|
query ++ fr"WHERE" ++ cond ++ order ++ limitOffset
|
||||||
logger.trace(s"List $batch items: $frag")
|
logger.trace(s"List $batch items: $frag")
|
||||||
@ -413,6 +421,7 @@ object QItem {
|
|||||||
case class SelectedItem(itemId: Ident, weight: Double)
|
case class SelectedItem(itemId: Ident, weight: Double)
|
||||||
def findSelectedItems(
|
def findSelectedItems(
|
||||||
q: Query,
|
q: Query,
|
||||||
|
maxNoteLen: Int,
|
||||||
items: Set[SelectedItem]
|
items: Set[SelectedItem]
|
||||||
): Stream[ConnectionIO, ListItem] =
|
): Stream[ConnectionIO, ListItem] =
|
||||||
if (items.isEmpty) Stream.empty
|
if (items.isEmpty) Stream.empty
|
||||||
@ -425,6 +434,7 @@ object QItem {
|
|||||||
val from = findItemsBase(
|
val from = findItemsBase(
|
||||||
q,
|
q,
|
||||||
true,
|
true,
|
||||||
|
maxNoteLen,
|
||||||
Seq(fr"tids.weight"),
|
Seq(fr"tids.weight"),
|
||||||
("tids(item_id, weight)", fr"(VALUES" ++ values ++ fr")")
|
("tids(item_id, weight)", fr"(VALUES" ++ values ++ fr")")
|
||||||
) ++
|
) ++
|
||||||
|
@ -146,7 +146,7 @@ viewQueue model =
|
|||||||
|
|
||||||
viewUserSettings : Model -> Html Msg
|
viewUserSettings : Model -> Html Msg
|
||||||
viewUserSettings model =
|
viewUserSettings model =
|
||||||
Html.map UserSettingsMsg (Page.UserSettings.View.view model.uiSettings model.userSettingsModel)
|
Html.map UserSettingsMsg (Page.UserSettings.View.view model.flags model.uiSettings model.userSettingsModel)
|
||||||
|
|
||||||
|
|
||||||
viewCollectiveSettings : Model -> Html Msg
|
viewCollectiveSettings : Model -> Html Msg
|
||||||
|
@ -197,6 +197,22 @@ viewItem settings item =
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
, div
|
||||||
|
[ classList
|
||||||
|
[ ( "content", True )
|
||||||
|
, ( "invisible hidden"
|
||||||
|
, settings.itemSearchNoteLength
|
||||||
|
<= 0
|
||||||
|
|| Util.String.isNothingOrBlank item.notes
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[ span [ class "small-info" ]
|
||||||
|
[ Maybe.withDefault "" item.notes
|
||||||
|
|> Util.String.ellipsis settings.itemSearchNoteLength
|
||||||
|
|> text
|
||||||
|
]
|
||||||
|
]
|
||||||
, div [ class "content" ]
|
, div [ class "content" ]
|
||||||
[ div [ class "ui horizontal list" ]
|
[ div [ class "ui horizontal list" ]
|
||||||
[ div
|
[ div
|
||||||
|
@ -12,7 +12,7 @@ import Comp.ColorTagger
|
|||||||
import Comp.IntField
|
import Comp.IntField
|
||||||
import Data.Color exposing (Color)
|
import Data.Color exposing (Color)
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.UiSettings exposing (StoredUiSettings, UiSettings)
|
import Data.UiSettings exposing (UiSettings)
|
||||||
import Dict exposing (Dict)
|
import Dict exposing (Dict)
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
@ -27,6 +27,8 @@ type alias Model =
|
|||||||
, tagColors : Dict String Color
|
, tagColors : Dict String Color
|
||||||
, tagColorModel : Comp.ColorTagger.Model
|
, tagColorModel : Comp.ColorTagger.Model
|
||||||
, nativePdfPreview : Bool
|
, nativePdfPreview : Bool
|
||||||
|
, itemSearchNoteLength : Maybe Int
|
||||||
|
, searchNoteLengthModel : Comp.IntField.Model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -36,7 +38,7 @@ init flags settings =
|
|||||||
, searchPageSizeModel =
|
, searchPageSizeModel =
|
||||||
Comp.IntField.init
|
Comp.IntField.init
|
||||||
(Just 10)
|
(Just 10)
|
||||||
(Just 500)
|
(Just flags.config.maxPageSize)
|
||||||
False
|
False
|
||||||
"Page size"
|
"Page size"
|
||||||
, tagColors = settings.tagCategoryColors
|
, tagColors = settings.tagCategoryColors
|
||||||
@ -45,6 +47,13 @@ init flags settings =
|
|||||||
[]
|
[]
|
||||||
Data.Color.all
|
Data.Color.all
|
||||||
, nativePdfPreview = settings.nativePdfPreview
|
, nativePdfPreview = settings.nativePdfPreview
|
||||||
|
, itemSearchNoteLength = Just settings.itemSearchNoteLength
|
||||||
|
, searchNoteLengthModel =
|
||||||
|
Comp.IntField.init
|
||||||
|
(Just 0)
|
||||||
|
(Just flags.config.maxNoteLength)
|
||||||
|
False
|
||||||
|
"Max. Note Length"
|
||||||
}
|
}
|
||||||
, Api.getTags flags "" GetTagsResp
|
, Api.getTags flags "" GetTagsResp
|
||||||
)
|
)
|
||||||
@ -55,6 +64,7 @@ type Msg
|
|||||||
| TagColorMsg Comp.ColorTagger.Msg
|
| TagColorMsg Comp.ColorTagger.Msg
|
||||||
| GetTagsResp (Result Http.Error TagList)
|
| GetTagsResp (Result Http.Error TagList)
|
||||||
| TogglePdfPreview
|
| TogglePdfPreview
|
||||||
|
| NoteLengthMsg Comp.IntField.Msg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -80,6 +90,22 @@ update sett msg model =
|
|||||||
in
|
in
|
||||||
( model_, nextSettings )
|
( model_, nextSettings )
|
||||||
|
|
||||||
|
NoteLengthMsg lm ->
|
||||||
|
let
|
||||||
|
( m, n ) =
|
||||||
|
Comp.IntField.update lm model.searchNoteLengthModel
|
||||||
|
|
||||||
|
nextSettings =
|
||||||
|
Maybe.map (\len -> { sett | itemSearchNoteLength = len }) n
|
||||||
|
|
||||||
|
model_ =
|
||||||
|
{ model
|
||||||
|
| searchNoteLengthModel = m
|
||||||
|
, itemSearchNoteLength = n
|
||||||
|
}
|
||||||
|
in
|
||||||
|
( model_, nextSettings )
|
||||||
|
|
||||||
TagColorMsg lm ->
|
TagColorMsg lm ->
|
||||||
let
|
let
|
||||||
( m_, d_ ) =
|
( m_, d_ ) =
|
||||||
@ -139,19 +165,32 @@ tagColorViewOpts =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
view : UiSettings -> Model -> Html Msg
|
view : Flags -> UiSettings -> Model -> Html Msg
|
||||||
view _ model =
|
view flags _ model =
|
||||||
div [ class "ui form" ]
|
div [ class "ui form" ]
|
||||||
[ div [ class "ui dividing header" ]
|
[ div [ class "ui dividing header" ]
|
||||||
[ text "Item Search"
|
[ text "Item Search"
|
||||||
]
|
]
|
||||||
, Html.map SearchPageSizeMsg
|
, Html.map SearchPageSizeMsg
|
||||||
(Comp.IntField.viewWithInfo
|
(Comp.IntField.viewWithInfo
|
||||||
"Maximum results in one page when searching items."
|
("Maximum results in one page when searching items. At most "
|
||||||
|
++ String.fromInt flags.config.maxPageSize
|
||||||
|
++ "."
|
||||||
|
)
|
||||||
model.itemSearchPageSize
|
model.itemSearchPageSize
|
||||||
"field"
|
"field"
|
||||||
model.searchPageSizeModel
|
model.searchPageSizeModel
|
||||||
)
|
)
|
||||||
|
, Html.map NoteLengthMsg
|
||||||
|
(Comp.IntField.viewWithInfo
|
||||||
|
("Maximum size of the item notes to display in card view. Between 0 - "
|
||||||
|
++ String.fromInt flags.config.maxNoteLength
|
||||||
|
++ "."
|
||||||
|
)
|
||||||
|
model.itemSearchNoteLength
|
||||||
|
"field"
|
||||||
|
model.searchNoteLengthModel
|
||||||
|
)
|
||||||
, div [ class "ui dividing header" ]
|
, div [ class "ui dividing header" ]
|
||||||
[ text "Item Detail"
|
[ text "Item Detail"
|
||||||
]
|
]
|
||||||
|
@ -115,10 +115,10 @@ isSuccess model =
|
|||||||
Maybe.map .success model.message == Just True
|
Maybe.map .success model.message == Just True
|
||||||
|
|
||||||
|
|
||||||
view : UiSettings -> String -> Model -> Html Msg
|
view : Flags -> UiSettings -> String -> Model -> Html Msg
|
||||||
view settings classes model =
|
view flags settings classes model =
|
||||||
div [ class classes ]
|
div [ class classes ]
|
||||||
[ Html.map UiSettingsFormMsg (Comp.UiSettingsForm.view settings model.formModel)
|
[ Html.map UiSettingsFormMsg (Comp.UiSettingsForm.view flags settings model.formModel)
|
||||||
, div [ class "ui divider" ] []
|
, div [ class "ui divider" ] []
|
||||||
, button
|
, button
|
||||||
[ class "ui primary button"
|
[ class "ui primary button"
|
||||||
|
@ -16,6 +16,8 @@ type alias Config =
|
|||||||
, docspellAssetPath : String
|
, docspellAssetPath : String
|
||||||
, integrationEnabled : Bool
|
, integrationEnabled : Bool
|
||||||
, fullTextSearchEnabled : Bool
|
, fullTextSearchEnabled : Bool
|
||||||
|
, maxPageSize : Int
|
||||||
|
, maxNoteLength : Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ type alias StoredUiSettings =
|
|||||||
{ itemSearchPageSize : Maybe Int
|
{ itemSearchPageSize : Maybe Int
|
||||||
, tagCategoryColors : List ( String, String )
|
, tagCategoryColors : List ( String, String )
|
||||||
, nativePdfPreview : Bool
|
, nativePdfPreview : Bool
|
||||||
|
, itemSearchNoteLength : Maybe Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -40,6 +41,7 @@ type alias UiSettings =
|
|||||||
{ itemSearchPageSize : Int
|
{ itemSearchPageSize : Int
|
||||||
, tagCategoryColors : Dict String Color
|
, tagCategoryColors : Dict String Color
|
||||||
, nativePdfPreview : Bool
|
, nativePdfPreview : Bool
|
||||||
|
, itemSearchNoteLength : Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -48,6 +50,7 @@ defaults =
|
|||||||
{ itemSearchPageSize = 60
|
{ itemSearchPageSize = 60
|
||||||
, tagCategoryColors = Dict.empty
|
, tagCategoryColors = Dict.empty
|
||||||
, nativePdfPreview = False
|
, nativePdfPreview = False
|
||||||
|
, itemSearchNoteLength = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -64,6 +67,8 @@ merge given fallback =
|
|||||||
)
|
)
|
||||||
fallback.tagCategoryColors
|
fallback.tagCategoryColors
|
||||||
, nativePdfPreview = given.nativePdfPreview
|
, nativePdfPreview = given.nativePdfPreview
|
||||||
|
, itemSearchNoteLength =
|
||||||
|
choose given.itemSearchNoteLength fallback.itemSearchNoteLength
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -79,6 +84,7 @@ toStoredUiSettings settings =
|
|||||||
Dict.map (\_ -> Data.Color.toString) settings.tagCategoryColors
|
Dict.map (\_ -> Data.Color.toString) settings.tagCategoryColors
|
||||||
|> Dict.toList
|
|> Dict.toList
|
||||||
, nativePdfPreview = settings.nativePdfPreview
|
, nativePdfPreview = settings.nativePdfPreview
|
||||||
|
, itemSearchNoteLength = Just settings.itemSearchNoteLength
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import Comp.ImapSettingsManage
|
|||||||
import Comp.NotificationManage
|
import Comp.NotificationManage
|
||||||
import Comp.ScanMailboxManage
|
import Comp.ScanMailboxManage
|
||||||
import Comp.UiSettingsManage
|
import Comp.UiSettingsManage
|
||||||
|
import Data.Flags exposing (Flags)
|
||||||
import Data.UiSettings exposing (UiSettings)
|
import Data.UiSettings exposing (UiSettings)
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
@ -14,8 +15,8 @@ import Page.UserSettings.Data exposing (..)
|
|||||||
import Util.Html exposing (classActive)
|
import Util.Html exposing (classActive)
|
||||||
|
|
||||||
|
|
||||||
view : UiSettings -> Model -> Html Msg
|
view : Flags -> UiSettings -> Model -> Html Msg
|
||||||
view settings model =
|
view flags settings model =
|
||||||
div [ class "usersetting-page ui padded grid" ]
|
div [ class "usersetting-page ui padded grid" ]
|
||||||
[ div [ class "sixteen wide mobile four wide tablet four wide computer column" ]
|
[ div [ class "sixteen wide mobile four wide tablet four wide computer column" ]
|
||||||
[ h4 [ class "ui top attached ablue-comp header" ]
|
[ h4 [ class "ui top attached ablue-comp header" ]
|
||||||
@ -51,7 +52,7 @@ view settings model =
|
|||||||
viewScanMailboxManage settings model
|
viewScanMailboxManage settings model
|
||||||
|
|
||||||
Just UiSettingsTab ->
|
Just UiSettingsTab ->
|
||||||
viewUiSettings settings model
|
viewUiSettings flags settings model
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
[]
|
[]
|
||||||
@ -72,8 +73,8 @@ makeTab model tab header icon =
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
viewUiSettings : UiSettings -> Model -> List (Html Msg)
|
viewUiSettings : Flags -> UiSettings -> Model -> List (Html Msg)
|
||||||
viewUiSettings settings model =
|
viewUiSettings flags settings model =
|
||||||
[ h2 [ class "ui header" ]
|
[ h2 [ class "ui header" ]
|
||||||
[ i [ class "cog icon" ] []
|
[ i [ class "cog icon" ] []
|
||||||
, text "UI Settings"
|
, text "UI Settings"
|
||||||
@ -84,6 +85,7 @@ viewUiSettings settings model =
|
|||||||
]
|
]
|
||||||
, Html.map UiSettingsMsg
|
, Html.map UiSettingsMsg
|
||||||
(Comp.UiSettingsManage.view
|
(Comp.UiSettingsManage.view
|
||||||
|
flags
|
||||||
settings
|
settings
|
||||||
"ui segment"
|
"ui segment"
|
||||||
model.uiSettingsModel
|
model.uiSettingsModel
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
module Util.String exposing
|
module Util.String exposing
|
||||||
( crazyEncode
|
( crazyEncode
|
||||||
, ellipsis
|
, ellipsis
|
||||||
|
, isBlank
|
||||||
|
, isNothingOrBlank
|
||||||
, underscoreToSpace
|
, underscoreToSpace
|
||||||
, withDefault
|
, withDefault
|
||||||
)
|
)
|
||||||
@ -31,7 +33,7 @@ ellipsis len str =
|
|||||||
str
|
str
|
||||||
|
|
||||||
else
|
else
|
||||||
String.left (len - 3) str ++ "..."
|
String.left (len - 1) str ++ "…"
|
||||||
|
|
||||||
|
|
||||||
withDefault : String -> String -> String
|
withDefault : String -> String -> String
|
||||||
@ -46,3 +48,14 @@ withDefault default str =
|
|||||||
underscoreToSpace : String -> String
|
underscoreToSpace : String -> String
|
||||||
underscoreToSpace str =
|
underscoreToSpace str =
|
||||||
String.replace "_" " " str
|
String.replace "_" " " str
|
||||||
|
|
||||||
|
|
||||||
|
isBlank : String -> Bool
|
||||||
|
isBlank s =
|
||||||
|
s == "" || (String.trim s == "")
|
||||||
|
|
||||||
|
|
||||||
|
isNothingOrBlank : Maybe String -> Bool
|
||||||
|
isNothingOrBlank ms =
|
||||||
|
Maybe.map isBlank ms
|
||||||
|
|> Maybe.withDefault True
|
||||||
|
@ -14,6 +14,7 @@ let
|
|||||||
app-id = "rest1";
|
app-id = "rest1";
|
||||||
base-url = "http://localhost:7880";
|
base-url = "http://localhost:7880";
|
||||||
max-item-page-size = 200;
|
max-item-page-size = 200;
|
||||||
|
max-note-length = 180;
|
||||||
bind = {
|
bind = {
|
||||||
address = "localhost";
|
address = "localhost";
|
||||||
port = 7880;
|
port = 7880;
|
||||||
@ -124,6 +125,17 @@ in {
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
max-note-length = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = defaults.max-note-length;
|
||||||
|
description = ''
|
||||||
|
The number of characters to return for each item notes when
|
||||||
|
searching. Item notes may be very long, when returning them with
|
||||||
|
all the results from a search, they add quite some data to return.
|
||||||
|
In order to keep this low, a limit can be defined here.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
bind = mkOption {
|
bind = mkOption {
|
||||||
type = types.submodule({
|
type = types.submodule({
|
||||||
options = {
|
options = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user