git.iko.soy/rowtype-yoga/purescript-docs-search
on master help sign in
Group modules by package in the sidebar (#34)
e5c1386Vladimir Kalnitsky
spago.dhall+2
@@ -29,12 +29,14 @@
29 , "node-readline"
29 , "node-readline"
30 , "optparse"
30 , "optparse"
31 , "profunctor"
31 , "profunctor"
32+ , "profunctor-lenses"
32 , "search-trie"
33 , "search-trie"
33 , "string-parsers"
34 , "string-parsers"
34 , "strings"
35 , "strings"
35 , "test-unit"
36 , "test-unit"
36 , "web-dom"
37 , "web-dom"
37 , "web-html"
38 , "web-html"
39+ , "web-storage"
38 ]
40 ]
39, packages =
41, packages =
40 ./packages.dhall
42 ./packages.dhall
src/Docs/Search/App.purs+72−12
@@ -1,13 +1,14 @@
1-- | This is the main module of the client-side Halogen app.
1-- | This is the main module of the client-side Halogen app.
2module Docs.Search.App where
2module Docs.Search.App where
3
3
4−import Prelude
5−
6import Docs.Search.App.SearchField as SearchField
4import Docs.Search.App.SearchField as SearchField
7import Docs.Search.App.SearchResults as SearchResults
5import Docs.Search.App.SearchResults as SearchResults
6+import Docs.Search.App.Sidebar as Sidebar
8import Docs.Search.Extra (whenJust)
7import Docs.Search.Extra (whenJust)
8+import Docs.Search.ModuleIndex as ModuleIndex
9import Docs.Search.PackageIndex as PackageIndex
9import Docs.Search.PackageIndex as PackageIndex
10
10
11+import Prelude
11import Control.Coroutine as Coroutine
12import Control.Coroutine as Coroutine
12import Data.Maybe (Maybe(..))
13import Data.Maybe (Maybe(..))
13import Data.Newtype (wrap)
14import Data.Newtype (wrap)
@@ -17,6 +18,7 @@ import Halogen as H
17import Halogen.Aff as HA
18import Halogen.Aff as HA
18import Halogen.VDom.Driver (runUI)
19import Halogen.VDom.Driver (runUI)
19import MarkdownIt as MD
20import MarkdownIt as MD
21+import Web.DOM.ChildNode as ChildNode
20import Web.DOM.Document as Document
22import Web.DOM.Document as Document
21import Web.DOM.Element as Element
23import Web.DOM.Element as Element
22import Web.DOM.Node as Node
24import Web.DOM.Node as Node
@@ -24,10 +26,12 @@ import Web.DOM.ParentNode as ParentNode
24import Web.DOM.Text as Text
26import Web.DOM.Text as Text
25import Web.Event.EventTarget (addEventListener, eventListener)
27import Web.Event.EventTarget (addEventListener, eventListener)
26import Web.HTML as HTML
28import Web.HTML as HTML
29+import Web.HTML.Event.HashChangeEvent.EventTypes (hashchange)
27import Web.HTML.HTMLDocument as HTMLDocument
30import Web.HTML.HTMLDocument as HTMLDocument
28import Web.HTML.HTMLElement (fromElement)
31import Web.HTML.HTMLElement (fromElement)
29import Web.HTML.Window as Window
32import Web.HTML.Window as Window
30−import Web.HTML.Event.HashChangeEvent.EventTypes (hashchange)
33+import Web.HTML.Event.EventTypes (focus)
34+
31
35
32main :: Effect Unit
36main :: Effect Unit
33main = do
37main = do
@@ -40,11 +44,21 @@ main = do
40 -- Initialize a `markdown-it` instance (we need it to render the docs as markdown)
44 -- Initialize a `markdown-it` instance (we need it to render the docs as markdown)
41 markdownIt <- MD.newMarkdownIt MD.Default mempty
45 markdownIt <- MD.newMarkdownIt MD.Default mempty
42
46
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+
44 HA.runHalogenAff do
56 HA.runHalogenAff do
45 packageIndex <- PackageIndex.loadPackageIndex
57 packageIndex <- PackageIndex.loadPackageIndex
58+ moduleIndex <- ModuleIndex.loadModuleIndex
46
59
47 let initialSearchEngineState = { packageIndex: packageIndex
60 let initialSearchEngineState = { packageIndex: packageIndex
61+ , moduleIndex: moduleIndex
48 , index: mempty
62 , index: mempty
49 , typeIndex: mempty
63 , typeIndex: mempty
50 }
64 }
@@ -53,6 +67,9 @@ main = do
53 SearchResults.mkComponent initialSearchEngineState pageContents markdownIt
67 SearchResults.mkComponent initialSearchEngineState pageContents markdownIt
54
68
55 sfio <- runUI SearchField.component unit searchField
69 sfio <- runUI SearchField.component unit searchField
70+ sbio <- do
71+ component <- Sidebar.mkComponent moduleIndex
72+ runUI component unit sidebarContainer
56 srio <- runUI resultsComponent unit searchResults
73 srio <- runUI resultsComponent unit searchResults
57
74
58 sfio.subscribe $
75 sfio.subscribe $
@@ -72,6 +89,16 @@ main = do
72
89
73 addEventListener hashchange listener true (Window.toEventTarget win)
90 addEventListener hashchange listener true (Window.toEventTarget win)
74
91
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+
75
102
76insertStyle :: Document.Document -> Effect Unit
103insertStyle :: Document.Document -> Effect Unit
77insertStyle doc = do
104insertStyle doc = do
@@ -102,24 +129,49 @@ insertStyle doc = do
102 /* Add a margin between badge icons and package/module names. */
129 /* Add a margin between badge icons and package/module names. */
103 margin-right: 0.25em;
130 margin-right: 0.25em;
104 }
131 }
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+ }
105 """
154 """
106
155
107 mbHead <-
156 mbHead <-
108 ParentNode.querySelector (wrap "head") (Document.toParentNode doc)
157 ParentNode.querySelector (wrap "head") (Document.toParentNode doc)
109
158
110 whenJust mbHead \head -> do
159 whenJust mbHead \head -> do
111−
112 contents <- Document.createTextNode styleContents doc
160 contents <- Document.createTextNode styleContents doc
113 style <- Document.createElement "style" doc
161 style <- Document.createElement "style" doc
114 void $ Node.appendChild (Text.toNode contents) (Element.toNode style)
162 void $ Node.appendChild (Text.toNode contents) (Element.toNode style)
115 void $ Node.appendChild (Element.toNode style) (Element.toNode head)
163 void $ Node.appendChild (Element.toNode style) (Element.toNode head)
116
164
165+
117-- | Query the DOM for specific elements that should always be present.
166-- | Query the DOM for specific elements that should always be present.
118getContainers
167getContainers
119 :: Document.Document
168 :: Document.Document
120 -> Effect (Maybe { searchField :: HTML.HTMLElement
169 -> Effect (Maybe { searchField :: HTML.HTMLElement
121 , searchResults :: HTML.HTMLElement
170 , searchResults :: HTML.HTMLElement
122− , pageContents :: Element.Element })
171+ , pageContents :: Element.Element
172+ , sidebarContainer :: HTML.HTMLElement
173+ , realSidebar :: Element.Element
174+ })
123getContainers doc = do
175getContainers doc = do
124 let docPN = Document.toParentNode doc
176 let docPN = Document.toParentNode doc
125 mbBanner <-
177 mbBanner <-
@@ -128,13 +180,21 @@ getContainers doc = do
128 ParentNode.querySelector (wrap ".everything-except-footer") docPN
180 ParentNode.querySelector (wrap ".everything-except-footer") docPN
129 mbContainer <-
181 mbContainer <-
130 ParentNode.querySelector (wrap ".everything-except-footer > .container") docPN
182 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
131 case unit of
187 case unit of
132− _ | Just banner <- mbBanner
188+ _ | Just banner <- mbBanner
133− , Just everything <- mbEverything
189+ , Just everything <- mbEverything
134− , Just pageContents <- mbContainer -> do
190+ , Just pageContents <- mbContainer
191+ , Just mainContainer <- mbMainContainer
192+ , Just realSidebar <- mbRealSidebar -> do
135 search <- Document.createElement "div" doc
193 search <- Document.createElement "div" doc
136 void $ Node.appendChild (Element.toNode search) (Element.toNode banner)
194 void $ Node.appendChild (Element.toNode search) (Element.toNode banner)
195+ pure do
137− pure $ fromElement search >>= \searchField ->
196+ searchField <- fromElement search
138− fromElement everything >>= \searchResults ->
197+ searchResults <- fromElement everything
198+ sidebarContainer <- fromElement mainContainer
139− pure { searchField, searchResults, pageContents }
199+ pure { searchField, searchResults, pageContents, realSidebar, sidebarContainer }
140 | otherwise -> pure Nothing
200 | otherwise -> pure Nothing
src/Docs/Search/App/SearchResults.purs+1−1
@@ -168,7 +168,7 @@ hidePageContents
168hidePageContents = do
168hidePageContents = do
169 state <- H.get
169 state <- H.get
170 H.liftEffect do
170 H.liftEffect do
171− Element.setAttribute "style" "display: none" state.contents
171+ Element.setAttribute "style" "display: none;" state.contents
172
172
173
173
174render
174render
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 ::
7 { declIndexDirectory :: String
7 { declIndexDirectory :: String
8 , mkIndexPartLoadPath :: Int -> String
8 , mkIndexPartLoadPath :: Int -> String
9 , mkIndexPartPath :: Int -> String
9 , mkIndexPartPath :: Int -> String
10+ , moduleIndexPath :: String
11+ , moduleIndexLoadPath :: String
12+ , groupModulesItem :: String
10 , packageInfoPath :: String
13 , packageInfoPath :: String
11 , packageInfoLoadPath :: String
14 , packageInfoLoadPath :: String
12 , mkShapeScriptPath :: String -> String
15 , mkShapeScriptPath :: String -> String
@@ -43,9 +46,13 @@ config =
43 \(partId :: Int) -> "html/index/declarations/" <> show partId <> ".js"
46 \(partId :: Int) -> "html/index/declarations/" <> show partId <> ".js"
44 , mkIndexPartLoadPath:
47 , mkIndexPartLoadPath:
45 \(partId :: Int) -> "./index/declarations/" <> show partId <> ".js"
48 \(partId :: Int) -> "./index/declarations/" <> show partId <> ".js"
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.
46− , packageInfoPath: "generated-docs/html/index/packages.json"
54+ , packageInfoPath: "generated-docs/html/index/packages.js"
47− -- ^ Path to package index.
48− , packageInfoLoadPath: "./index/packages.json"
55+ , packageInfoLoadPath: "./index/packages.js"
49 -- ^ Used to load package index to the browser scope.
56 -- ^ Used to load package index to the browser scope.
50 , resultsCount: 25
57 , resultsCount: 25
51 -- ^ How many results to show by default?
58 -- ^ 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(.
4import Docs.Search.PackageIndex (Scores)
4import Docs.Search.PackageIndex (Scores)
5import Docs.Search.SearchResult (ResultInfo(..), SearchResult(..))
5import Docs.Search.SearchResult (ResultInfo(..), SearchResult(..))
6import Docs.Search.TypeDecoder (Constraint(..), QualifiedName(..), Type(..), Kind, joinForAlls)
6import Docs.Search.TypeDecoder (Constraint(..), QualifiedName(..), Type(..), Kind, joinForAlls)
7+import Docs.Search.Types (ModuleName, PackageName)
7
8
8import Prelude
9import Prelude
9−
10import Control.Alt ((<|>))
10import Control.Alt ((<|>))
11import Data.Array ((!!))
11import Data.Array ((!!))
12import Data.Array as Array
12import Data.Array as Array
13import Data.Foldable (foldr)
13import Data.Foldable (foldr)
14import Data.List (List, (:))
14import Data.List (List, (:))
15import Data.List as List
15import Data.List as List
16+import Data.Map as Map
16import Data.Maybe (Maybe(..), fromMaybe)
17import Data.Maybe (Maybe(..), fromMaybe)
17import Data.Newtype (class Newtype, unwrap, wrap)
18import Data.Newtype (class Newtype, unwrap, wrap)
18import Data.Search.Trie (Trie, alter)
19import Data.Search.Trie (Trie, alter)
@@ -20,11 +21,6 @@ import Data.String.CodeUnits (stripPrefix, stripSuffix, toCharArray)
20import Data.String.Common (split) as String
21import Data.String.Common (split) as String
21import Data.String.Common (toLower)
22import Data.String.Common (toLower)
22import Data.String.Pattern (Pattern(..))
23import Data.String.Pattern (Pattern(..))
23−import Data.Map as Map
24−
25−
26−type ModuleName = String
27−type PackageName = String
28
24
29
25
30newtype Declarations
26newtype Declarations
@@ -191,7 +187,7 @@ getLevelAndName DeclExternKind name = { name, declLevel: KindLevel }
191-- | Extract package name from `sourceSpan.name`, which contains path to
187-- | Extract package name from `sourceSpan.name`, which contains path to
192-- | the source file. If `ModuleName` string starts with `Prim.`, it's a
188-- | the source file. If `ModuleName` string starts with `Prim.`, it's a
193-- | built-in (guaranteed by the compiler).
189-- | built-in (guaranteed by the compiler).
194−extractPackageName :: ModuleName -> Maybe SourceSpan -> String
190+extractPackageName :: ModuleName -> Maybe SourceSpan -> PackageName
195extractPackageName moduleName _
191extractPackageName moduleName _
196 | String.split (Pattern ".") moduleName !! 0 == Just "Prim" = "<builtin>"
192 | String.split (Pattern ".") moduleName !! 0 == Just "Prim" = "<builtin>"
197extractPackageName _ Nothing = "<unknown>"
193extractPackageName _ Nothing = "<unknown>"
src/Docs/Search/Engine.purs+5−2
@@ -1,6 +1,7 @@
1module Docs.Search.Engine where
1module Docs.Search.Engine where
2
2
3import Docs.Search.PackageIndex (PackageIndex, PackageResult)
3import Docs.Search.PackageIndex (PackageIndex, PackageResult)
4+import Docs.Search.ModuleIndex (ModuleIndex)
4import Docs.Search.SearchResult (SearchResult, typeOfResult)
5import Docs.Search.SearchResult (SearchResult, typeOfResult)
5import Docs.Search.TypeQuery (TypeQuery(..), parseTypeQuery, penalty)
6import Docs.Search.TypeQuery (TypeQuery(..), parseTypeQuery, penalty)
6
7
@@ -36,6 +37,7 @@ type EngineState index typeIndex
36 = { index :: index
37 = { index :: index
37 , typeIndex :: typeIndex
38 , typeIndex :: typeIndex
38 , packageIndex :: PackageIndex
39 , packageIndex :: PackageIndex
40+ , moduleIndex :: ModuleIndex
39 }
41 }
40
42
41
43
@@ -44,9 +46,10 @@ mkEngineState
44 . index
46 . index
45 -> typeIndex
47 -> typeIndex
46 -> PackageIndex
48 -> PackageIndex
49+ -> ModuleIndex
47 -> EngineState index typeIndex
50 -> EngineState index typeIndex
48−mkEngineState index typeIndex packageIndex =
51+mkEngineState index typeIndex packageIndex moduleIndex =
49− { index, typeIndex, packageIndex }
52+ { index, typeIndex, packageIndex, moduleIndex }
50
53
51
54
52data Result
55data Result
src/Docs/Search/IndexBuilder.purs+13−3
@@ -1,16 +1,16 @@
1module Docs.Search.IndexBuilder where
1module Docs.Search.IndexBuilder where
2
2
3+import Docs.Search.BrowserEngine (getPartId)
3import Docs.Search.Config (config)
4import Docs.Search.Config (config)
4import Docs.Search.Declarations (Declarations(..), mkDeclarations)
5import Docs.Search.Declarations (Declarations(..), mkDeclarations)
5import Docs.Search.DocsJson (DocsJson)
6import Docs.Search.DocsJson (DocsJson)
6import Docs.Search.Extra ((>#>))
7import Docs.Search.Extra ((>#>))
7−import Docs.Search.BrowserEngine (getPartId)
8+import Docs.Search.ModuleIndex (ModuleIndex, mkModuleIndex)
8import Docs.Search.PackageIndex (PackageInfo, mkPackageInfo, mkScores)
9import Docs.Search.PackageIndex (PackageInfo, mkPackageInfo, mkScores)
9import Docs.Search.SearchResult (SearchResult)
10import Docs.Search.SearchResult (SearchResult)
10import Docs.Search.TypeIndex (TypeIndex, mkTypeIndex)
11import Docs.Search.TypeIndex (TypeIndex, mkTypeIndex)
11
12
12import Prelude
13import Prelude
13−
14import Data.Argonaut.Core (stringify)
14import Data.Argonaut.Core (stringify)
15import Data.Argonaut.Decode (decodeJson)
15import Data.Argonaut.Decode (decodeJson)
16import Data.Argonaut.Encode (encodeJson)
16import Data.Argonaut.Encode (encodeJson)
@@ -78,6 +78,7 @@ run' cfg = do
78 index = mkDeclarations scores docsJsons
78 index = mkDeclarations scores docsJsons
79 typeIndex = mkTypeIndex scores docsJsons
79 typeIndex = mkTypeIndex scores docsJsons
80 packageInfo = mkPackageInfo packageMetas
80 packageInfo = mkPackageInfo packageMetas
81+ moduleIndex = mkModuleIndex index
81
82
82 createDirectories cfg
83 createDirectories cfg
83
84
@@ -85,6 +86,7 @@ run' cfg = do
85 ignore <$> parallel (writeIndex cfg index)
86 ignore <$> parallel (writeIndex cfg index)
86 <*> parallel (writeTypeIndex cfg typeIndex)
87 <*> parallel (writeTypeIndex cfg typeIndex)
87 <*> parallel (writePackageInfo packageInfo)
88 <*> parallel (writePackageInfo packageInfo)
89+ <*> parallel (writeModuleIndex moduleIndex)
88 <*> parallel (if cfg.noPatch
90 <*> parallel (if cfg.noPatch
89 then pure unit
91 then pure unit
90 else patchDocs cfg)
92 else patchDocs cfg)
@@ -105,7 +107,7 @@ run' cfg = do
105 show countOfPackages <>
107 show countOfPackages <>
106 " packages to the search index."
108 " packages to the search index."
107
109
108− where ignore _ _ _ _ _ = unit
110+ where ignore _ _ _ _ _ _ _ = unit
109
111
110
112
111-- | Exit early if something is missing.
113-- | Exit early if something is missing.
@@ -220,6 +222,14 @@ writePackageInfo packageInfo = do
220 where
222 where
221 header = "window.DocsSearchPackageIndex = "
223 header = "window.DocsSearchPackageIndex = "
222
224
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+
223-- | Get a mapping from index parts to index contents.
233-- | Get a mapping from index parts to index contents.
224getIndex :: Declarations -> Map Int (Array (Tuple String (Array SearchResult)))
234getIndex :: Declarations -> Map Int (Array (Tuple String (Array SearchResult)))
225getIndex (Declarations trie) =
235getIndex (Declarations trie) =
src/Docs/Search/Interactive.purs+3−1
@@ -2,6 +2,7 @@
2module Docs.Search.Interactive where
2module Docs.Search.Interactive where
3
3
4import Docs.Search.Declarations (Declarations, mkDeclarations)
4import Docs.Search.Declarations (Declarations, mkDeclarations)
5+import Docs.Search.ModuleIndex (mkModuleIndex)
5import Docs.Search.DocsJson (DataDeclType(..))
6import Docs.Search.DocsJson (DataDeclType(..))
6import Docs.Search.Engine (mkEngineState, Result(..))
7import Docs.Search.Engine (mkEngineState, Result(..))
7import Docs.Search.Engine as Engine
8import Docs.Search.Engine as Engine
@@ -51,7 +52,8 @@ run cfg = launchAff_ $ do
51 index = mkDeclarations scores docsJsons
52 index = mkDeclarations scores docsJsons
52 typeIndex = docsJsons >>= resultsWithTypes scores
53 typeIndex = docsJsons >>= resultsWithTypes scores
53 packageIndex = mkPackageIndex $ mkPackageInfo packageMetas
54 packageIndex = mkPackageIndex $ mkPackageInfo packageMetas
55+ moduleIndex = mkModuleIndex index
54− engineState = mkEngineState (unwrap index) typeIndex packageIndex
56+ engineState = mkEngineState (unwrap index) typeIndex packageIndex moduleIndex
55
57
56 let countOfDefinitions = Trie.size $ unwrap index
58 let countOfDefinitions = Trie.size $ unwrap index
57 countOfTypeDefinitions = Array.length typeIndex
59 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) {
9 script.src = url;
9 script.src = url;
10 script.addEventListener('load', function () {
10 script.addEventListener('load', function () {
11 if (typeof window.DocsSearchPackageIndex === 'undefined') {
11 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."));
13 } else {
13 } else {
14 resolve(window.DocsSearchPackageIndex);
14 resolve(window.DocsSearchPackageIndex);
15 }
15 }
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