7 module Rakka.Resource.Search
11 import Control.Applicative
13 import Control.Arrow.ArrowIf
14 import Control.Arrow.ArrowIO
15 import Control.Arrow.ArrowList
16 import Control.Arrow.ArrowTree
17 import Control.Arrow.Unicode
18 import qualified Codec.Binary.UTF8.Generic as UTF8
19 import Control.Monad.Trans
21 import Data.Monoid.Unicode
22 import Data.Text (Text)
23 import qualified Data.Text as T
25 import qualified Data.Time.RFC1123 as RFC1123
26 import Network.HTTP.Lucu
27 import Network.URI hiding (query, fragment)
28 import Prelude.Unicode
29 import Rakka.Environment
33 import Rakka.SystemConfig
35 import Rakka.W3CDateTime
36 import Rakka.Wiki.Engine
37 import System.FilePath
38 import Text.HyperEstraier hiding (getText)
39 import Text.XML.HXT.Arrow.Namespace
40 import Text.XML.HXT.Arrow.XmlArrow
41 import Text.XML.HXT.DOM.TypeDefs
42 import Text.XML.HXT.XPath
44 resSearch :: Environment -> ResourceDef
47 resUsesNativeThread = False
49 , resGet = Just $ handleSearch env
57 resultsPerSection :: Int
58 resultsPerSection = 10
61 maxSectionWindowSize :: Int
62 maxSectionWindowSize = 10
64 findQueryParam ∷ String → [(String, FormData)] → Maybe String
65 findQueryParam name qps
66 = UTF8.toString ∘ fdContent <$> lookup name qps
69 <searchResult query="foo bar baz"
74 <page name="Page/1" lastModified="2000-01-01T00:00:00">
75 aaa <hit>foo</hit> bbb
81 handleSearch ∷ Environment → Resource ()
83 = do params ← getQueryForm
85 let query = fromMaybe "" $ findQueryParam "q" params
86 order = findQueryParam "order" params
88 $ fmap read $ findQueryParam "from" params
89 to = fromMaybe (from + resultsPerSection)
90 $ fmap read $ findQueryParam "to" params
92 cond ← liftIO $ mkCond (T.pack query) (T.pack <$> order) from to
93 result ← searchPages (envStorage env) cond
95 let to' = min (from + length (srPages result)) to
97 BaseURI baseURI ← getSysConf (envSysConf env)
98 runIdempotentA baseURI $ proc ()
99 → do tree ← ( eelem "/"
100 += ( eelem "searchResult"
101 += sattr "query" query
103 Just o → sattr "order" o
106 += sattr "from" (show from)
107 += sattr "to" (show to')
108 += sattr "total" (show $ srTotal result)
109 += ( constL (srPages result)
115 returnA ⤙ outputXmlPage' tree (searchResultToXHTML env)
117 mkCond ∷ Text → Maybe Text → Int → Int → IO Condition
118 mkCond query order from to
119 = do cond ← newCondition
122 Just o → setOrder cond o
125 setMax cond (to - from + 1)
128 mkPageElem ∷ (ArrowChoice (⇝), ArrowXml (⇝), ArrowIO (⇝)) ⇒ HitPage ⇝ XmlTree
129 mkPageElem = ( eelem "page"
130 += attr "name" (arr (T.unpack ∘ hpPageName) ⋙ mkText)
131 += attr "lastModified" ( arrIO (utcToLocalZonedTime ∘ hpLastMod)
133 arr formatW3CDateTime
143 mkSnippetTree ∷ (ArrowChoice (⇝), ArrowXml (⇝)) ⇒ SnippetFragment ⇝ XmlTree
144 mkSnippetTree = proc fragment
146 Boundary → eelem "boundary" ⤙ ()
147 NormalText t → mkText ⤙ T.unpack t
148 HighlightedWord w → ( eelem "hit"
152 searchResultToXHTML ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
155 searchResultToXHTML env
157 → do SiteName siteName ← getSysConfA (envSysConf env) ⤙ ()
158 BaseURI baseURI ← getSysConfA (envSysConf env) ⤙ ()
159 StyleSheet styleSheet ← getSysConfA (envSysConf env) ⤙ ()
160 GlobalLock isGLocked ← getSysConfA (envSysConf env) ⤙ ()
162 let cssHref = [uriToString id (mkObjectURI baseURI styleSheet) ""]
163 scriptSrc = [uriToString id (baseURI { uriPath = uriPath baseURI </> "js" }) ""]
165 pageTitle ← listA (readSubPage env) ⤙ "PageTitle"
166 leftSideBar ← listA (readSubPage env) ⤙ "SideBar/Left"
167 rightSideBar ← listA (readSubPage env) ⤙ "SideBar/Right"
171 += sattr "xmlns" "http://www.w3.org/1999/xhtml"
174 += txt (T.unpack siteName)
176 += getXPathTreesInDoc "/searchResult/@query/text()"
181 += sattr "rel" "stylesheet"
182 += sattr "type" "text/css"
183 += attr "href" (arr id ⋙ mkText)
185 += ( constL scriptSrc
188 += sattr "type" "text/javascript"
189 += attr "src" (arr id ⋙ mkText)
192 += sattr "type" "text/javascript"
193 += txt ("Rakka.baseURI=\"" ⊕ uriToString id baseURI "" ⊕ "\";")
194 += txt ("Rakka.isGlobalLocked=" ⊕ trueOrFalse isGLocked ⊕ ";" )
195 += txt "Rakka.isSpecialPage=true;" ) )
198 += sattr "class" "header"
201 += sattr "class" "center"
203 += sattr "class" "title"
207 += sattr "class" "body"
209 += txt "Search Result"
212 += sattr "class" "searchStat"
213 += txt "Search result for "
215 += sattr "class" "queryString"
216 += getXPathTreesInDoc "/searchResult/@query/text()"
219 += getXPathTreesInDoc "/searchResult/@total/text()"
222 += ( getXPathTreesInDoc "/searchResult/page"
226 += ( ( ( getXPathTreesInDoc "/searchResult/@query/text()"
231 maybeA ( getXPathTreesInDoc "/searchResult/@order/text()"
236 ( getXPathTreesInDoc "/searchResult/@from/text()"
240 arr ((`div` resultsPerSection) . read)
243 ( getXPathTreesInDoc "/searchResult/@total/text()"
247 arr ((+ 1) . (`div` resultsPerSection) . (\x → x - 1) . read) ) )
249 ( ((> 1) . snd . snd . snd)
251 formatPager baseURI ) ) ) )
253 += sattr "class" "footer"
256 += sattr "class" "left sideBar"
258 += sattr "class" "content"
259 += constL leftSideBar
263 += sattr "class" "right sideBar"
265 += sattr "class" "content"
266 += constL rightSideBar
271 uniqueNamespacesFromDeclAndQNames
274 formatItem ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
279 += sattr "class" "searchResult"
281 += attr "href" ( getAttrValue "name"
283 arr (\x → uriToString id (mkPageURI baseURI (T.pack x)) "")
287 += (getAttrValue "name" ⋙ mkText)
290 += sattr "class" "date"
291 += ( getAttrValue "lastModified"
293 arr (zonedTimeToUTC . fromJust . parseW3CDateTime)
295 arrIO utcToLocalZonedTime
305 choiceA [ isText :-> this
306 , hasName "boundary" :-> txt " ... "
307 , hasName "hit" :-> ( eelem "span"
308 += sattr "class" "highlighted"
316 formatPager :: (ArrowChoice a, ArrowXml a) => URI -> a (String, (Maybe String, (Int, Int))) XmlTree
319 += sattr "class" "pager"
325 arr (fst . snd . snd)
333 proc (query, (order, (currentSection, section)))
334 -> if currentSection == section then
338 += sattr "class" "currentSection"
339 += (arr show ⋙ mkText)
345 += attr "href" ( mkSectionURI baseURI
349 += (arr (show . snd . snd) ⋙ mkText)
350 ) ⤙ (query, (order, section))
354 mkSectionWindow :: ArrowList a => a (Int, Int) Int
356 = proc (currentSection, totalSections)
357 -> let windowWidth = min maxSectionWindowSize totalSections
358 windowBegin = currentSection - (windowWidth `div` 2)
359 (begin, end) = if windowBegin < 0 then
363 if windowBegin + windowWidth >= totalSections then
365 (totalSections - windowWidth, totalSections - 1)
368 (windowBegin, windowBegin + windowWidth - 1)
370 arrL id ⤙ [begin .. end]
373 mkSectionURI :: Arrow a => URI -> a (String, (Maybe String, Int)) URI
375 = arr $ \ (query, (order, section))
377 uriPath = uriPath baseURI </> "search.html"
378 , uriQuery = '?' : mkQueryString ( [ ("q" , query)
379 , ("from", show $ section * resultsPerSection)
380 , ("to" , show $ (section + 1) * resultsPerSection - 1)
384 Just o -> [("order", o)]
389 uriToText :: ArrowXml a => a URI XmlTree
390 uriToText = arr (\ uri -> uriToString id uri "") ⋙ mkText
394 readSubPage :: (ArrowXml a, ArrowChoice a, ArrowIO a) =>
395 Environment -> a PageName XmlTree
397 = proc (subPageName) ->
398 do subPage ← getPageA (envStorage env) ⋙ arr fromJust ⋙ xmlizePage ⤙ (subPageName, Nothing)
399 subXHTML ← makeSubXHTML (envStorage env) (envSysConf env) (envInterpTable env) ⤙ (Nothing, Nothing, subPage)