git.iko.soy/rowtype-yoga/purescript-docs-search
on master help sign in
Group modules by package in the sidebar (#34)
commite5c1386ed5d30b12d0a9b194e7d8cc67bbc5ba1e authorVladimir Kalnitsky <klntsky@gmail.com> dateWed 8 Jul 2020, 14:27 (6 years ago) parentb1f9554
13 files changed+346−30
spago.dhall+2 src/Docs/Search/App.purs+72−12 src/Docs/Search/App/SearchResults.purs+1−1 src/Docs/Search/App/Sidebar.purs+157 src/Docs/Search/Config.purs+10−3 src/Docs/Search/Declarations.purs+3−7 src/Docs/Search/Engine.purs+5−2 src/Docs/Search/IndexBuilder.purs+13−3 src/Docs/Search/Interactive.purs+3−1 src/Docs/Search/ModuleIndex.js+24 src/Docs/Search/ModuleIndex.purs+50 src/Docs/Search/PackageIndex.js+1−1 src/Docs/Search/Types.purs+5
spago.dhall+2
@@ -29,12 +29,14 @@
2929 , "node-readline"
3030 , "optparse"
3131 , "profunctor"
32+ , "profunctor-lenses"
3233 , "search-trie"
3334 , "string-parsers"
3435 , "strings"
3536 , "test-unit"
3637 , "web-dom"
3738 , "web-html"
39+ , "web-storage"
3840 ]
3941, packages =
4042 ./packages.dhall
src/Docs/Search/App.purs+72−12
@@ -1,13 +1,14 @@
11-- | This is the main module of the client-side Halogen app.
22module Docs.Search.App where
33
4−import Prelude
5−
64import Docs.Search.App.SearchField as SearchField
75import Docs.Search.App.SearchResults as SearchResults
6+import Docs.Search.App.Sidebar as Sidebar
87import Docs.Search.Extra (whenJust)
8+import Docs.Search.ModuleIndex as ModuleIndex
99import Docs.Search.PackageIndex as PackageIndex
1010
11+import Prelude
1112import Control.Coroutine as Coroutine
1213import Data.Maybe (Maybe(..))
1314import Data.Newtype (wrap)
@@ -17,6 +18,7 @@ import Halogen as H
1718import Halogen.Aff as HA
1819import Halogen.VDom.Driver (runUI)
1920import MarkdownIt as MD
21+import Web.DOM.ChildNode as ChildNode
2022import Web.DOM.Document as Document
2123import Web.DOM.Element as Element
2224import Web.DOM.Node as Node
@@ -24,10 +26,12 @@ import Web.DOM.ParentNode as ParentNode
2426import Web.DOM.Text as Text
2527import Web.Event.EventTarget (addEventListener, eventListener)
2628import Web.HTML as HTML
29+import Web.HTML.Event.HashChangeEvent.EventTypes (hashchange)
2730import Web.HTML.HTMLDocument as HTMLDocument
2831import Web.HTML.HTMLElement (fromElement)
2932import Web.HTML.Window as Window
30−import Web.HTML.Event.HashChangeEvent.EventTypes (hashchange)
33+import Web.HTML.Event.EventTypes (focus)
34+
3135
3236main :: Effect Unit
3337main = do
@@ -40,11 +44,21 @@ main = do
4044 -- Initialize a `markdown-it` instance (we need it to render the docs as markdown)
4145 markdownIt <- MD.newMarkdownIt MD.Default mempty
4246
43− whenJust mbContainers \ { searchField, searchResults, pageContents } -> do
47+ whenJust mbContainers \ { searchField
48+ , searchResults
49+ , pageContents
50+ , sidebarContainer
51+ , realSidebar } -> do
52+
53+ -- Hide real sidebar completely - we are going to recreate it as Halogen component.
54+ ChildNode.remove $ Element.toChildNode realSidebar
55+
4456 HA.runHalogenAff do
4557 packageIndex <- PackageIndex.loadPackageIndex
58+ moduleIndex <- ModuleIndex.loadModuleIndex
4659
4760 let initialSearchEngineState = { packageIndex: packageIndex
61+ , moduleIndex: moduleIndex
4862 , index: mempty
4963 , typeIndex: mempty
5064 }
@@ -53,6 +67,9 @@ main = do
5367 SearchResults.mkComponent initialSearchEngineState pageContents markdownIt
5468
5569 sfio <- runUI SearchField.component unit searchField
70+ sbio <- do
71+ component <- Sidebar.mkComponent moduleIndex
72+ runUI component unit sidebarContainer
5673 srio <- runUI resultsComponent unit searchResults
5774
5875 sfio.subscribe $
@@ -72,6 +89,16 @@ main = do
7289
7390 addEventListener hashchange listener true (Window.toEventTarget win)
7491
92+ -- Subscribe to window focus events
93+ H.liftEffect do
94+
95+ listener <-
96+ eventListener \event ->
97+ launchAff_ do
98+ sbio.query $ H.tell Sidebar.UpdateModuleGrouping
99+
100+ addEventListener focus listener true (Window.toEventTarget win)
101+
75102
76103insertStyle :: Document.Document -> Effect Unit
77104insertStyle doc = do
@@ -102,24 +129,49 @@ insertStyle doc = do
102129 /* Add a margin between badge icons and package/module names. */
103130 margin-right: 0.25em;
104131 }
132+ .li-package {
133+ font-weight: bold;
134+ cursor: pointer;
135+ color: #c4953a;
136+ }
137+ /* Make spaces narrower in the sidebar */
138+ .li-package > details > ul {
139+ margin-top: auto;
140+ margin-bottom: auto;
141+ }
142+ #group-modules__label, #group-modules__input {
143+ display: inline-block;
144+ vertical-align: middle;
145+ color: #666666;
146+ font-size: 0.8rem;
147+ font-weight: 300;
148+ letter-spacing: 1px;
149+ margin-bottom: -0.8em;
150+ }
151+ summary:focus {
152+ outline: none;
153+ }
105154 """
106155
107156 mbHead <-
108157 ParentNode.querySelector (wrap "head") (Document.toParentNode doc)
109158
110159 whenJust mbHead \head -> do
111−
112160 contents <- Document.createTextNode styleContents doc
113161 style <- Document.createElement "style" doc
114162 void $ Node.appendChild (Text.toNode contents) (Element.toNode style)
115163 void $ Node.appendChild (Element.toNode style) (Element.toNode head)
116164
165+
117166-- | Query the DOM for specific elements that should always be present.
118167getContainers
119168 :: Document.Document
120169 -> Effect (Maybe { searchField :: HTML.HTMLElement
121170 , searchResults :: HTML.HTMLElement
122− , pageContents :: Element.Element })
171+ , pageContents :: Element.Element
172+ , sidebarContainer :: HTML.HTMLElement
173+ , realSidebar :: Element.Element
174+ })
123175getContainers doc = do
124176 let docPN = Document.toParentNode doc
125177 mbBanner <-
@@ -128,13 +180,21 @@ getContainers doc = do
128180 ParentNode.querySelector (wrap ".everything-except-footer") docPN
129181 mbContainer <-
130182 ParentNode.querySelector (wrap ".everything-except-footer > .container") docPN
183+ mbMainContainer <-
184+ ParentNode.querySelector (wrap ".everything-except-footer > main") docPN
185+ mbRealSidebar <-
186+ ParentNode.querySelector (wrap ".col--aside") docPN
131187 case unit of
132− _ | Just banner <- mbBanner
133− , Just everything <- mbEverything
134− , Just pageContents <- mbContainer -> do
188+ _ | Just banner <- mbBanner
189+ , Just everything <- mbEverything
190+ , Just pageContents <- mbContainer
191+ , Just mainContainer <- mbMainContainer
192+ , Just realSidebar <- mbRealSidebar -> do
135193 search <- Document.createElement "div" doc
136194 void $ Node.appendChild (Element.toNode search) (Element.toNode banner)
137− pure $ fromElement search >>= \searchField ->
138− fromElement everything >>= \searchResults ->
139− pure { searchField, searchResults, pageContents }
195+ pure do
196+ searchField <- fromElement search
197+ searchResults <- fromElement everything
198+ sidebarContainer <- fromElement mainContainer
199+ pure { searchField, searchResults, pageContents, realSidebar, sidebarContainer }
140200 | otherwise -> pure Nothing
src/Docs/Search/App/SearchResults.purs+1−1
@@ -168,7 +168,7 @@ hidePageContents
168168hidePageContents = do
169169 state <- H.get
170170 H.liftEffect do
171− Element.setAttribute "style" "display: none" state.contents
171+ Element.setAttribute "style" "display: none;" state.contents
172172
173173
174174render
src/Docs/Search/App/Sidebar.pursnew file+157
@@ -0,0 +1,157 @@
1+module Docs.Search.App.Sidebar where
2+
3+import Docs.Search.Config (config)
4+import Docs.Search.ModuleIndex (ModuleIndex)
5+import Docs.Search.Types (ModuleName, PackageName)
6+
7+import Prelude
8+import Data.Array as Array
9+import Data.Lens ((.~))
10+import Data.Lens.Record (prop)
11+import Data.List (foldr)
12+import Data.Map (Map)
13+import Data.Map as Map
14+import Data.Maybe (Maybe(..), isJust)
15+import Data.Newtype (wrap)
16+import Data.Set (Set)
17+import Data.Set as Set
18+import Data.Symbol (SProxy(..))
19+import Data.Tuple.Nested (type (/\), (/\))
20+import Effect (Effect)
21+import Effect.Aff (Aff)
22+import Halogen as H
23+import Halogen.HTML as HH
24+import Halogen.HTML.Events as HE
25+import Halogen.HTML.Properties as HP
26+import Web.HTML as HTML
27+import Web.HTML.Window as Window
28+import Web.Storage.Storage as Storage
29+
30+
31+data Action = ToggleGrouping GroupingMode
32+
33+data Query a = UpdateModuleGrouping a
34+
35+data GroupingMode = GroupByPackage | DontGroup
36+
37+derive instance groupingModeEq :: Eq GroupingMode
38+
39+
40+type State = { moduleIndex :: Map PackageName (Set ModuleName)
41+ , groupingMode :: GroupingMode
42+ , moduleNames :: Array ModuleName
43+ }
44+
45+
46+mkComponent
47+ :: forall i
48+ . ModuleIndex
49+ -> Aff (H.Component HH.HTML Query i Action Aff)
50+mkComponent moduleIndex = do
51+ groupingMode <- H.liftEffect loadGroupingModeFromLocalStorage
52+ pure $
53+ H.mkComponent
54+ { initialState: const { moduleIndex
55+ , groupingMode
56+ , moduleNames
57+ }
58+ , render
59+ , eval: H.mkEval $ H.defaultEval { handleAction = handleAction
60+ , handleQuery = handleQuery
61+ }
62+ }
63+ where
64+ moduleNames = Array.sort $ Array.fromFoldable $ foldr Set.union mempty moduleIndex
65+
66+
67+handleAction
68+ :: forall o
69+ . Action
70+ -> H.HalogenM State Action () o Aff Unit
71+handleAction (ToggleGrouping groupingMode) = do
72+ H.modify_ (_groupingMode .~ groupingMode)
73+
74+ H.liftEffect do
75+ window <- HTML.window
76+ localStorage <- Window.localStorage window
77+
78+ if groupingMode == GroupByPackage
79+ then Storage.setItem config.groupModulesItem "true" localStorage
80+ else Storage.removeItem config.groupModulesItem localStorage
81+
82+
83+handleQuery
84+ :: forall a i
85+ . Query a
86+ -> H.HalogenM State i () Action Aff (Maybe a)
87+handleQuery (UpdateModuleGrouping next) = do
88+ oldGroupingMode <- H.get <#> _.groupingMode
89+ newGroupingMode <- H.liftEffect loadGroupingModeFromLocalStorage
90+ when (oldGroupingMode /= newGroupingMode) do
91+ H.modify_ (_groupingMode .~ newGroupingMode)
92+ pure Nothing
93+
94+
95+render
96+ :: forall m
97+ . State
98+ -> H.ComponentHTML Action () m
99+render { moduleIndex, groupingMode, moduleNames } =
100+
101+ HH.div [ HP.classes [ wrap "col", wrap "col--aside" ] ]
102+
103+ [ HH.h3_ [ HH.text "Modules" ]
104+ , HH.input [ HP.id_ "group-modules__input"
105+ , HP.type_ HP.InputCheckbox
106+ , HP.checked (groupingMode == GroupByPackage)
107+ , HE.onChecked $ Just <<< ToggleGrouping <<< isCheckedToGroupingMode
108+ ]
109+
110+ , HH.text " "
111+ , HH.label [ HP.for "group-modules__input"
112+ , HP.id_ "group-modules__label"
113+ ]
114+ [ HH.text " GROUP BY PACKAGE" ]
115+
116+ , HH.ul_ $ if groupingMode == GroupByPackage
117+ then renderPackageEntry <$> packageList
118+ else renderModuleName <$> moduleNames
119+ ]
120+ where
121+
122+ renderPackageEntry (packageName /\ modules) =
123+ HH.li [ HP.classes [ wrap "li-package" ] ]
124+ [ HH.details_
125+ [ HH.summary_ [ HH.text packageName ]
126+ , HH.ul_ $ Set.toUnfoldable modules <#> renderModuleName
127+ ]
128+ ]
129+
130+ renderModuleName moduleName =
131+ HH.li_
132+ [ HH.a [ HP.href (moduleName <> ".html") ]
133+ [ HH.text moduleName ]
134+ ]
135+
136+ packageList :: Array (String /\ Set ModuleName)
137+ packageList = Map.toUnfoldable moduleIndex
138+
139+
140+-- | Decide whether to group modules by package in the sidebar, using localStorage.
141+loadGroupingModeFromLocalStorage :: Effect GroupingMode
142+loadGroupingModeFromLocalStorage = do
143+ window <- HTML.window
144+ localStorage <- Window.localStorage window
145+ mbGroupModules <- Storage.getItem config.groupModulesItem localStorage
146+ pure $ if isJust mbGroupModules then GroupByPackage else DontGroup
147+
148+
149+-- | Convert checkbox status to sidebar mode
150+isCheckedToGroupingMode :: Boolean -> GroupingMode
151+isCheckedToGroupingMode = if _ then GroupByPackage else DontGroup
152+
153+
154+-- Some optics:
155+
156+_groupingMode :: forall a b rest. (a -> b) -> { groupingMode :: a | rest } -> { groupingMode :: b | rest }
157+_groupingMode = prop (SProxy :: SProxy "groupingMode")
src/Docs/Search/Config.purs+10−3
@@ -7,6 +7,9 @@ config ::
77 { declIndexDirectory :: String
88 , mkIndexPartLoadPath :: Int -> String
99 , mkIndexPartPath :: Int -> String
10+ , moduleIndexPath :: String
11+ , moduleIndexLoadPath :: String
12+ , groupModulesItem :: String
1013 , packageInfoPath :: String
1114 , packageInfoLoadPath :: String
1215 , mkShapeScriptPath :: String -> String
@@ -43,9 +46,13 @@ config =
4346 \(partId :: Int) -> "html/index/declarations/" <> show partId <> ".js"
4447 , mkIndexPartLoadPath:
4548 \(partId :: Int) -> "./index/declarations/" <> show partId <> ".js"
46− , packageInfoPath: "generated-docs/html/index/packages.json"
47− -- ^ Path to package index.
48− , packageInfoLoadPath: "./index/packages.json"
49+ , moduleIndexPath: "generated-docs/html/index/modules.js"
50+ , moduleIndexLoadPath: "./index/modules.js"
51+ -- ^ Used to load mode index to the browser scope.
52+ , groupModulesItem: "PureScriptDocsSearchGroupModules"
53+ -- ^ localStorage key to save sidebar checkbox value to.
54+ , packageInfoPath: "generated-docs/html/index/packages.js"
55+ , packageInfoLoadPath: "./index/packages.js"
4956 -- ^ Used to load package index to the browser scope.
5057 , resultsCount: 25
5158 -- ^ How many results to show by default?
src/Docs/Search/Declarations.purs+3−7
@@ -4,15 +4,16 @@ import Docs.Search.DocsJson (ChildDeclType(..), ChildDeclaration(..), DeclType(.
44import Docs.Search.PackageIndex (Scores)
55import Docs.Search.SearchResult (ResultInfo(..), SearchResult(..))
66import Docs.Search.TypeDecoder (Constraint(..), QualifiedName(..), Type(..), Kind, joinForAlls)
7+import Docs.Search.Types (ModuleName, PackageName)
78
89import Prelude
9−
1010import Control.Alt ((<|>))
1111import Data.Array ((!!))
1212import Data.Array as Array
1313import Data.Foldable (foldr)
1414import Data.List (List, (:))
1515import Data.List as List
16+import Data.Map as Map
1617import Data.Maybe (Maybe(..), fromMaybe)
1718import Data.Newtype (class Newtype, unwrap, wrap)
1819import Data.Search.Trie (Trie, alter)
@@ -20,11 +21,6 @@ import Data.String.CodeUnits (stripPrefix, stripSuffix, toCharArray)
2021import Data.String.Common (split) as String
2122import Data.String.Common (toLower)
2223import Data.String.Pattern (Pattern(..))
23−import Data.Map as Map
24−
25−
26−type ModuleName = String
27−type PackageName = String
2824
2925
3026newtype Declarations
@@ -191,7 +187,7 @@ getLevelAndName DeclExternKind name = { name, declLevel: KindLevel }
191187-- | Extract package name from `sourceSpan.name`, which contains path to
192188-- | the source file. If `ModuleName` string starts with `Prim.`, it's a
193189-- | built-in (guaranteed by the compiler).
194−extractPackageName :: ModuleName -> Maybe SourceSpan -> String
190+extractPackageName :: ModuleName -> Maybe SourceSpan -> PackageName
195191extractPackageName moduleName _
196192 | String.split (Pattern ".") moduleName !! 0 == Just "Prim" = "<builtin>"
197193extractPackageName _ Nothing = "<unknown>"
src/Docs/Search/Engine.purs+5−2
@@ -1,6 +1,7 @@
11module Docs.Search.Engine where
22
33import Docs.Search.PackageIndex (PackageIndex, PackageResult)
4+import Docs.Search.ModuleIndex (ModuleIndex)
45import Docs.Search.SearchResult (SearchResult, typeOfResult)
56import Docs.Search.TypeQuery (TypeQuery(..), parseTypeQuery, penalty)
67
@@ -36,6 +37,7 @@ type EngineState index typeIndex
3637 = { index :: index
3738 , typeIndex :: typeIndex
3839 , packageIndex :: PackageIndex
40+ , moduleIndex :: ModuleIndex
3941 }
4042
4143
@@ -44,9 +46,10 @@ mkEngineState
4446 . index
4547 -> typeIndex
4648 -> PackageIndex
49+ -> ModuleIndex
4750 -> EngineState index typeIndex
48−mkEngineState index typeIndex packageIndex =
49− { index, typeIndex, packageIndex }
51+mkEngineState index typeIndex packageIndex moduleIndex =
52+ { index, typeIndex, packageIndex, moduleIndex }
5053
5154
5255data Result
src/Docs/Search/IndexBuilder.purs+13−3
@@ -1,16 +1,16 @@
11module Docs.Search.IndexBuilder where
22
3+import Docs.Search.BrowserEngine (getPartId)
34import Docs.Search.Config (config)
45import Docs.Search.Declarations (Declarations(..), mkDeclarations)
56import Docs.Search.DocsJson (DocsJson)
67import Docs.Search.Extra ((>#>))
7−import Docs.Search.BrowserEngine (getPartId)
8+import Docs.Search.ModuleIndex (ModuleIndex, mkModuleIndex)
89import Docs.Search.PackageIndex (PackageInfo, mkPackageInfo, mkScores)
910import Docs.Search.SearchResult (SearchResult)
1011import Docs.Search.TypeIndex (TypeIndex, mkTypeIndex)
1112
1213import Prelude
13−
1414import Data.Argonaut.Core (stringify)
1515import Data.Argonaut.Decode (decodeJson)
1616import Data.Argonaut.Encode (encodeJson)
@@ -78,6 +78,7 @@ run' cfg = do
7878 index = mkDeclarations scores docsJsons
7979 typeIndex = mkTypeIndex scores docsJsons
8080 packageInfo = mkPackageInfo packageMetas
81+ moduleIndex = mkModuleIndex index
8182
8283 createDirectories cfg
8384
@@ -85,6 +86,7 @@ run' cfg = do
8586 ignore <$> parallel (writeIndex cfg index)
8687 <*> parallel (writeTypeIndex cfg typeIndex)
8788 <*> parallel (writePackageInfo packageInfo)
89+ <*> parallel (writeModuleIndex moduleIndex)
8890 <*> parallel (if cfg.noPatch
8991 then pure unit
9092 else patchDocs cfg)
@@ -105,7 +107,7 @@ run' cfg = do
105107 show countOfPackages <>
106108 " packages to the search index."
107109
108− where ignore _ _ _ _ _ = unit
110+ where ignore _ _ _ _ _ _ _ = unit
109111
110112
111113-- | Exit early if something is missing.
@@ -220,6 +222,14 @@ writePackageInfo packageInfo = do
220222 where
221223 header = "window.DocsSearchPackageIndex = "
222224
225+
226+writeModuleIndex :: ModuleIndex -> Aff Unit
227+writeModuleIndex moduleIndex = do
228+ writeTextFile UTF8 config.moduleIndexPath $
229+ header <> stringify (encodeJson moduleIndex)
230+ where
231+ header = "window.DocsSearchModuleIndex = "
232+
223233-- | Get a mapping from index parts to index contents.
224234getIndex :: Declarations -> Map Int (Array (Tuple String (Array SearchResult)))
225235getIndex (Declarations trie) =
src/Docs/Search/Interactive.purs+3−1
@@ -2,6 +2,7 @@
22module Docs.Search.Interactive where
33
44import Docs.Search.Declarations (Declarations, mkDeclarations)
5+import Docs.Search.ModuleIndex (mkModuleIndex)
56import Docs.Search.DocsJson (DataDeclType(..))
67import Docs.Search.Engine (mkEngineState, Result(..))
78import Docs.Search.Engine as Engine
@@ -51,7 +52,8 @@ run cfg = launchAff_ $ do
5152 index = mkDeclarations scores docsJsons
5253 typeIndex = docsJsons >>= resultsWithTypes scores
5354 packageIndex = mkPackageIndex $ mkPackageInfo packageMetas
54− engineState = mkEngineState (unwrap index) typeIndex packageIndex
55+ moduleIndex = mkModuleIndex index
56+ engineState = mkEngineState (unwrap index) typeIndex packageIndex moduleIndex
5557
5658 let countOfDefinitions = Trie.size $ unwrap index
5759 countOfTypeDefinitions = Array.length typeIndex
src/Docs/Search/ModuleIndex.jsnew file+24
@@ -0,0 +1,24 @@
1+/* global exports */
2+
3+exports.load = function (url) {
4+ return function () {
5+ return new Promise(function (resolve, reject) {
6+ if (typeof window.DocsSearchModuleIndex === 'undefined') {
7+ var script = document.createElement('script');
8+ script.type = 'text/javascript';
9+ script.src = url;
10+ script.addEventListener('load', function () {
11+ if (typeof window.DocsSearchModuleIndex === 'undefined') {
12+ reject(new Error("Couldn't load module index"));
13+ } else {
14+ resolve(window.DocsSearchModuleIndex);
15+ }
16+ });
17+ script.addEventListener('error', reject);
18+ document.body.appendChild(script);
19+ } else {
20+ resolve(window.DocsSearchModuleIndex);
21+ }
22+ });
23+ };
24+};
src/Docs/Search/ModuleIndex.pursnew file+50
@@ -0,0 +1,50 @@
1+module Docs.Search.ModuleIndex where
2+
3+import Docs.Search.Config (config)
4+import Docs.Search.Declarations (Declarations(..))
5+import Docs.Search.SearchResult (SearchResult(..))
6+import Docs.Search.Types (ModuleName, PackageName)
7+
8+import Prelude
9+import Control.Promise (Promise, toAffE)
10+import Data.Argonaut.Core (Json)
11+import Data.Argonaut.Decode (decodeJson)
12+import Data.Either (hush)
13+import Data.Foldable (foldr)
14+import Data.List (List)
15+import Data.Map (Map)
16+import Data.Map as Map
17+import Data.Maybe (fromMaybe)
18+import Data.Search.Trie as Trie
19+import Data.Set (Set)
20+import Data.Set as Set
21+import Effect (Effect)
22+import Effect.Aff (Aff)
23+
24+
25+type ModuleIndex = Map PackageName (Set ModuleName)
26+
27+
28+-- | Constructs a mapping from packages to modules
29+mkModuleIndex :: Declarations -> ModuleIndex
30+mkModuleIndex (Declarations trie) =
31+ foldr (Map.unionWith Set.union) mempty $ extract <$> Trie.values trie
32+ where
33+ extract
34+ :: List SearchResult
35+ -> Map PackageName (Set ModuleName)
36+ extract = foldr (Map.unionWith Set.union) mempty <<< map mkEntry
37+ where
38+ mkEntry (SearchResult { packageName, moduleName }) =
39+ Map.singleton packageName (Set.singleton moduleName)
40+
41+
42+loadModuleIndex :: Aff ModuleIndex
43+loadModuleIndex = do
44+ json <- toAffE $ load config.moduleIndexLoadPath
45+ pure $ fromMaybe mempty $ hush $ decodeJson json
46+
47+
48+foreign import load
49+ :: String
50+ -> Effect (Promise Json)
src/Docs/Search/PackageIndex.js+1−1
@@ -9,7 +9,7 @@ exports.load = function (url) {
99 script.src = url;
1010 script.addEventListener('load', function () {
1111 if (typeof window.DocsSearchPackageIndex === 'undefined') {
12− reject(new Error("Couldn't load index for type shape " + shape));
12+ reject(new Error("Couldn't load package index."));
1313 } else {
1414 resolve(window.DocsSearchPackageIndex);
1515 }
src/Docs/Search/Types.pursnew file+5
@@ -0,0 +1,5 @@
1+module Docs.Search.Types where
2+
3+type ModuleName = String
4+
5+type PackageName = String