Support search engine escaping of spaces (+) (#215)

This commit is contained in:
Marek Fajkus 2020-10-28 19:51:01 +01:00 committed by GitHub
parent adbb224490
commit e412085ea8
Failed to generate hash of commit
6 changed files with 207 additions and 100 deletions

View file

@ -1,7 +1,5 @@
module Main exposing (main)
--exposing (UrlRequest(..))
import Browser
import Browser.Navigation
import Html

View file

@ -43,6 +43,7 @@ import Html.Parser.Util
import Json.Decode
import Json.Encode
import Regex
import Route
import Search
@ -102,7 +103,7 @@ update navKey msg model =
let
( newModel, newCmd ) =
Search.update
"options"
Route.Options
navKey
subMsg
model
@ -116,8 +117,7 @@ update navKey msg model =
view : Model -> Html Msg
view model =
Search.view
"options"
Search.view { toRoute = Route.Options, categoryName = "options" }
"Search NixOS options"
model
viewSuccess

View file

@ -43,6 +43,7 @@ import Json.Decode
import Json.Decode.Pipeline
import Json.Encode
import Regex
import Route
import Search
@ -139,7 +140,7 @@ update navKey msg model =
let
( newModel, newCmd ) =
Search.update
"packages"
Route.Packages
navKey
subMsg
model
@ -153,8 +154,7 @@ update navKey msg model =
view : Model -> Html Msg
view model =
Search.view
"packages"
Search.view { toRoute = Route.Packages, categoryName = "packages" }
"Search NixOS packages"
model
viewSuccess

View file

@ -1,9 +1,11 @@
module Route exposing (Route(..), fromUrl, href, replaceUrl)
module Route exposing (Route(..), fromUrl, href, replaceUrl, routeToString)
import Browser.Navigation
import Html
import Html.Attributes
import Route.SearchQuery
import Url
import Url.Builder exposing (QueryParameter)
import Url.Parser exposing ((<?>))
import Url.Parser.Query
@ -19,30 +21,32 @@ type Route
| Options (Maybe String) (Maybe String) (Maybe String) (Maybe Int) (Maybe Int) (Maybe String)
parser : Url.Parser.Parser (Route -> msg) msg
parser =
parser : Url.Url -> Url.Parser.Parser (Route -> msg) msg
parser url =
let
rawQuery =
Route.SearchQuery.toRawQuery url
withSearchQuery : (a -> Maybe String -> b) -> a -> b
withSearchQuery f channel =
f channel <|
Maybe.andThen Route.SearchQuery.searchQueryToString <|
Maybe.andThen (Route.SearchQuery.searchString "query") rawQuery
in
Url.Parser.oneOf
[ Url.Parser.map
Home
Url.Parser.top
, Url.Parser.map
NotFound
(Url.Parser.s "not-found")
, Url.Parser.map
Packages
[ Url.Parser.map Home Url.Parser.top
, Url.Parser.map NotFound (Url.Parser.s "not-found")
, Url.Parser.map (withSearchQuery Packages)
(Url.Parser.s "packages"
<?> Url.Parser.Query.string "channel"
<?> Url.Parser.Query.string "query"
<?> Url.Parser.Query.string "show"
<?> Url.Parser.Query.int "from"
<?> Url.Parser.Query.int "size"
<?> Url.Parser.Query.string "sort"
)
, Url.Parser.map
Options
, Url.Parser.map (withSearchQuery Options)
(Url.Parser.s "options"
<?> Url.Parser.Query.string "channel"
<?> Url.Parser.Query.string "query"
<?> Url.Parser.Query.string "show"
<?> Url.Parser.Query.int "from"
<?> Url.Parser.Query.int "size"
@ -71,7 +75,7 @@ fromUrl url =
-- This makes it *literally* the path, so we can proceed
-- with parsing as if it had been a normal path all along.
--{ url | path = Maybe.withDefault "" url.fragment, fragment = Nothing }
Url.Parser.parse parser url
Url.Parser.parse (parser url) url
@ -79,41 +83,63 @@ fromUrl url =
routeToString : Route -> String
routeToString page =
routeToString =
let
( path, query ) =
routeToPieces page
buildString ( path, query, searchQuery ) =
Route.SearchQuery.absolute path query <|
Maybe.withDefault [] <|
Maybe.map List.singleton searchQuery
in
"/" ++ String.join "/" path ++ "?" ++ String.join "&" (List.filterMap Basics.identity query)
buildString << routeToPieces
routeToPieces : Route -> ( List String, List (Maybe String) )
routeToPieces : Route -> ( List String, List QueryParameter, Maybe ( String, Route.SearchQuery.SearchQuery ) )
routeToPieces page =
case page of
Home ->
( [], [] )
let
channelQ =
Maybe.map (Url.Builder.string "channel")
NotFound ->
( [ "not-found" ], [] )
queryQ =
Maybe.map (Route.SearchQuery.toSearchQuery "query")
Packages channel query show from size sort ->
( [ "packages" ]
, [ channel
, query
, show
, Maybe.map String.fromInt from
, Maybe.map String.fromInt size
, sort
]
)
showQ =
Maybe.map (Url.Builder.string "show")
Options channel query show from size sort ->
( [ "options" ]
, [ channel
, query
, show
, Maybe.map String.fromInt from
, Maybe.map String.fromInt size
, sort
]
)
fromQ =
Maybe.map (Url.Builder.int "from")
sizeQ =
Maybe.map (Url.Builder.int "size")
sortQ =
Maybe.map (Url.Builder.string "sort")
in
(\( path, urlQ, searchQuery ) -> ( path, List.filterMap identity urlQ, searchQuery )) <|
case page of
Home ->
( [], [], Nothing )
NotFound ->
( [ "not-found" ], [], Nothing )
Packages channel query show from size sort ->
( [ "packages" ]
, [ channelQ channel
, showQ show
, fromQ from
, sizeQ size
, sortQ sort
]
, queryQ query
)
Options channel query show from size sort ->
( [ "options" ]
, [ channelQ channel
, showQ show
, fromQ from
, sizeQ size
, sortQ sort
]
, queryQ query
)

87
src/Route/SearchQuery.elm Normal file
View file

@ -0,0 +1,87 @@
module Route.SearchQuery exposing
( RawQuery
, SearchQuery
, absolute
, searchQueryToString
, searchString
, toRawQuery
, toSearchQuery
)
import Dict exposing (Dict)
import Url
import Url.Builder
-- RawQuery
type RawQuery
= RawQuery (Dict String String)
chunk : String -> String -> Maybe ( String, String )
chunk sep str =
case String.split sep str of
[] ->
Nothing
[ key ] ->
Just ( key, "" )
key :: xs ->
Just ( key, String.join sep xs )
toRawQuery : Url.Url -> Maybe RawQuery
toRawQuery =
Maybe.map (RawQuery << Dict.fromList << List.filterMap (chunk "=") << String.split "&")
<< .query
-- SearchQuery
{-| This is type safe wrapper for working with search queries in url
-}
type SearchQuery
= SearchQuery String
searchString : String -> RawQuery -> Maybe SearchQuery
searchString name (RawQuery dict) =
Maybe.map SearchQuery <| Dict.get name dict
searchQueryToString : SearchQuery -> Maybe String
searchQueryToString (SearchQuery str) =
Url.percentDecode <| String.replace "+" "%20" str
toSearchQuery : String -> String -> ( String, SearchQuery )
toSearchQuery name query =
( name, SearchQuery <| String.replace "%20" "+" <| Url.percentEncode query )
{-| Build absolute URL with support for search query strings
-}
absolute : List String -> List Url.Builder.QueryParameter -> List ( String, SearchQuery ) -> String
absolute path query searchQuery =
let
searchStrings =
List.map (\( name, SearchQuery val ) -> name ++ "=" ++ val) searchQuery
|> String.join "&"
in
Url.Builder.absolute path query
|> (\str ->
str
++ (case query of
[] ->
"?" ++ searchStrings
_ ->
"&" ++ searchStrings
)
)

View file

@ -62,11 +62,23 @@ import Http
import Json.Decode
import Json.Encode
import RemoteData
import Route exposing (Route)
import Set
import Task
import Url
import Url.Builder
type alias SearchRoute =
Maybe String
-> Maybe String
-> Maybe String
-> Maybe Int
-> Maybe Int
-> Maybe String
-> Route
type alias Model a =
{ channel : String
, query : Maybe String
@ -174,12 +186,12 @@ type Msg a
update :
String
SearchRoute
-> Browser.Navigation.Key
-> Msg a
-> Model a
-> ( Model a, Cmd (Msg a) )
update path navKey msg model =
update toRoute navKey msg model =
case msg of
NoOp ->
( model
@ -193,7 +205,7 @@ update path navKey msg model =
in
( { model | sort = sort }
, createUrl
path
toRoute
model.channel
model.query
model.show
@ -218,7 +230,7 @@ update path navKey msg model =
else
createUrl
path
toRoute
channel
model.query
model.show
@ -240,7 +252,7 @@ update path navKey msg model =
else
( { model | result = RemoteData.Loading }
, createUrl
path
toRoute
model.channel
model.query
model.show
@ -258,7 +270,7 @@ update path navKey msg model =
ShowDetails selected ->
( model
, createUrl
path
toRoute
model.channel
model.query
(if model.show == Just selected then
@ -275,8 +287,10 @@ update path navKey msg model =
)
{-| TODO: Sort should be part of Route type
-}
createUrl :
String
SearchRoute
-> String
-> Maybe String
-> Maybe String
@ -284,30 +298,9 @@ createUrl :
-> Int
-> Sort
-> String
createUrl path channel query show from size sort =
[ Url.Builder.int "from" from
, Url.Builder.int "size" size
, Url.Builder.string "sort" <| toSortId sort
, Url.Builder.string "channel" channel
]
|> List.append
(query
|> Maybe.map
(\x ->
[ Url.Builder.string "query" x ]
)
|> Maybe.withDefault []
)
|> List.append
(show
|> Maybe.map
(\x ->
[ Url.Builder.string "show" x
]
)
|> Maybe.withDefault []
)
|> Url.Builder.absolute [ path ]
createUrl toRoute channel query show from size sort =
toRoute (Just channel) query show (Just from) (Just size) (Just <| toSortId sort)
|> Route.routeToString
@ -320,6 +313,7 @@ type Channel
| Release_20_03
| Release_20_09
type alias ChannelDetails =
{ id : String
, title : String
@ -455,13 +449,15 @@ fromSortId id =
view :
String
{ toRoute : SearchRoute
, categoryName : String
}
-> String
-> Model a
-> (String -> Maybe String -> SearchResult a -> Html b)
-> (Msg a -> b)
-> Html b
view path title model viewSuccess outMsg =
view { toRoute, categoryName } title model viewSuccess outMsg =
div
[ class "search-page"
]
@ -520,14 +516,15 @@ view path title model viewSuccess outMsg =
[ type_ "text"
, id "search-query-input"
, autofocus True
, placeholder <| "Search for " ++ path
, onInput (\x -> outMsg (QueryInput x))
, placeholder <| "Search for " ++ categoryName
, onInput (outMsg << QueryInput)
, value <| Maybe.withDefault "" model.query
]
[]
, div [ class "loader" ] []
, div [ class "btn-group" ]
[ button [ class "btn" ] [ text "Search" ]
[ button [ class "btn", type_ "submit" ]
[ text "Search" ]
]
]
]
@ -542,8 +539,7 @@ view path title model viewSuccess outMsg =
RemoteData.Success result ->
if result.hits.total.value == 0 then
div []
[ h4 [] [ text <| "No " ++ path ++ " found!" ]
]
[ h4 [] [ text <| "No " ++ categoryName ++ " found!" ] ]
else
div []
@ -594,9 +590,9 @@ view path title model viewSuccess outMsg =
]
]
]
, viewPager outMsg model result path
, viewPager outMsg model result toRoute
, viewSuccess model.channel model.show result
, viewPager outMsg model result path
, viewPager outMsg model result toRoute
]
RemoteData.Failure error ->
@ -629,9 +625,9 @@ viewPager :
(Msg a -> b)
-> Model a
-> SearchResult a
-> String
-> SearchRoute
-> Html b
viewPager _ model result path =
viewPager _ model result toRoute =
ul [ class "pager" ]
[ li
[ classList
@ -645,7 +641,7 @@ viewPager _ model result path =
else
href <|
createUrl
path
toRoute
model.channel
model.query
model.show
@ -667,7 +663,7 @@ viewPager _ model result path =
else
createUrl
path
toRoute
model.channel
model.query
model.show
@ -689,7 +685,7 @@ viewPager _ model result path =
else
createUrl
path
toRoute
model.channel
model.query
model.show
@ -719,7 +715,7 @@ viewPager _ model result path =
0
in
createUrl
path
toRoute
model.channel
model.query
model.show