mirror of
				https://github.com/TheAnachronism/docspell.git
				synced 2025-10-30 21:40:12 +00:00 
			
		
		
		
	Use search stats to populate search menu
This commit is contained in:
		| @@ -86,7 +86,7 @@ object BackendApp { | ||||
|       customFieldsImpl <- OCustomFields(store) | ||||
|       simpleSearchImpl = OSimpleSearch(fulltextImpl, itemSearchImpl) | ||||
|       clientSettingsImpl <- OClientSettings(store) | ||||
|       shareImpl <- Resource.pure(OShare(store, itemSearchImpl)) | ||||
|       shareImpl <- Resource.pure(OShare(store, itemSearchImpl, simpleSearchImpl)) | ||||
|     } yield new BackendApp[F] { | ||||
|       val login = loginImpl | ||||
|       val signup = signupImpl | ||||
|   | ||||
| @@ -9,15 +9,19 @@ package docspell.backend.ops | ||||
| import cats.data.OptionT | ||||
| import cats.effect._ | ||||
| import cats.implicits._ | ||||
|  | ||||
| import docspell.backend.PasswordCrypt | ||||
| import docspell.backend.auth.ShareToken | ||||
| import docspell.backend.ops.OItemSearch.{AttachmentPreviewData, Batch, Query} | ||||
| import docspell.backend.ops.OShare.{ShareQuery, VerifyResult} | ||||
| import docspell.backend.ops.OSimpleSearch.StringSearchResult | ||||
| import docspell.common._ | ||||
| import docspell.query.ItemQuery | ||||
| import docspell.query.ItemQuery.Expr.AttachId | ||||
| import docspell.store.Store | ||||
| import docspell.store.queries.SearchSummary | ||||
| import docspell.store.records.RShare | ||||
|  | ||||
| import scodec.bits.ByteVector | ||||
|  | ||||
| trait OShare[F[_]] { | ||||
| @@ -51,6 +55,9 @@ trait OShare[F[_]] { | ||||
|       shareId: Ident | ||||
|   ): OptionT[F, AttachmentPreviewData[F]] | ||||
|  | ||||
|   def searchSummary( | ||||
|       settings: OSimpleSearch.StatsSettings | ||||
|   )(shareId: Ident, q: ItemQueryString): OptionT[F, StringSearchResult[SearchSummary]] | ||||
| } | ||||
|  | ||||
| object OShare { | ||||
| @@ -101,7 +108,11 @@ object OShare { | ||||
|     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] { | ||||
|       private[this] val logger = Logger.log4s[F](org.log4s.getLogger) | ||||
|  | ||||
| @@ -238,5 +249,23 @@ object OShare { | ||||
|                 .mapFilter(_ => None) | ||||
|             else OptionT(itemSearch.findAttachmentPreview(attachId, sq.cid)) | ||||
|         } 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: | ||||
|                 $ref: "#/components/schemas/BasicResult" | ||||
|  | ||||
|   /share/search: | ||||
|   /share/search/query: | ||||
|     post: | ||||
|       operationId: "share-search" | ||||
|       operationId: "share-search-query" | ||||
|       tags: [Share] | ||||
|       summary: Performs a search in a share. | ||||
|       description: | | ||||
| @@ -1581,6 +1581,72 @@ paths: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $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: | ||||
|     post: | ||||
|   | ||||
| @@ -10,17 +10,18 @@ import cats.data.NonEmptyList | ||||
| import cats.data.OptionT | ||||
| import cats.effect._ | ||||
| import cats.implicits._ | ||||
|  | ||||
| import docspell.backend.ops.OItemSearch.AttachmentPreviewData | ||||
| import docspell.backend.ops._ | ||||
| import docspell.restapi.model.BasicResult | ||||
| import docspell.store.records.RFileMeta | ||||
| import docspell.restserver.http4s.{QueryParam => QP} | ||||
| import docspell.store.records.RFileMeta | ||||
|  | ||||
| import org.http4s._ | ||||
| import org.http4s.circe.CirceEntityEncoder._ | ||||
| import org.http4s.dsl.Http4sDsl | ||||
| import org.http4s.headers._ | ||||
| import org.http4s.headers.ETag.EntityTag | ||||
| import org.http4s.headers._ | ||||
| import org.typelevel.ci.CIString | ||||
|  | ||||
| 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]])( | ||||
|       data: OItemSearch.BinaryData[F] | ||||
|   ): F[Response[F]] = { | ||||
|   | ||||
| @@ -125,10 +125,7 @@ object AttachmentRoutes { | ||||
|         for { | ||||
|           fileData <- | ||||
|             backend.itemSearch.findAttachmentPreview(id, user.account.collective) | ||||
|           resp <- | ||||
|             fileData | ||||
|               .map(data => withResponseHeaders(Ok())(data)) | ||||
|               .getOrElse(NotFound(BasicResult(false, "Not found"))) | ||||
|           resp <- BinaryUtil.respondHead(dsl)(fileData) | ||||
|         } yield resp | ||||
|  | ||||
|       case POST -> Root / Ident(id) / "preview" => | ||||
|   | ||||
| @@ -28,11 +28,11 @@ import docspell.restserver.http4s.BinaryUtil | ||||
| import docspell.restserver.http4s.Responses | ||||
| import docspell.restserver.http4s.{QueryParam => QP} | ||||
|  | ||||
| import org.http4s.HttpRoutes | ||||
| import org.http4s.circe.CirceEntityDecoder._ | ||||
| import org.http4s.circe.CirceEntityEncoder._ | ||||
| import org.http4s.dsl.Http4sDsl | ||||
| import org.http4s.headers._ | ||||
| import org.http4s.{HttpRoutes, Response} | ||||
| import org.log4s._ | ||||
|  | ||||
| object ItemRoutes { | ||||
| @@ -415,7 +415,11 @@ object ItemRoutes { | ||||
|   def searchItems[F[_]: Sync]( | ||||
|       backend: BackendApp[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._ | ||||
|  | ||||
|     def convertFts(res: OSimpleSearch.Items.FtsItems): ItemLightList = | ||||
| @@ -459,7 +463,7 @@ object ItemRoutes { | ||||
|       settings: OSimpleSearch.StatsSettings, | ||||
|       fixQuery: Query.Fix, | ||||
|       itemQuery: ItemQueryString | ||||
|   ) = { | ||||
|   ): F[Response[F]] = { | ||||
|     import dsl._ | ||||
|  | ||||
|     backend.simpleSearch | ||||
| @@ -479,7 +483,6 @@ object ItemRoutes { | ||||
|         case StringSearchResult.ParseFailed(pf) => | ||||
|           BadRequest(BasicResult(false, s"Error reading query: ${pf.render}")) | ||||
|       } | ||||
|  | ||||
|   } | ||||
|  | ||||
|   implicit final class OptionString(opt: Option[String]) { | ||||
|   | ||||
| @@ -26,12 +26,20 @@ object ShareAttachmentRoutes { | ||||
|     val dsl = new Http4sDsl[F] {} | ||||
|     import dsl._ | ||||
|  | ||||
|     HttpRoutes.of { case req @ GET -> Root / Ident(id) / "preview" => | ||||
|       for { | ||||
|         fileData <- | ||||
|           backend.share.findAttachmentPreview(id, token.id).value | ||||
|         resp <- BinaryUtil.respond(dsl, req)(fileData) | ||||
|       } yield resp | ||||
|     HttpRoutes.of { | ||||
|       case req @ GET -> Root / Ident(id) / "preview" => | ||||
|         for { | ||||
|           fileData <- | ||||
|             backend.share.findAttachmentPreview(id, token.id).value | ||||
|           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.auth.ShareToken | ||||
| import docspell.backend.ops.OSimpleSearch | ||||
| import docspell.backend.ops.OSimpleSearch.StringSearchResult | ||||
| 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.conv.Conversions | ||||
| 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.CirceEntityEncoder._ | ||||
| import org.http4s.dsl.Http4sDsl | ||||
| import org.http4s.{HttpRoutes, Response} | ||||
|  | ||||
| object ShareSearchRoutes { | ||||
|  | ||||
| @@ -34,33 +38,68 @@ object ShareSearchRoutes { | ||||
|     val dsl = new Http4sDsl[F] {} | ||||
|     import dsl._ | ||||
|  | ||||
|     HttpRoutes.of { case req @ POST -> Root => | ||||
|       backend.share | ||||
|         .findShareQuery(token.id) | ||||
|         .semiflatMap { share => | ||||
|           for { | ||||
|             userQuery <- req.as[ItemQuery] | ||||
|             batch = Batch( | ||||
|               userQuery.offset.getOrElse(0), | ||||
|               userQuery.limit.getOrElse(cfg.maxItemPageSize) | ||||
|             ).restrictLimitTo( | ||||
|               cfg.maxItemPageSize | ||||
|             ) | ||||
|             itemQuery = ItemQueryString(userQuery.query) | ||||
|             settings = OSimpleSearch.Settings( | ||||
|               batch, | ||||
|               cfg.fullTextSearch.enabled, | ||||
|               userQuery.withDetails.getOrElse(false), | ||||
|               cfg.maxNoteLength, | ||||
|               searchMode = SearchMode.Normal | ||||
|             ) | ||||
|             account = share.asAccount | ||||
|             fixQuery = Query.Fix(account, Some(share.query.expr), None) | ||||
|             _ <- logger.debug(s"Searching in share ${share.id.id}: ${userQuery.query}") | ||||
|             resp <- ItemRoutes.searchItems(backend, dsl)(settings, fixQuery, itemQuery) | ||||
|           } yield resp | ||||
|         } | ||||
|         .getOrElseF(NotFound()) | ||||
|     HttpRoutes.of { | ||||
|       case req @ POST -> Root / "query" => | ||||
|         backend.share | ||||
|           .findShareQuery(token.id) | ||||
|           .semiflatMap { share => | ||||
|             for { | ||||
|               userQuery <- req.as[ItemQuery] | ||||
|               batch = Batch( | ||||
|                 userQuery.offset.getOrElse(0), | ||||
|                 userQuery.limit.getOrElse(cfg.maxItemPageSize) | ||||
|               ).restrictLimitTo( | ||||
|                 cfg.maxItemPageSize | ||||
|               ) | ||||
|               itemQuery = ItemQueryString(userQuery.query) | ||||
|               settings = OSimpleSearch.Settings( | ||||
|                 batch, | ||||
|                 cfg.fullTextSearch.enabled, | ||||
|                 userQuery.withDetails.getOrElse(false), | ||||
|                 cfg.maxNoteLength, | ||||
|                 searchMode = SearchMode.Normal | ||||
|               ) | ||||
|               account = share.asAccount | ||||
|               fixQuery = Query.Fix(account, Some(share.query.expr), None) | ||||
|               _ <- logger.debug(s"Searching in share ${share.id.id}: ${userQuery.query}") | ||||
|               resp <- ItemRoutes.searchItems(backend, dsl)(settings, fixQuery, itemQuery) | ||||
|             } yield resp | ||||
|           } | ||||
|           .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], | ||||
|     fields: List[FieldStats], | ||||
|     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 | ||||
|     , saveClientSettings | ||||
|     , searchShare | ||||
|     , searchShareStats | ||||
|     , sendMail | ||||
|     , setAttachmentName | ||||
|     , setCollectiveSettings | ||||
| @@ -2283,13 +2284,23 @@ verifyShare flags secret receive = | ||||
| searchShare : Flags -> String -> ItemQuery -> (Result Http.Error ItemLightList -> msg) -> Cmd msg | ||||
| searchShare flags token search receive = | ||||
|     Http2.sharePost | ||||
|         { url = flags.config.baseUrl ++ "/api/v1/share/search" | ||||
|         { url = flags.config.baseUrl ++ "/api/v1/share/search/query" | ||||
|         , token = token | ||||
|         , body = Http.jsonBody (Api.Model.ItemQuery.encode search) | ||||
|         , 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 id = | ||||
|     "/api/v1/share/attachment/" ++ id ++ "/preview?withFallback=true" | ||||
|   | ||||
| @@ -15,6 +15,7 @@ module Comp.SearchMenu exposing | ||||
|     , isFulltextSearch | ||||
|     , isNamesSearch | ||||
|     , linkTargetMsg | ||||
|     , setFromStats | ||||
|     , textSearchString | ||||
|     , update | ||||
|     , updateDrop | ||||
| @@ -379,6 +380,11 @@ type Msg | ||||
|     | ToggleOpenAllAkkordionTabs | ||||
|  | ||||
|  | ||||
| setFromStats : SearchStats -> Msg | ||||
| setFromStats stats = | ||||
|     GetStatsResp (Ok stats) | ||||
|  | ||||
|  | ||||
| linkTargetMsg : LinkTarget -> Maybe Msg | ||||
| linkTargetMsg linkTarget = | ||||
|     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 | ||||
|     = ToggleTag String | ||||
|     | ToggleCat String | ||||
| @@ -422,6 +428,7 @@ viewTagsDrop2 texts ddm wm settings model = | ||||
|             [ a | ||||
|                 [ class S.secondaryBasicButtonPlain | ||||
|                 , class "border rounded flex-none px-1 py-1" | ||||
|                 , classList [ ( "hidden", noEmptyTags model ) ] | ||||
|                 , href "#" | ||||
|                 , onClick ToggleShowEmpty | ||||
|                 ] | ||||
|   | ||||
| @@ -9,6 +9,7 @@ module Page.Share.Data exposing (Mode(..), Model, Msg(..), PageError(..), init) | ||||
|  | ||||
| import Api | ||||
| import Api.Model.ItemLightList exposing (ItemLightList) | ||||
| import Api.Model.SearchStats exposing (SearchStats) | ||||
| import Api.Model.ShareSecret exposing (ShareSecret) | ||||
| import Api.Model.ShareVerifyResult exposing (ShareVerifyResult) | ||||
| import Comp.ItemCardList | ||||
| @@ -41,7 +42,6 @@ type alias Model = | ||||
|     , verifyResult : ShareVerifyResult | ||||
|     , passwordModel : PasswordModel | ||||
|     , pageError : PageError | ||||
|     , items : ItemLightList | ||||
|     , searchMenuModel : Comp.SearchMenu.Model | ||||
|     , powerSearchInput : Comp.PowerSearchInput.Model | ||||
|     , searchInProgress : Bool | ||||
| @@ -58,7 +58,6 @@ emptyModel flags = | ||||
|         , passwordFailed = False | ||||
|         } | ||||
|     , pageError = PageErrorNone | ||||
|     , items = Api.Model.ItemLightList.empty | ||||
|     , searchMenuModel = Comp.SearchMenu.init flags | ||||
|     , powerSearchInput = Comp.PowerSearchInput.init | ||||
|     , searchInProgress = False | ||||
| @@ -79,6 +78,7 @@ init shareId flags = | ||||
| type Msg | ||||
|     = VerifyResp (Result Http.Error ShareVerifyResult) | ||||
|     | SearchResp (Result Http.Error ItemLightList) | ||||
|     | StatsResp (Result Http.Error SearchStats) | ||||
|     | SetPassword String | ||||
|     | SubmitPassword | ||||
|     | SearchMenuMsg Comp.SearchMenu.Msg | ||||
|   | ||||
| @@ -91,6 +91,16 @@ update flags settings shareId msg model = | ||||
|         SearchResp (Err err) -> | ||||
|             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 -> | ||||
|             let | ||||
|                 pm = | ||||
| @@ -191,8 +201,14 @@ makeSearchCmd flags model = | ||||
|             , query = Q.renderMaybe mq | ||||
|             , 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 | ||||
|     Api.searchShare flags model.verifyResult.token (request xq) SearchResp | ||||
|     Cmd.batch [ searchCmd, statsCmd ] | ||||
|  | ||||
|  | ||||
| linkTargetMsg : LinkTarget -> Maybe Msg | ||||
|   | ||||
		Reference in New Issue
	
	Block a user