mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-06 15:15:58 +00:00
Use search stats to populate search menu
This commit is contained in:
parent
e52271f9cd
commit
e961a5ac10
@ -86,7 +86,7 @@ object BackendApp {
|
|||||||
customFieldsImpl <- OCustomFields(store)
|
customFieldsImpl <- OCustomFields(store)
|
||||||
simpleSearchImpl = OSimpleSearch(fulltextImpl, itemSearchImpl)
|
simpleSearchImpl = OSimpleSearch(fulltextImpl, itemSearchImpl)
|
||||||
clientSettingsImpl <- OClientSettings(store)
|
clientSettingsImpl <- OClientSettings(store)
|
||||||
shareImpl <- Resource.pure(OShare(store, itemSearchImpl))
|
shareImpl <- Resource.pure(OShare(store, itemSearchImpl, simpleSearchImpl))
|
||||||
} yield new BackendApp[F] {
|
} yield new BackendApp[F] {
|
||||||
val login = loginImpl
|
val login = loginImpl
|
||||||
val signup = signupImpl
|
val signup = signupImpl
|
||||||
|
@ -9,15 +9,19 @@ package docspell.backend.ops
|
|||||||
import cats.data.OptionT
|
import cats.data.OptionT
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
|
||||||
import docspell.backend.PasswordCrypt
|
import docspell.backend.PasswordCrypt
|
||||||
import docspell.backend.auth.ShareToken
|
import docspell.backend.auth.ShareToken
|
||||||
import docspell.backend.ops.OItemSearch.{AttachmentPreviewData, Batch, Query}
|
import docspell.backend.ops.OItemSearch.{AttachmentPreviewData, Batch, Query}
|
||||||
import docspell.backend.ops.OShare.{ShareQuery, VerifyResult}
|
import docspell.backend.ops.OShare.{ShareQuery, VerifyResult}
|
||||||
|
import docspell.backend.ops.OSimpleSearch.StringSearchResult
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.query.ItemQuery
|
import docspell.query.ItemQuery
|
||||||
import docspell.query.ItemQuery.Expr.AttachId
|
import docspell.query.ItemQuery.Expr.AttachId
|
||||||
import docspell.store.Store
|
import docspell.store.Store
|
||||||
|
import docspell.store.queries.SearchSummary
|
||||||
import docspell.store.records.RShare
|
import docspell.store.records.RShare
|
||||||
|
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
trait OShare[F[_]] {
|
trait OShare[F[_]] {
|
||||||
@ -51,6 +55,9 @@ trait OShare[F[_]] {
|
|||||||
shareId: Ident
|
shareId: Ident
|
||||||
): OptionT[F, AttachmentPreviewData[F]]
|
): OptionT[F, AttachmentPreviewData[F]]
|
||||||
|
|
||||||
|
def searchSummary(
|
||||||
|
settings: OSimpleSearch.StatsSettings
|
||||||
|
)(shareId: Ident, q: ItemQueryString): OptionT[F, StringSearchResult[SearchSummary]]
|
||||||
}
|
}
|
||||||
|
|
||||||
object OShare {
|
object OShare {
|
||||||
@ -101,7 +108,11 @@ object OShare {
|
|||||||
def publishUntilInPast: ChangeResult = PublishUntilInPast
|
def publishUntilInPast: ChangeResult = PublishUntilInPast
|
||||||
}
|
}
|
||||||
|
|
||||||
def apply[F[_]: Async](store: Store[F], itemSearch: OItemSearch[F]): OShare[F] =
|
def apply[F[_]: Async](
|
||||||
|
store: Store[F],
|
||||||
|
itemSearch: OItemSearch[F],
|
||||||
|
simpleSearch: OSimpleSearch[F]
|
||||||
|
): OShare[F] =
|
||||||
new OShare[F] {
|
new OShare[F] {
|
||||||
private[this] val logger = Logger.log4s[F](org.log4s.getLogger)
|
private[this] val logger = Logger.log4s[F](org.log4s.getLogger)
|
||||||
|
|
||||||
@ -238,5 +249,23 @@ object OShare {
|
|||||||
.mapFilter(_ => None)
|
.mapFilter(_ => None)
|
||||||
else OptionT(itemSearch.findAttachmentPreview(attachId, sq.cid))
|
else OptionT(itemSearch.findAttachmentPreview(attachId, sq.cid))
|
||||||
} yield res
|
} yield res
|
||||||
|
|
||||||
|
def searchSummary(
|
||||||
|
settings: OSimpleSearch.StatsSettings
|
||||||
|
)(
|
||||||
|
shareId: Ident,
|
||||||
|
q: ItemQueryString
|
||||||
|
): OptionT[F, StringSearchResult[SearchSummary]] =
|
||||||
|
findShareQuery(shareId)
|
||||||
|
.semiflatMap { share =>
|
||||||
|
val fix = Query.Fix(share.asAccount, Some(share.query.expr), None)
|
||||||
|
simpleSearch
|
||||||
|
.searchSummaryByString(settings)(fix, q)
|
||||||
|
.map {
|
||||||
|
case StringSearchResult.Success(summary) =>
|
||||||
|
StringSearchResult.Success(summary.onlyExisting)
|
||||||
|
case other => other
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1558,9 +1558,9 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/BasicResult"
|
$ref: "#/components/schemas/BasicResult"
|
||||||
|
|
||||||
/share/search:
|
/share/search/query:
|
||||||
post:
|
post:
|
||||||
operationId: "share-search"
|
operationId: "share-search-query"
|
||||||
tags: [Share]
|
tags: [Share]
|
||||||
summary: Performs a search in a share.
|
summary: Performs a search in a share.
|
||||||
description: |
|
description: |
|
||||||
@ -1581,6 +1581,72 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/ItemLightList"
|
$ref: "#/components/schemas/ItemLightList"
|
||||||
|
/share/search/stats:
|
||||||
|
post:
|
||||||
|
operationId: "share-search-stats"
|
||||||
|
tags: [ Share ]
|
||||||
|
summary: Get basic statistics about search results.
|
||||||
|
description: |
|
||||||
|
Instead of returning the results of a query, uses it to return
|
||||||
|
a summary, constraint to the share.
|
||||||
|
security:
|
||||||
|
- shareTokenHeader: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ItemQuery"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/SearchStats"
|
||||||
|
/share/attachment/{id}/preview:
|
||||||
|
head:
|
||||||
|
operationId: "share-attach-check-preview"
|
||||||
|
tags: [ Attachment ]
|
||||||
|
summary: Get the headers to a preview image of an attachment file.
|
||||||
|
description: |
|
||||||
|
Checks if an image file showing a preview of the attachment is
|
||||||
|
available. If not available, a 404 is returned.
|
||||||
|
security:
|
||||||
|
- shareTokenHeader: []
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/id"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Ok
|
||||||
|
404:
|
||||||
|
description: NotFound
|
||||||
|
get:
|
||||||
|
operationId: "share-attach-get-preview"
|
||||||
|
tags: [ Attachment ]
|
||||||
|
summary: Get a preview image of an attachment file.
|
||||||
|
description: |
|
||||||
|
Gets a image file showing a preview of the attachment. Usually
|
||||||
|
it is a small image of the first page of the document.If not
|
||||||
|
available, a 404 is returned. However, if the query parameter
|
||||||
|
`withFallback` is `true`, a fallback preview image is
|
||||||
|
returned. You can also use the `HEAD` method to check for
|
||||||
|
existence.
|
||||||
|
|
||||||
|
The attachment must be in the search results of the current
|
||||||
|
share.
|
||||||
|
security:
|
||||||
|
- shareTokenHeader: []
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/id"
|
||||||
|
- $ref: "#/components/parameters/withFallback"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/octet-stream:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
|
||||||
/admin/user/resetPassword:
|
/admin/user/resetPassword:
|
||||||
post:
|
post:
|
||||||
|
@ -10,17 +10,18 @@ import cats.data.NonEmptyList
|
|||||||
import cats.data.OptionT
|
import cats.data.OptionT
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
|
||||||
import docspell.backend.ops.OItemSearch.AttachmentPreviewData
|
import docspell.backend.ops.OItemSearch.AttachmentPreviewData
|
||||||
import docspell.backend.ops._
|
import docspell.backend.ops._
|
||||||
import docspell.restapi.model.BasicResult
|
import docspell.restapi.model.BasicResult
|
||||||
import docspell.store.records.RFileMeta
|
|
||||||
import docspell.restserver.http4s.{QueryParam => QP}
|
import docspell.restserver.http4s.{QueryParam => QP}
|
||||||
|
import docspell.store.records.RFileMeta
|
||||||
|
|
||||||
import org.http4s._
|
import org.http4s._
|
||||||
import org.http4s.circe.CirceEntityEncoder._
|
import org.http4s.circe.CirceEntityEncoder._
|
||||||
import org.http4s.dsl.Http4sDsl
|
import org.http4s.dsl.Http4sDsl
|
||||||
import org.http4s.headers._
|
|
||||||
import org.http4s.headers.ETag.EntityTag
|
import org.http4s.headers.ETag.EntityTag
|
||||||
|
import org.http4s.headers._
|
||||||
import org.typelevel.ci.CIString
|
import org.typelevel.ci.CIString
|
||||||
|
|
||||||
object BinaryUtil {
|
object BinaryUtil {
|
||||||
@ -53,6 +54,15 @@ object BinaryUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def respondHead[F[_]: Async](
|
||||||
|
dsl: Http4sDsl[F]
|
||||||
|
)(fileData: Option[AttachmentPreviewData[F]]): F[Response[F]] = {
|
||||||
|
import dsl._
|
||||||
|
fileData
|
||||||
|
.map(data => withResponseHeaders(dsl, Ok())(data))
|
||||||
|
.getOrElse(NotFound(BasicResult(false, "Not found")))
|
||||||
|
}
|
||||||
|
|
||||||
def withResponseHeaders[F[_]: Sync](dsl: Http4sDsl[F], resp: F[Response[F]])(
|
def withResponseHeaders[F[_]: Sync](dsl: Http4sDsl[F], resp: F[Response[F]])(
|
||||||
data: OItemSearch.BinaryData[F]
|
data: OItemSearch.BinaryData[F]
|
||||||
): F[Response[F]] = {
|
): F[Response[F]] = {
|
||||||
|
@ -125,10 +125,7 @@ object AttachmentRoutes {
|
|||||||
for {
|
for {
|
||||||
fileData <-
|
fileData <-
|
||||||
backend.itemSearch.findAttachmentPreview(id, user.account.collective)
|
backend.itemSearch.findAttachmentPreview(id, user.account.collective)
|
||||||
resp <-
|
resp <- BinaryUtil.respondHead(dsl)(fileData)
|
||||||
fileData
|
|
||||||
.map(data => withResponseHeaders(Ok())(data))
|
|
||||||
.getOrElse(NotFound(BasicResult(false, "Not found")))
|
|
||||||
} yield resp
|
} yield resp
|
||||||
|
|
||||||
case POST -> Root / Ident(id) / "preview" =>
|
case POST -> Root / Ident(id) / "preview" =>
|
||||||
|
@ -28,11 +28,11 @@ import docspell.restserver.http4s.BinaryUtil
|
|||||||
import docspell.restserver.http4s.Responses
|
import docspell.restserver.http4s.Responses
|
||||||
import docspell.restserver.http4s.{QueryParam => QP}
|
import docspell.restserver.http4s.{QueryParam => QP}
|
||||||
|
|
||||||
import org.http4s.HttpRoutes
|
|
||||||
import org.http4s.circe.CirceEntityDecoder._
|
import org.http4s.circe.CirceEntityDecoder._
|
||||||
import org.http4s.circe.CirceEntityEncoder._
|
import org.http4s.circe.CirceEntityEncoder._
|
||||||
import org.http4s.dsl.Http4sDsl
|
import org.http4s.dsl.Http4sDsl
|
||||||
import org.http4s.headers._
|
import org.http4s.headers._
|
||||||
|
import org.http4s.{HttpRoutes, Response}
|
||||||
import org.log4s._
|
import org.log4s._
|
||||||
|
|
||||||
object ItemRoutes {
|
object ItemRoutes {
|
||||||
@ -415,7 +415,11 @@ object ItemRoutes {
|
|||||||
def searchItems[F[_]: Sync](
|
def searchItems[F[_]: Sync](
|
||||||
backend: BackendApp[F],
|
backend: BackendApp[F],
|
||||||
dsl: Http4sDsl[F]
|
dsl: Http4sDsl[F]
|
||||||
)(settings: OSimpleSearch.Settings, fixQuery: Query.Fix, itemQuery: ItemQueryString) = {
|
)(
|
||||||
|
settings: OSimpleSearch.Settings,
|
||||||
|
fixQuery: Query.Fix,
|
||||||
|
itemQuery: ItemQueryString
|
||||||
|
): F[Response[F]] = {
|
||||||
import dsl._
|
import dsl._
|
||||||
|
|
||||||
def convertFts(res: OSimpleSearch.Items.FtsItems): ItemLightList =
|
def convertFts(res: OSimpleSearch.Items.FtsItems): ItemLightList =
|
||||||
@ -459,7 +463,7 @@ object ItemRoutes {
|
|||||||
settings: OSimpleSearch.StatsSettings,
|
settings: OSimpleSearch.StatsSettings,
|
||||||
fixQuery: Query.Fix,
|
fixQuery: Query.Fix,
|
||||||
itemQuery: ItemQueryString
|
itemQuery: ItemQueryString
|
||||||
) = {
|
): F[Response[F]] = {
|
||||||
import dsl._
|
import dsl._
|
||||||
|
|
||||||
backend.simpleSearch
|
backend.simpleSearch
|
||||||
@ -479,7 +483,6 @@ object ItemRoutes {
|
|||||||
case StringSearchResult.ParseFailed(pf) =>
|
case StringSearchResult.ParseFailed(pf) =>
|
||||||
BadRequest(BasicResult(false, s"Error reading query: ${pf.render}"))
|
BadRequest(BasicResult(false, s"Error reading query: ${pf.render}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit final class OptionString(opt: Option[String]) {
|
implicit final class OptionString(opt: Option[String]) {
|
||||||
|
@ -26,12 +26,20 @@ object ShareAttachmentRoutes {
|
|||||||
val dsl = new Http4sDsl[F] {}
|
val dsl = new Http4sDsl[F] {}
|
||||||
import dsl._
|
import dsl._
|
||||||
|
|
||||||
HttpRoutes.of { case req @ GET -> Root / Ident(id) / "preview" =>
|
HttpRoutes.of {
|
||||||
for {
|
case req @ GET -> Root / Ident(id) / "preview" =>
|
||||||
fileData <-
|
for {
|
||||||
backend.share.findAttachmentPreview(id, token.id).value
|
fileData <-
|
||||||
resp <- BinaryUtil.respond(dsl, req)(fileData)
|
backend.share.findAttachmentPreview(id, token.id).value
|
||||||
} yield resp
|
resp <- BinaryUtil.respond(dsl, req)(fileData)
|
||||||
|
} yield resp
|
||||||
|
|
||||||
|
case HEAD -> Root / Ident(id) / "preview" =>
|
||||||
|
for {
|
||||||
|
fileData <-
|
||||||
|
backend.share.findAttachmentPreview(id, token.id).value
|
||||||
|
resp <- BinaryUtil.respondHead(dsl)(fileData)
|
||||||
|
} yield resp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,15 +12,19 @@ import cats.implicits._
|
|||||||
import docspell.backend.BackendApp
|
import docspell.backend.BackendApp
|
||||||
import docspell.backend.auth.ShareToken
|
import docspell.backend.auth.ShareToken
|
||||||
import docspell.backend.ops.OSimpleSearch
|
import docspell.backend.ops.OSimpleSearch
|
||||||
|
import docspell.backend.ops.OSimpleSearch.StringSearchResult
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.restapi.model.ItemQuery
|
import docspell.query.FulltextExtract.Result.{TooMany, UnsupportedPosition}
|
||||||
|
import docspell.restapi.model._
|
||||||
import docspell.restserver.Config
|
import docspell.restserver.Config
|
||||||
|
import docspell.restserver.conv.Conversions
|
||||||
import docspell.store.qb.Batch
|
import docspell.store.qb.Batch
|
||||||
import docspell.store.queries.Query
|
import docspell.store.queries.{Query, SearchSummary}
|
||||||
|
|
||||||
import org.http4s.HttpRoutes
|
|
||||||
import org.http4s.circe.CirceEntityDecoder._
|
import org.http4s.circe.CirceEntityDecoder._
|
||||||
|
import org.http4s.circe.CirceEntityEncoder._
|
||||||
import org.http4s.dsl.Http4sDsl
|
import org.http4s.dsl.Http4sDsl
|
||||||
|
import org.http4s.{HttpRoutes, Response}
|
||||||
|
|
||||||
object ShareSearchRoutes {
|
object ShareSearchRoutes {
|
||||||
|
|
||||||
@ -34,33 +38,68 @@ object ShareSearchRoutes {
|
|||||||
val dsl = new Http4sDsl[F] {}
|
val dsl = new Http4sDsl[F] {}
|
||||||
import dsl._
|
import dsl._
|
||||||
|
|
||||||
HttpRoutes.of { case req @ POST -> Root =>
|
HttpRoutes.of {
|
||||||
backend.share
|
case req @ POST -> Root / "query" =>
|
||||||
.findShareQuery(token.id)
|
backend.share
|
||||||
.semiflatMap { share =>
|
.findShareQuery(token.id)
|
||||||
for {
|
.semiflatMap { share =>
|
||||||
userQuery <- req.as[ItemQuery]
|
for {
|
||||||
batch = Batch(
|
userQuery <- req.as[ItemQuery]
|
||||||
userQuery.offset.getOrElse(0),
|
batch = Batch(
|
||||||
userQuery.limit.getOrElse(cfg.maxItemPageSize)
|
userQuery.offset.getOrElse(0),
|
||||||
).restrictLimitTo(
|
userQuery.limit.getOrElse(cfg.maxItemPageSize)
|
||||||
cfg.maxItemPageSize
|
).restrictLimitTo(
|
||||||
)
|
cfg.maxItemPageSize
|
||||||
itemQuery = ItemQueryString(userQuery.query)
|
)
|
||||||
settings = OSimpleSearch.Settings(
|
itemQuery = ItemQueryString(userQuery.query)
|
||||||
batch,
|
settings = OSimpleSearch.Settings(
|
||||||
cfg.fullTextSearch.enabled,
|
batch,
|
||||||
userQuery.withDetails.getOrElse(false),
|
cfg.fullTextSearch.enabled,
|
||||||
cfg.maxNoteLength,
|
userQuery.withDetails.getOrElse(false),
|
||||||
searchMode = SearchMode.Normal
|
cfg.maxNoteLength,
|
||||||
)
|
searchMode = SearchMode.Normal
|
||||||
account = share.asAccount
|
)
|
||||||
fixQuery = Query.Fix(account, Some(share.query.expr), None)
|
account = share.asAccount
|
||||||
_ <- logger.debug(s"Searching in share ${share.id.id}: ${userQuery.query}")
|
fixQuery = Query.Fix(account, Some(share.query.expr), None)
|
||||||
resp <- ItemRoutes.searchItems(backend, dsl)(settings, fixQuery, itemQuery)
|
_ <- logger.debug(s"Searching in share ${share.id.id}: ${userQuery.query}")
|
||||||
} yield resp
|
resp <- ItemRoutes.searchItems(backend, dsl)(settings, fixQuery, itemQuery)
|
||||||
}
|
} yield resp
|
||||||
.getOrElseF(NotFound())
|
}
|
||||||
|
.getOrElseF(NotFound())
|
||||||
|
|
||||||
|
case req @ POST -> Root / "stats" =>
|
||||||
|
for {
|
||||||
|
userQuery <- req.as[ItemQuery]
|
||||||
|
itemQuery = ItemQueryString(userQuery.query)
|
||||||
|
settings = OSimpleSearch.StatsSettings(
|
||||||
|
useFTS = cfg.fullTextSearch.enabled,
|
||||||
|
searchMode = userQuery.searchMode.getOrElse(SearchMode.Normal)
|
||||||
|
)
|
||||||
|
stats <- backend.share.searchSummary(settings)(token.id, itemQuery).value
|
||||||
|
resp <- stats.map(mkSummaryResponse(dsl)).getOrElse(NotFound())
|
||||||
|
} yield resp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def mkSummaryResponse[F[_]: Sync](
|
||||||
|
dsl: Http4sDsl[F]
|
||||||
|
)(r: StringSearchResult[SearchSummary]): F[Response[F]] = {
|
||||||
|
import dsl._
|
||||||
|
r match {
|
||||||
|
case StringSearchResult.Success(summary) =>
|
||||||
|
Ok(Conversions.mkSearchStats(summary))
|
||||||
|
case StringSearchResult.FulltextMismatch(TooMany) =>
|
||||||
|
BadRequest(BasicResult(false, "Fulltext search is not possible in this share."))
|
||||||
|
case StringSearchResult.FulltextMismatch(UnsupportedPosition) =>
|
||||||
|
BadRequest(
|
||||||
|
BasicResult(
|
||||||
|
false,
|
||||||
|
"Fulltext search must be in root position or inside the first AND."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
case StringSearchResult.ParseFailed(pf) =>
|
||||||
|
BadRequest(BasicResult(false, s"Error reading query: ${pf.render}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,4 +12,14 @@ case class SearchSummary(
|
|||||||
cats: List[CategoryCount],
|
cats: List[CategoryCount],
|
||||||
fields: List[FieldStats],
|
fields: List[FieldStats],
|
||||||
folders: List[FolderCount]
|
folders: List[FolderCount]
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
def onlyExisting: SearchSummary =
|
||||||
|
SearchSummary(
|
||||||
|
count,
|
||||||
|
tags.filter(_.count > 0),
|
||||||
|
cats.filter(_.count > 0),
|
||||||
|
fields.filter(_.count > 0),
|
||||||
|
folders.filter(_.count > 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -114,6 +114,7 @@ module Api exposing
|
|||||||
, restoreItem
|
, restoreItem
|
||||||
, saveClientSettings
|
, saveClientSettings
|
||||||
, searchShare
|
, searchShare
|
||||||
|
, searchShareStats
|
||||||
, sendMail
|
, sendMail
|
||||||
, setAttachmentName
|
, setAttachmentName
|
||||||
, setCollectiveSettings
|
, setCollectiveSettings
|
||||||
@ -2283,13 +2284,23 @@ verifyShare flags secret receive =
|
|||||||
searchShare : Flags -> String -> ItemQuery -> (Result Http.Error ItemLightList -> msg) -> Cmd msg
|
searchShare : Flags -> String -> ItemQuery -> (Result Http.Error ItemLightList -> msg) -> Cmd msg
|
||||||
searchShare flags token search receive =
|
searchShare flags token search receive =
|
||||||
Http2.sharePost
|
Http2.sharePost
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/share/search"
|
{ url = flags.config.baseUrl ++ "/api/v1/share/search/query"
|
||||||
, token = token
|
, token = token
|
||||||
, body = Http.jsonBody (Api.Model.ItemQuery.encode search)
|
, body = Http.jsonBody (Api.Model.ItemQuery.encode search)
|
||||||
, expect = Http.expectJson receive Api.Model.ItemLightList.decoder
|
, expect = Http.expectJson receive Api.Model.ItemLightList.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
searchShareStats : Flags -> String -> ItemQuery -> (Result Http.Error SearchStats -> msg) -> Cmd msg
|
||||||
|
searchShareStats flags token search receive =
|
||||||
|
Http2.sharePost
|
||||||
|
{ url = flags.config.baseUrl ++ "/api/v1/share/search/stats"
|
||||||
|
, token = token
|
||||||
|
, body = Http.jsonBody (Api.Model.ItemQuery.encode search)
|
||||||
|
, expect = Http.expectJson receive Api.Model.SearchStats.decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
shareAttachmentPreviewURL : String -> String
|
shareAttachmentPreviewURL : String -> String
|
||||||
shareAttachmentPreviewURL id =
|
shareAttachmentPreviewURL id =
|
||||||
"/api/v1/share/attachment/" ++ id ++ "/preview?withFallback=true"
|
"/api/v1/share/attachment/" ++ id ++ "/preview?withFallback=true"
|
||||||
|
@ -15,6 +15,7 @@ module Comp.SearchMenu exposing
|
|||||||
, isFulltextSearch
|
, isFulltextSearch
|
||||||
, isNamesSearch
|
, isNamesSearch
|
||||||
, linkTargetMsg
|
, linkTargetMsg
|
||||||
|
, setFromStats
|
||||||
, textSearchString
|
, textSearchString
|
||||||
, update
|
, update
|
||||||
, updateDrop
|
, updateDrop
|
||||||
@ -379,6 +380,11 @@ type Msg
|
|||||||
| ToggleOpenAllAkkordionTabs
|
| ToggleOpenAllAkkordionTabs
|
||||||
|
|
||||||
|
|
||||||
|
setFromStats : SearchStats -> Msg
|
||||||
|
setFromStats stats =
|
||||||
|
GetStatsResp (Ok stats)
|
||||||
|
|
||||||
|
|
||||||
linkTargetMsg : LinkTarget -> Maybe Msg
|
linkTargetMsg : LinkTarget -> Maybe Msg
|
||||||
linkTargetMsg linkTarget =
|
linkTargetMsg linkTarget =
|
||||||
case linkTarget of
|
case linkTarget of
|
||||||
|
@ -245,6 +245,12 @@ makeWorkModel sel model =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
noEmptyTags : Model -> Bool
|
||||||
|
noEmptyTags model =
|
||||||
|
Dict.filter (\k -> \v -> v.count == 0) model.availableTags
|
||||||
|
|> Dict.isEmpty
|
||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= ToggleTag String
|
= ToggleTag String
|
||||||
| ToggleCat String
|
| ToggleCat String
|
||||||
@ -422,6 +428,7 @@ viewTagsDrop2 texts ddm wm settings model =
|
|||||||
[ a
|
[ a
|
||||||
[ class S.secondaryBasicButtonPlain
|
[ class S.secondaryBasicButtonPlain
|
||||||
, class "border rounded flex-none px-1 py-1"
|
, class "border rounded flex-none px-1 py-1"
|
||||||
|
, classList [ ( "hidden", noEmptyTags model ) ]
|
||||||
, href "#"
|
, href "#"
|
||||||
, onClick ToggleShowEmpty
|
, onClick ToggleShowEmpty
|
||||||
]
|
]
|
||||||
|
@ -9,6 +9,7 @@ module Page.Share.Data exposing (Mode(..), Model, Msg(..), PageError(..), init)
|
|||||||
|
|
||||||
import Api
|
import Api
|
||||||
import Api.Model.ItemLightList exposing (ItemLightList)
|
import Api.Model.ItemLightList exposing (ItemLightList)
|
||||||
|
import Api.Model.SearchStats exposing (SearchStats)
|
||||||
import Api.Model.ShareSecret exposing (ShareSecret)
|
import Api.Model.ShareSecret exposing (ShareSecret)
|
||||||
import Api.Model.ShareVerifyResult exposing (ShareVerifyResult)
|
import Api.Model.ShareVerifyResult exposing (ShareVerifyResult)
|
||||||
import Comp.ItemCardList
|
import Comp.ItemCardList
|
||||||
@ -41,7 +42,6 @@ type alias Model =
|
|||||||
, verifyResult : ShareVerifyResult
|
, verifyResult : ShareVerifyResult
|
||||||
, passwordModel : PasswordModel
|
, passwordModel : PasswordModel
|
||||||
, pageError : PageError
|
, pageError : PageError
|
||||||
, items : ItemLightList
|
|
||||||
, searchMenuModel : Comp.SearchMenu.Model
|
, searchMenuModel : Comp.SearchMenu.Model
|
||||||
, powerSearchInput : Comp.PowerSearchInput.Model
|
, powerSearchInput : Comp.PowerSearchInput.Model
|
||||||
, searchInProgress : Bool
|
, searchInProgress : Bool
|
||||||
@ -58,7 +58,6 @@ emptyModel flags =
|
|||||||
, passwordFailed = False
|
, passwordFailed = False
|
||||||
}
|
}
|
||||||
, pageError = PageErrorNone
|
, pageError = PageErrorNone
|
||||||
, items = Api.Model.ItemLightList.empty
|
|
||||||
, searchMenuModel = Comp.SearchMenu.init flags
|
, searchMenuModel = Comp.SearchMenu.init flags
|
||||||
, powerSearchInput = Comp.PowerSearchInput.init
|
, powerSearchInput = Comp.PowerSearchInput.init
|
||||||
, searchInProgress = False
|
, searchInProgress = False
|
||||||
@ -79,6 +78,7 @@ init shareId flags =
|
|||||||
type Msg
|
type Msg
|
||||||
= VerifyResp (Result Http.Error ShareVerifyResult)
|
= VerifyResp (Result Http.Error ShareVerifyResult)
|
||||||
| SearchResp (Result Http.Error ItemLightList)
|
| SearchResp (Result Http.Error ItemLightList)
|
||||||
|
| StatsResp (Result Http.Error SearchStats)
|
||||||
| SetPassword String
|
| SetPassword String
|
||||||
| SubmitPassword
|
| SubmitPassword
|
||||||
| SearchMenuMsg Comp.SearchMenu.Msg
|
| SearchMenuMsg Comp.SearchMenu.Msg
|
||||||
|
@ -91,6 +91,16 @@ update flags settings shareId msg model =
|
|||||||
SearchResp (Err err) ->
|
SearchResp (Err err) ->
|
||||||
noSub ( { model | pageError = PageErrorHttp err, searchInProgress = False }, Cmd.none )
|
noSub ( { model | pageError = PageErrorHttp err, searchInProgress = False }, Cmd.none )
|
||||||
|
|
||||||
|
StatsResp (Ok stats) ->
|
||||||
|
update flags
|
||||||
|
settings
|
||||||
|
shareId
|
||||||
|
(SearchMenuMsg (Comp.SearchMenu.setFromStats stats))
|
||||||
|
model
|
||||||
|
|
||||||
|
StatsResp (Err err) ->
|
||||||
|
noSub ( { model | pageError = PageErrorHttp err }, Cmd.none )
|
||||||
|
|
||||||
SetPassword pw ->
|
SetPassword pw ->
|
||||||
let
|
let
|
||||||
pm =
|
pm =
|
||||||
@ -191,8 +201,14 @@ makeSearchCmd flags model =
|
|||||||
, query = Q.renderMaybe mq
|
, query = Q.renderMaybe mq
|
||||||
, searchMode = Just (Data.SearchMode.asString Data.SearchMode.Normal)
|
, searchMode = Just (Data.SearchMode.asString Data.SearchMode.Normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchCmd =
|
||||||
|
Api.searchShare flags model.verifyResult.token (request xq) SearchResp
|
||||||
|
|
||||||
|
statsCmd =
|
||||||
|
Api.searchShareStats flags model.verifyResult.token (request xq) StatsResp
|
||||||
in
|
in
|
||||||
Api.searchShare flags model.verifyResult.token (request xq) SearchResp
|
Cmd.batch [ searchCmd, statsCmd ]
|
||||||
|
|
||||||
|
|
||||||
linkTargetMsg : LinkTarget -> Maybe Msg
|
linkTargetMsg : LinkTarget -> Maybe Msg
|
||||||
|
Loading…
x
Reference in New Issue
Block a user