Merge pull request #211 from eikek/notes-in-listitem

Notes in listitem
This commit is contained in:
mergify[bot] 2020-08-04 22:25:17 +00:00 committed by GitHub
commit 17e072ef6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 174 additions and 42 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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
) )

View File

@ -197,6 +197,7 @@ trait Conversions {
i.folder.map(mkIdName), i.folder.map(mkIdName),
i.fileCount, i.fileCount,
Nil, Nil,
i.notes,
Nil Nil
) )

View File

@ -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)

View File

@ -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] =

View File

@ -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)"
} }

View File

@ -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")")
) ++ ) ++

View File

@ -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

View File

@ -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

View File

@ -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"
] ]

View File

@ -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"

View File

@ -16,6 +16,8 @@ type alias Config =
, docspellAssetPath : String , docspellAssetPath : String
, integrationEnabled : Bool , integrationEnabled : Bool
, fullTextSearchEnabled : Bool , fullTextSearchEnabled : Bool
, maxPageSize : Int
, maxNoteLength : Int
} }

View File

@ -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
} }

View File

@ -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

View File

@ -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

View File

@ -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 = {