mirror of
				https://github.com/TheAnachronism/docspell.git
				synced 2025-11-03 18:00:11 +00:00 
			
		
		
		
	@@ -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 = {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user