packages feed

panda 0.0.0.2 → 0.0.0.3

raw patch · 28 files changed

+478/−272 lines, 28 filesdep +HaXmldep +haskell98

Dependencies added: HaXml, haskell98

Files

LICENSE view
@@ -1,20 +1,13 @@-Copyright (c) 2008 Wang, Jinjing--Permission is hereby granted, free of charge, to any person obtaining a copy-of this software and associated documentation files (the "Software"), to deal-in the Software without restriction, including without limitation the rights-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell-copies of the Software, and to permit persons to whom the Software is-furnished to do so, subject to the following conditions:--The above copyright notice and this permission notice shall be included in-all copies or substantial portions of the Software.+(c) 2008 Wang, Jinjing -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENMPS. IN NO EVENT SHALL THE-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN-THE SOFTWARE.+This work ‘as-is’ we provide.+No warranty, express or implied.+We’ve done our best,+to debug and test.+Liability for damages denied. +Permission is granted hereby,+to copy, share, and modify.+Use as is fit,+free or for profit.+On this notice these rights rely.
Panda/Config/Global.hs view
@@ -2,7 +2,6 @@  import Panda.Helper.Env import Prelude hiding ((.), (/), id)-import MPS  db_id      = "db" flat_id    = "."@@ -32,3 +31,5 @@ author_email  = config_for "author_email"  panda_url     = "http://github.com/nfjinjing/panda"++per_page      = 7 :: Int
Panda/Controller/Application.hs view
@@ -1,29 +1,27 @@ {-# OPTIONS -fno-monomorphism-restriction #-} -module Panda.Controller.Application (pages) where-+module Panda.Controller.Application where  import qualified Data.Map as Map  -- env import Panda.Helper.Env import Prelude hiding ((.), (/), id)-import Kibro (kibro)          -import Kibro.DB.Sqlite3       import Panda.Type.State hiding (pager) import qualified Panda.Config.Global as Config import qualified Panda.Type.Pager as Pager  -- model-import Panda.Model.Post as Post-import Panda.Model.Tag as Tag+import qualified Panda.Model.Post as Post+import qualified Panda.Model.Tag as Tag  -- view import qualified Panda.View.Theme.BluePrint.Post as PostV-import qualified Panda.View.Theme.BluePrint.Template as T+import qualified Panda.View.Theme.BluePrint.Template.Template as T import qualified Panda.View.Theme.BluePrint.Static as StaticV import qualified Panda.View.Theme.BluePrint.Tag as TagV-import qualified Panda.View.RSS as RSS+import qualified Panda.View.Theme.BluePrint.Search as SearchV+import qualified Panda.View.RSS as RSSV   @@ -32,26 +30,30 @@ pages =    [ ("^/$"                  ,index             )   , ("^/([?].+$)?$"         ,index             )-  , ("^/rss.xml$"           ,blog_feed         )+  , ("^/rss.xml$"           ,index_feed        )   , (blog_regex             ,blog              )   , ("^/static/."           ,static            )+  , ("^/tag/.*/rss.xml$"    ,tag_feed          )   , ("^/tag/."              ,tag               )-  , ("^/tag/.*([?].+$)?$"   ,index             )+  , ("^/search"             ,search            )   ] +per_page = Config.per_page+ index = do   blogs <- Post.list.liftIO-  p <- pager 10 $ blogs.length+  p <- pager per_page $ blogs.length   tags <- Tag.list.liftIO   let nav = if p.Pager.current == 1 then Home else Other   let state = State Config.blog_id p tags nav      blogs.map (Tag.fill_tag tags) .PostV.list state .output_html -blog_feed = do+index_feed = do   blogs <- Post.list.liftIO-  blogs.RSS.rss.output+  blogs.RSSV.rss.output + blog = do   id <- uri <.> ("blog/" ++)   blog <- Post.get id .liftIO@@ -71,11 +73,32 @@ tag = do   id <- uri   tags <- Tag.list.liftIO-  let tag_name = Tag.name_from_id id+  let tag_name = Tag.id_to_name id   case Tag.tag_map' tags .Map.lookup tag_name of     Nothing -> "page not found" .output     Just blog_set -> do       blogs <- blog_set.to_list.rsort.mapM (Post.get) .liftIO <.> map (Tag.fill_tag tags)-      p <- pager 10 $ blogs.length+      p <- pager per_page $ blogs.length       let state = State id p tags Other       blogs.TagV.view state.output_html++tag_feed = do+  id <- uri <.> (split "/" >>> init >>> join "/")+  tags <- Tag.list.liftIO+  let tag_name = Tag.id_to_name id+  case Tag.tag_map' tags .Map.lookup tag_name of+    Nothing -> "page not found" .output+    Just blog_set -> do+      blogs <- blog_set.to_list.rsort.mapM (Post.get) .liftIO <.> map (Tag.fill_tag tags)+      blogs.RSSV.rss.output++search = do+  s <- param_with_default "s" ""+  blogs <- Post.search s .liftIO++  p <- pager per_page $ blogs.length+  tags <- Tag.list.liftIO+  query <- uri+  let state = State query p tags Other++  blogs.map (Tag.fill_tag tags) .SearchV.view state s.output_html
Panda/Helper/Env.hs view
@@ -1,6 +1,5 @@ module Panda.Helper.Env  (     module MPS                 -  , module System.Directory       , module Control.Monad          , module Control.Arrow          , module Data.List           @@ -14,16 +13,12 @@ ) where    import MPS-import System.Directory-import Kibro hiding (tag)-import Kibro.DB.Sqlite3 import Control.Monad hiding (join) import Control.Arrow ((>>>)) import Data.List import Network.URI import Network.CGI import Text.Pandoc.UTF8-import Text.XHtml.Strict import System.Locale import System.Time.Parse import Panda.Helper.Helper
+ Panda/Helper/Escape.hs view
@@ -0,0 +1,22 @@+module Panda.Helper.Escape where+++import Text.XML.HaXml hiding ((!))+import Text.XML.HaXml.Parse (xmlParseWith, document)+import Text.XML.HaXml.Lex (xmlLex)++++unescape :: String -> String+unescape = concatMap ctext . xmlUnEscapeContent stdXmlEscaper .+              unwrapTag .+              either error id . fst . xmlParseWith document .+              xmlLex "oops, lexer failed" . wrapWithTag "t"+  where+    ctext (CString _ txt _)         = txt+    ctext (CRef (RefEntity name) _) = '&' : name ++ ";" -- skipped by escaper+    ctext (CRef (RefChar num) _)    = '&' : '#' : show num ++ ";" -- ditto+    ctext _                 = error "oops, can't unescape non-cdata"+    wrapWithTag t s = concat ["<", t, ">", s, "</", t, ">"]+    unwrapTag (Document _ _ (Elem _ _ c) _) = c+    unwrapTag _                             = error "oops, not wrapped"
Panda/Helper/Helper.hs view
@@ -8,35 +8,43 @@ import Control.Arrow ((>>>), (&&&)) import Text.XHtml.Strict import Control.Monad hiding (join)+import Data.Maybe -import Panda.Type.Pager as Pager+import Panda.Type.Pager as Pager hiding (empty) import MPS import Prelude hiding ((.), (/), id)+import Panda.Helper.Escape+import Char+import Data.List+import System.Directory  (/) = (</>)  + -- controller-uri = do-  uri <- requestURI-  uri.uriPath.urlDecode.tail.return+uri = requestURI <.> (uriPath >>> urlDecode >>> tail)   -markdown_link l s = ["[", s, "]", "(", l, ")"] .join'- params = do   query_string <- requestURI <.> uriQuery   case query_string of-    '?':s -> s.split "&" .map (split "=" >>> tuple2) .return+    '?':s -> s.formDecode .return     otherwise -> return []  param_with_default s d = do   ps <- params   return $ case ps.lookup s of     Nothing -> d-    Just x -> x.read+    Just x -> x.unescape.strip +input_with_default s d = do+  input <- getInput s+  return $ case input of+    Nothing -> d+    Just x -> x.unescape.strip+ pager length total = liftM5 ( Pager length ) current has_next has_previous next previous where-  current      = param_with_default "page" 1+  current      = param_with_default "page" "1" <.> read   has_next     = current <.> ( (* length) >>> (< total.fromIntegral) )   has_previous = current <.> (> 1)   next         = current <.> (+ 1)@@ -46,27 +54,21 @@ for_current_page p xs = xs.drop ((p.Pager.current - 1) * p.Pager.length) .take (p.Pager.length)  -- -- view-id           = identifier+id               = identifier -css_link l   = itag "link" ! [rel "stylesheet", thetype "text/css", href l]-js_link l    = itag "script" ! [thetype "text/javascript", src l]-svg s a w h  = image ! [src ("/svg/" ++ s), height (h.show), width (w.show), alt a]+css_link l       = itag "link" ! [rel "stylesheet", thetype "text/css", href l]+js_link l        = itag "script" ! [thetype "text/javascript", src l]+rss_link l       = itag "link" ! [rel "alternate", thetype "application/rss+xml", href l, title "RSS 2.0"]+meta_tag         = meta ! [httpequiv "Content-Type", content "text/html; charset=utf-8"] -meta_tag     = meta ! [httpequiv "Content-Type", content "text/html; charset=utf-8"] -div_id s     = thediv ! [id s]-div_class s  = thediv ! [theclass s]+div_id s         = thediv ! [id s]+div_class s      = thediv ! [theclass s] div_class_id x y = thediv ! [theclass x, id y]-space        = hr ! [theclass "space"] -sep_by _ []  = toHtml ""-sep_by _ [x] = toHtmlFromList [x]-sep_by x xs  = ( init >>> map (+++ x) ) &&& last >>> splash (+++) $ xs+output_html      = output ... renderHtml -output_html  = output ... renderHtml  ie s =    [ "<!-- [if IE]>"@@ -81,3 +83,13 @@   uriQuery = "",   uriFragment = ""   }++++-- generic+lower = map toLower+upper = map toUpper++ls s = getDirectoryContents s <.> (\\ [".", ".."])++filter_comment = lines >>> map strip >>> reject null >>> reject (head >>> (== '#')) >>> join "\n"
Panda/Model/Post.hs view
@@ -3,7 +3,7 @@ module Panda.Model.Post where  -- env-import Panda.Helper.Env+import Panda.Helper.Env hiding (match) import Prelude hiding ((.), (/), id) import qualified Panda.Config.Global as Config @@ -15,9 +15,7 @@   }   deriving (Show, Eq) -list = do-  ids <- getDirectoryContents Config.blog_uri <.> (\\ [".", ".."]) <.> rsort <.> map ("" / Config.blog_id /)-  mapM get ids+list = ls Config.blog_uri <.> rsort <.> map (Config.blog_id /) >>= mapM get  get id       = liftM2 (Post id (get_title id) ) (get_body id) (return []) get_title id = id.fromUTF8.words.tail.join " "@@ -28,3 +26,6 @@   Nothing -> parse_date "2000-1-1".fromJust   Just x -> x +match s x = [title, body] .map (send_to x >>> lower >>> isInfixOf (s.lower)) .or+search "" = return []+search s  = list <.> filter (match s)
Panda/Model/Tag.hs view
@@ -19,40 +19,21 @@   }   deriving (Show, Eq) -name x = x.uid.name_from_id-name_from_id x = x.split "/" .tail.join'-sorted xs = xs.List.sortBy(\a b -> (b.resources.Set.size) `compare` (a.resources.Set.size))-id_from_name x = tag_id / x--list = do-  ids <- getDirectoryContents Config.tag_uri <.> (\\ [".", ".."]) <.> map ("" / Config.tag_id /)-  mapM get ids--get id       = get_resources id <.> Tag id-get_resources id  = (Config.flat_uri / id) .readFile -  <.> lines <.> map strip <.> reject empty <.> reject (head >>> (== '#')) <.> map (blog_id / ) <.> to_set+name x       = x.uid.id_to_name+id_to_name x = x.split "/" .tail.join'+sorted xs    = xs.List.sortBy(\a b -> (b.resources.Set.size) `compare` (a.resources.Set.size))+name_to_id x = tag_id / x +list = ls Config.tag_uri <.> map (Config.tag_id /) >>= mapM get +get id = get_resources id <.> Tag id+get_resources id = (Config.flat_uri / id) .readFile +  <.> filter_comment <.> lines <.> map (blog_id / ) <.> to_set  --- many to many relationship is modeled using 2 maps--- one from tags, and the other from resources--to_tuple x = (x.name, x.resources)--tag_map' xs = xs . map (to_tuple) . to_h-tag_map = list <.> tag_map'--tag_reverse_map' xs = res.labeling (for_resource xs) where-  res = xs.map resources .reduce Set.union .to_list--tag_reverse_map = list <.> tag_reverse_map'--uncategorized = []-for_resource xs x = case tags of -    [] -> uncategorized-    otherwise -> tags-  where-    tags = xs.select (resources >>> Set.member x) .map name+to_tuple x        = (x.name, x.resources)+tag_map' xs       = xs . map (to_tuple) . to_h+tag_map           = list <.> tag_map' -fill_tag xs x = x { Post.tags = for_resource xs (x.Post.uid) }+for_resource xs x = xs.select (resources >>> Set.member x) .map name+fill_tag xs x     = x { Post.tags = for_resource xs (x.Post.uid) }
Panda/Type/State.hs view
@@ -13,5 +13,4 @@   , nav_location :: NavLocation   }   -data NavLocation = Home | About | Other-  deriving (Eq, Show)+data NavLocation = Home | About | Other deriving (Eq, Show)
Panda/View/RSS.hs view
@@ -14,9 +14,7 @@ entry_uri x = "/" ++ Post.uid x.urlEncode.split "/".tail.join "/"  -- RSS-channel_rss_template = -  [ Language "en-us"-  ]+channel_rss_template = [ Language "en-us" ]  item_rss_template x =    [ Title $ x.Post.title
− Panda/View/Theme/BluePrint/Body.hs
@@ -1,60 +0,0 @@-module Panda.View.Theme.BluePrint.Body where---- env-import Panda.Helper.Env-import Prelude hiding ((.), (/), id, span)-import Panda.Config.Global as Config-import qualified Panda.Type.State as State-import Text.XHtml.Strict---- view-import Panda.View.Theme.BluePrint.Sidebar-import Panda.View.Theme.BluePrint.Footer---html_body state x = body << container << -  [ body_header state-  , div_id "page" << [ body_content x, html_sidebar state ]-  , html_footer-  ]--container   = div_class "container"--body_header state = toHtml-  [ div_class_id "column span-12 first" "header" << -    [ search_form-    , site_name-    ]-  , div_class_id "column span-12 first large" "nav" << navigation state-  ]--search_form = spaceHtml-search_form' = -  div_id "search" << gui "/search" << thediv << -    textfield "s" ! [strAttr "onFocus" "clearInput('s', 'Search')", strAttr "onBlur" "clearInput('s', 'Search')"]-  -site_name = toHtml $-  [ toHtml $ hotlink "/" ! [theclass "logo"] << ""-  , h1 << Config.blog_title-  , div_class "description" << Config.blog_subtitle-  ]--navigation state = div_class "content" <<-  ulist << -  [ li ! [theclass ("first" ++ current_home)] << home_link-  , li ! [theclass ("page_item page-item-2" ++ current_about)] << about-  ]-  where-    nav = state.State.nav_location-    current = " current_page_item"-    current_home = at State.Home-    current_about = at State.About-    at x = if nav == x then current else ""--home_link   = hotlink "/" << "Home"-about       = hotlink "/static/about" << "About"---body_content x = div_class_id "column span-9 first" "maincontent" <<-  div_class "content" << x-
− Panda/View/Theme/BluePrint/Footer.hs
@@ -1,21 +0,0 @@-module Panda.View.Theme.BluePrint.Footer where-  -import Panda.Helper.Env-import Prelude hiding ((.), (/), id, span)-import Panda.Config.Global as Config-import qualified Panda.Type.State as State-import Text.XHtml.Strict---import Panda.Config.Global as Config--html_footer = div_id "footer" <<-  [ toHtml $ copyright-  , toHtml $ "2008 "-  , toHtml $ Config.blog_title-  , toHtml $ br-  , toHtml $ "Powered by "-  , toHtml $ hotlink Config.panda_url << "Panda"-  , toHtml $ " using "-  , toHtml $ hotlink "http://www.haskell.org/" << "Haskell"-  ]
− Panda/View/Theme/BluePrint/Header.hs
@@ -1,16 +0,0 @@-module Panda.View.Theme.BluePrint.Header where--import Panda.Helper.Env-import Prelude hiding ((.), (/), id, span)-import Panda.Config.Global as Config-import qualified Panda.Type.State as State-import Text.XHtml.Strict---html_head  = header << [meta_tag, title_tag, screen_css, wp_css] -- , ie_css, custom_css]--title_tag  = thetitle << Config.blog_title-screen_css = css_link "/theme/blueprint/wp/css/screen.css"-wp_css     = css_link "/theme/blueprint/wp/css/blueprint-wp.css"--- ie_css  = ie $ css_link "theme/blueprint/core/ie.css"-custom_css = css_link "/theme/blueprint/wp/css/custom.css"
Panda/View/Theme/BluePrint/Helper.hs view
@@ -1,34 +1,32 @@ module Panda.View.Theme.BluePrint.Helper where -import Kibro -import MPS-import Prelude hiding ((.), (/), id)-import Control.Arrow ((>>>)) -import Panda.Helper.Helper-import Panda.View.Theme.BluePrint.Template-import Panda.Model.Post as Post+import Panda.Helper.Env+import Prelude hiding ((.), (/), id, span) import Panda.Config.Global as Config-import Panda.Type.Pager as Pager-import Network.URI-import Text.RSS-import System.Time-import System.Locale import qualified Panda.Type.State as State+import Text.XHtml.Strict+  +import Panda.View.Theme.BluePrint.Template.Template+import Panda.Model.Post as Post+import Panda.Type.Pager as Pager + nav p r =    [ div_class "alighleft" << nav_next p r   , div_class "alignright" << nav_previous p r   ]    nav_previous p r = if p.Pager.has_previous -  then toHtml $ hotlink ( r ++ "?page=" ++ p.Pager.previous.show ) << previous_sign+  then toHtml $ hotlink ( r' ++ "page=" ++ p.Pager.previous.show ) << previous_sign   else spaceHtml-+  where r' = if isSuffixOf "&" r then r else r ++ "?"+     nav_next p r = if p.Pager.has_next-  then toHtml $ hotlink ( r ++ "?page=" ++ p.Pager.next.show ) << next_sign+  then toHtml $ hotlink ( r' ++ "page=" ++ p.Pager.next.show ) << next_sign   else spaceHtml+  where r' = if isSuffixOf "&" r then r else r ++ "?"  next_sign     = "Next Entries »" previous_sign = "« Previous Entries"
Panda/View/Theme/BluePrint/Post.hs view
@@ -17,13 +17,13 @@  -- view import Panda.View.Theme.BluePrint.Helper-import Panda.View.Theme.BluePrint.Template+import Panda.View.Theme.BluePrint.Template.Template  -- HTML -- render single entry entry x = div_class "post" << [entry_title, entry_mark, entry_body] where   entry_title = x.title_link-  entry_body  = p ! [theclass "entry"] << x.Post.body.markup+  entry_body  = div_class "entry" << x.Post.body.markup   entry_mark  = p ! [theclass "small"] <<      [ x.blog_date, x.blog_tags ] @@ -34,7 +34,8 @@ blog_tags x | x.tags.null = toHtml "" blog_tags x | otherwise   = toHtml " | " +++ toHtml ["Published in ".toHtml, x.tags.map tag_link .intersperse (", ".toHtml) .toHtml] -tag_link s = toHtml $ hotlink ("/tag/" ++ s ) << s+tag_link s                = toHtml $ hotlink ("/tag/" ++ s ) << s+  -- entry view view state x = x.entry.page state
+ Panda/View/Theme/BluePrint/Search.hs view
@@ -0,0 +1,16 @@+module Panda.View.Theme.BluePrint.Search where+  +import Panda.Helper.Env+import Prelude hiding ((.), (/), id, span)+import Panda.Config.Global as Config+import qualified Panda.Type.State as State+import Text.XHtml.Strict++-- view+import Panda.View.Theme.BluePrint.Post hiding (view)+import Panda.View.Theme.BluePrint.Helper+import Panda.View.Theme.BluePrint.Template.Template++view state s = for_current_page p >>> map entry >>> (+++ nav p ("/search" ++ "?s=" ++ (s.urlEncode) ++ "&")) >>> page state+  where p = state.State.pager+
− Panda/View/Theme/BluePrint/Sidebar.hs
@@ -1,34 +0,0 @@-module Panda.View.Theme.BluePrint.Sidebar where--import Panda.Helper.Env-import Prelude hiding ((.), (/), id, span)-import Panda.Config.Global as Config-import qualified Panda.Type.State as State-import Text.XHtml.Strict--import qualified Data.Set as Set---- model-import Panda.Model.Tag as Tag--html_sidebar state = -  div_class_id "column span-3 last" "sidebar" << unordList-    [ feed-    , [hr]-    , tag_list $ state.State.tags-    ]--feed = -  [ h2 << "Keep Update"-  , p ! [theclass "small"] << rss_link-  ]--rss_link = hotlink "/rss.xml" << -  [ -- image ! [src "/image/feed.png", alt "feed link", height "15", width "15"]-    "Subscribe to Feed" ]-    -tag_list tags = -  [ h2 << "Tags"-  , unordList $ tags.Tag.sorted.map tag_link]-  -tag_link x = hotlink ("/" ++ x.uid) << x.Tag.name +++ (" (" ++ x.Tag.resources.Set.size.show ++ ")")
Panda/View/Theme/BluePrint/Static.hs view
@@ -6,6 +6,6 @@ import qualified Panda.Type.State as State import Text.XHtml.Strict -import Panda.View.Theme.BluePrint.Template+import Panda.View.Theme.BluePrint.Template.Template  view tags x = x.markup.page tags
Panda/View/Theme/BluePrint/Tag.hs view
@@ -12,7 +12,7 @@ -- view import Panda.View.Theme.BluePrint.Post hiding (view) import Panda.View.Theme.BluePrint.Helper-import Panda.View.Theme.BluePrint.Template+import Panda.View.Theme.BluePrint.Template.Template  view state = for_current_page p >>> map entry >>> (+++ nav p ("/" ++ tag_id)) >>> page state   where p = state.State.pager
− Panda/View/Theme/BluePrint/Template.hs
@@ -1,11 +0,0 @@-module Panda.View.Theme.BluePrint.Template where--import Panda.View.Theme.BluePrint.Header-import Panda.View.Theme.BluePrint.Body--import Panda.Helper.Env--page state x = -  [ html_head-  , html_body state x-  ]
+ Panda/View/Theme/BluePrint/Template/Body.hs view
@@ -0,0 +1,61 @@+module Panda.View.Theme.BluePrint.Template.Body where++-- env+import Panda.Helper.Env+import Prelude hiding ((.), (/), id, span)+import Panda.Config.Global as Config+import qualified Panda.Type.State as State+import Text.XHtml.Strict++-- view+import Panda.View.Theme.BluePrint.Template.Sidebar+import Panda.View.Theme.BluePrint.Template.Footer+++html_body state x = body << container << +  [ body_header state+  , div_id "page" << [ body_content x, html_sidebar state ]+  , html_footer+  ]++container   = div_class "container"++body_header state = toHtml+  [ div_class_id "column span-12 first" "header" << +    [ search_form+    , site_name+    ]+  , div_class_id "column span-12 first large" "nav" << navigation state+  ]++-- search_form = spaceHtml+search_form = +  div_id "search" << form ! [action "/search", method "get"] << thediv << +    textfield "s" -- ! [strAttr "onFocus" "clearInput('s', 'Search')", strAttr "onBlur" "clearInput('s', 'Search')"]+  +site_name = toHtml $+  [ toHtml $ hotlink "/" ! [theclass "logo"] << ""+  , h1 << Config.blog_title+  , div_class "description" << Config.blog_subtitle+  ]++navigation state = div_class "content" <<+  ulist << +  [ li ! [theclass ("first" ++ current_home)] << home_link+  , li ! [theclass ("page_item page-item-2" ++ current_about)] << about+  ]+  where+    nav           = state.State.nav_location+    current       = " current_page_item"+    current_home  = at State.Home+    current_about = at State.About+    at x          = if nav == x then current else ""+++home_link   = hotlink "/" << "Home"+about       = hotlink "/static/about" << "About"+++body_content x = div_class_id "column span-9 first" "maincontent" <<+  div_class "content" << x+
+ Panda/View/Theme/BluePrint/Template/Footer.hs view
@@ -0,0 +1,21 @@+module Panda.View.Theme.BluePrint.Template.Footer where+  +import Panda.Helper.Env+import Prelude hiding ((.), (/), id, span)+import Panda.Config.Global as Config+import qualified Panda.Type.State as State+import Text.XHtml.Strict+++import Panda.Config.Global as Config++html_footer = div_id "footer" <<+  [ toHtml $ copyright+  , toHtml $ "2008 "+  , toHtml $ Config.blog_title+  , toHtml $ br+  , toHtml $ "Powered by "+  , toHtml $ hotlink Config.panda_url << "Panda"+  , toHtml $ " using "+  , toHtml $ hotlink "http://www.haskell.org/" << "Haskell"+  ]
+ Panda/View/Theme/BluePrint/Template/Header.hs view
@@ -0,0 +1,18 @@+module Panda.View.Theme.BluePrint.Template.Header where++import Panda.Helper.Env+import Prelude hiding ((.), (/), id, span)+import Panda.Config.Global as Config+import qualified Panda.Type.State as State+import Text.XHtml.Strict+++html_head  = header << [meta_tag, title_tag, screen_css, wp_css, custom_css, rss_tag] -- , ie_css, custom_css]++title_tag  = thetitle << Config.blog_title+screen_css = css_link "/theme/blueprint/wp/css/screen.css"+wp_css     = css_link "/theme/blueprint/wp/css/blueprint-wp.css"+-- ie_css  = ie $ css_link "theme/blueprint/core/ie.css"+custom_css = css_link "/theme/blueprint/wp/css/custom.css"++rss_tag = rss_link "rss.xml"
+ Panda/View/Theme/BluePrint/Template/Sidebar.hs view
@@ -0,0 +1,41 @@+module Panda.View.Theme.BluePrint.Template.Sidebar where++import Panda.Helper.Env hiding (rss_link)+import Prelude hiding ((.), (/), id, span)+import Panda.Config.Global as Config+import qualified Panda.Type.State as State+import Text.XHtml.Strict++import qualified Data.Set as Set++-- model+import Panda.Model.Tag as Tag++html_sidebar state = +  div_class_id "column span-3 last" "sidebar" << unordList+    [ feed state+    , hr+    , tag_list $ state.State.tags+    ]++feed state = toHtml+  [ h2 << "Subscribe"+  , p ! [theclass "feed"] << rss_link state+  ]++rss_link state = +  if tagged +  then+    let tag_name = uid.split "/".tail.first in+      link ("tag" / tag_name) tag_name+  else link "" "Home"+  where+    uid = State.uid state+    tagged = uid.match "^tag/.+" .isJust+    link r s = hotlink ("/" / r / "rss.xml") ! [ theclass "feedlink" ] << s++tag_list tags = toHtml+  [ h2 << "Tags"+  , unordList $ tags.Tag.sorted.map tag_link]+  +tag_link x = hotlink ("/" ++ x.uid) << x.Tag.name +++ (" (" ++ x.Tag.resources.Set.size.show ++ ")")
+ Panda/View/Theme/BluePrint/Template/Template.hs view
@@ -0,0 +1,11 @@+module Panda.View.Theme.BluePrint.Template.Template where++import Panda.View.Theme.BluePrint.Template.Header+import Panda.View.Theme.BluePrint.Template.Body++import Panda.Helper.Env++page state x = +  [ html_head+  , html_body state x+  ]
+ changelog.markdown view
@@ -0,0 +1,25 @@+0.0.0.3+--------++### New Features++* Search+* Feed per tag+* Theme through CSS++### Migration++* Checkout `public/theme/blueprint/wp/css/custom.css` in `panda-template`++0.0.0.2+--------++### General++* A new blueprint theme adopted from <http://www.fireandknowledge.org/archives/2007/09/05/blueprint-wordpress-theme/>+* Support for tags, see `panda-template` for example.+* Support for subtitle++### Migration++* Add a tag folder under `db`
panda.cabal view
@@ -1,5 +1,5 @@ Name:                 panda-Version:              0.0.0.2+Version:              0.0.0.3 Build-type:           Simple Synopsis:             Simple Static Blog Engine Description:          Simple Static Blog Engine@@ -11,26 +11,34 @@ Cabal-version:        >= 1.2 category:             Web homepage:             http://github.com/nfjinjing/panda/+data-files:			      readme.markdown, changelog.markdown  library-  build-depends: base, cgi, network, pandoc >= 0.46, old-locale, old-time, directory, filepath, containers >= 0.1.0.2, mps >= 0.0, parsedate >= 3000.0.0, rss >= 3000.1.0, xhtml >= 3000.2.0.0, kibro >= 0.0+  build-depends: base, cgi, network, haskell98, pandoc >= 0.46, old-locale, old-time, directory, filepath, containers >= 0.1.0.2, mps >= 0.0, parsedate >= 3000.0.0, rss >= 3000.1.0, xhtml >= 3000.2.0.0, kibro >= 0.0, HaXml >= 1.19.4   exposed-modules:  Panda                     Panda.Controller.Application                     Panda.Config.Global+                                         Panda.Helper.Helper+                    Panda.Helper.Escape                     Panda.Helper.Env+                     Panda.Model.Post                     Panda.Model.Tag+                     Panda.Type.Pager                     Panda.Type.State+                     Panda.View.RSS                     Panda.View.Theme.BluePrint.Post                     Panda.View.Theme.BluePrint.Static-                    Panda.View.Theme.BluePrint.Template-                    Panda.View.Theme.BluePrint.Header-                    Panda.View.Theme.BluePrint.Body-                    Panda.View.Theme.BluePrint.Footer-                    Panda.View.Theme.BluePrint.Sidebar                     Panda.View.Theme.BluePrint.Helper                     Panda.View.Theme.BluePrint.Tag+                    Panda.View.Theme.BluePrint.Search+                    Panda.View.Theme.BluePrint.Template.Template+                    Panda.View.Theme.BluePrint.Template.Header+                    Panda.View.Theme.BluePrint.Template.Body+                    Panda.View.Theme.BluePrint.Template.Footer+                    Panda.View.Theme.BluePrint.Template.Sidebar+                      
+ readme.markdown view
@@ -0,0 +1,123 @@+Panda static blogging engine+==============================++Sample file db structure+-------------------------++    db+    |---- blog+    |     |---- 08-09-01 first post+    |     |---- 09-09-02 learn javascript+    |+    |---- tag+          |---- programming+          |---- funny++Why+----++* Blog entries are stored locally in markdown format ( can be changed easily with more hacking )+* Remote server is a mirror of local repository+* No server side API for publishing / editing, just use Darcs, Git or any SCM+* No need to find external hosting of your videos, images or sound. Put them in public folder ( still managed by SCM if you wish )+* Embed any HTML snippets+* Statically typed HTML combinator = valid html+* Based on Kibro+* Freshman level MVC design++Quick Demo+-----------++It powers [my blog](http://jinjing.blog.easymic.com) ;)++Note: the demo might run on an experimental branch, so differences from the cabal version could occur.+        +Status+-------++Panda is in its early stage. I got tired of importing / exporting blogs when switching blogging platforms, +I needed a way to not be bounded to any service API, and that's the main design goal for Panda. There's no theming yet+(except that you wish to hack the source).++The main benefit is that if I ever wanted to stop using Panda, all my blogs and resources are in manageable states.++Install+--------++### install [lighttpd](http://www.lighttpd.net/ )+++### install panda++    cabal install panda++### bootstrap++    # panda is a kibro project+    kibro new myblog+    cd myblog+    +    # get a template to start+    rm -r db; rm -r public+    git clone git://github.com/nfjinjing/panda-template.git db+    sh db/scripts/bootstrap.sh+    +### run++	kibro start++It should be running on `http://127.0.0.1:3000` now.++Since Panda is based on Kibro, it helps to read how [Kibro](http://chrisdone.com/blog/2008/08/06/kibro-haskell-lighttpd-and-fastcgi/) works.++Goodies+--------++Install `Ruby` / `Rake`, then `rake -T` to see a list of helper commands.++Config+-------++Edit `db/config/site.txt`++    blog_title   = My shiny blog+    host_name    = yourhost.com+    author_email = your_mail@yourhost.com++Restart required. (hint: `rake r`)+++Future+------++The template db is just a starting point for you to experiment on. However, I would encourage you to keep your own db in your own SCM, that way, you will never lose your data and never have to care about application logic again.++There are many things could be done. Whatever there will be, the key idea is simplest data / folder structure, absolutely no parsing, no xml, no code is data like and stuff :)++Hacking+--------++Another way to get Panda running with source is:++    # clone the source+    git clone git://github.com/nfjinjing/panda.git myblog+    cd myblog+    +    # get a template to start+    git clone git://github.com/nfjinjing/panda-template.git db+    +    # manually adjust path information for lighttpd+    # edit first 6 lines of app/lighttpd/lighttpd.conf to match your path+    +    # manual staging+    ln -s db/public public+    +    # run+    kibro start+    +I'm heavily using the [MPS](http://github.com/nfjinjing/mps/tree/master ) package which introduces things like++    reject, join, belongs_to, match, gsub+    +and so on.+