-
Notifications
You must be signed in to change notification settings - Fork 2
/
site.hs
295 lines (258 loc) · 12.1 KB
/
site.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
--------------------------------------------------------------------------------
{-# LANGUAGE OverloadedStrings #-}
import Prelude hiding (div)
import Control.Monad
import Data.Monoid
import Data.List (uncons)
import Data.Maybe (fromMaybe, listToMaybe)
import Control.Applicative
import Hakyll
import Text.Blaze.Html.Renderer.String
import Text.Blaze.Html5 as H hiding (map, main)
import Text.Blaze.Html5.Attributes as A
import System.FilePath
import Debug.Trace
--------------------------------------------------------------------------------
data Page = Home | Organisation | News | Events | Supervisors | Zulip
deriving (Eq)
type URL = String
type Children = [(String, URL)]
type MenuEntry = (Page, URL, Children)
type EventInfo = (String, URL)
instance Show Page where
show Home = "Home"
show Organisation = "Organisation"
show News = "News"
show Events = "Events"
show Supervisors = "PhD Opportunities"
show Zulip = "Zulip"
data SupervisorData = MkSupervisor {
supName :: String,
supSiteUrl :: Maybe String,
supPicUrl :: String,
supInterests :: String
}
institutions :: [(String, String)]
institutions = [("University of Edinburgh", "edinburgh"),
("University of Glasgow", "glasgow"),
("Heriot-Watt University", "heriot-watt"),
("University of St Andrews", "st-andrews"),
("University of Stirling", "stirling"),
("University of Strathclyde", "strathclyde"),
("University of the West of Scotland", "uws")]
numNewsItems :: Int
numNewsItems = 3
--------------------------------------------------------------------------------
-- Matches a file in the given glob, then copies across
idMatch glob =
match glob $ do
route idRoute
compile copyFileCompiler
-- Replaces a file extension but keeps the path
replaceExt :: String -> Identifier -> FilePath
replaceExt ext path = (toFilePath path) -<.> ext
-- Replaces a file extension and discards the path
replaceExtFilename :: String -> Identifier -> FilePath
replaceExtFilename ext path = (takeFileName (toFilePath path)) -<.> ext
-- Compiles a markdown file and routes it to an HTML file
compileMarkdown glob routeFn pg =
match glob $ do
route (customRoute routeFn)
compile $ do
sCtx <- skeletonContext Organisation
pandocCompiler
>>= loadAndApplyTemplate "templates/content.html" defaultContext
>>= loadAndApplyTemplate "templates/skeleton.html" sCtx
>>= relativizeUrls
main :: IO ()
main = hakyll $ do
-- Assets
idMatch "assets/img/*"
idMatch "assets/img/**/*"
idMatch "assets/css/*"
idMatch "assets/js/*"
idMatch "assets/static/*"
idMatch "assets/static/**/*"
idMatch "assets/vendor/**/*"
-- Templates
match "templates/*" $ compile templateBodyCompiler
-- Events
match "content/events/regular/*.md" $ compile pandocCompiler
match "content/events/seminars/*.md" $ compile pandocCompiler
compileMarkdown "content/events/spli/*.md" (replaceExt "html") Events
-- (also need to re-load events for the navbar)
match "content/events/spli/*.md" $ version "navItems" $ compile pandocCompiler
-- News items
match "content/news/*.md" $ version "compiledNews" $ do
let itemContext = dateField "date" "%B %e, %Y" <> defaultContext
route $ setExtension "html"
compile $ do
sCtx <- skeletonContext News
pandocCompiler
>>= loadAndApplyTemplate "templates/blog-details.html" itemContext
>>= loadAndApplyTemplate "templates/skeleton.html" sCtx
>>= relativizeUrls
match "content/news/*.md" $ compile pandocCompiler
paginator <- buildPaginateWith grouper "content/news/*.md" makeId
paginateRules paginator $ \pageNum pattern -> do
route idRoute
compile $ do
sCtx <- skeletonContext News
newsItems <- recentFirst =<< loadAll pattern
let itemsContext = constField "title" ("News - Page " ++ (show pageNum))
<> listField "posts" shortItemContext (return newsItems)
<> paginateContext paginator pageNum
<> defaultContext
makeItem ""
>>= loadAndApplyTemplate "templates/blog.html" itemsContext
>>= loadAndApplyTemplate "templates/skeleton.html" sCtx
>>= relativizeUrls
-- Main page
match "content/about.md" $ do
picsIdents <- getMatches "assets/img/hero-carousel/*"
let mkPicItem ident = Item ident (toFilePath ident)
let (pic1, pics) = maybe (mempty, [])
(\(firstPic, rest) ->
((constField "firstImage" (toFilePath firstPic)), (map mkPicItem rest)))
(uncons picsIdents)
route $ customRoute $ const "index.html"
compile $ do
newsItems <- recentFirst =<< loadAll ("content/news/*.md" .&&. hasNoVersion) :: Compiler [Item String]
let ctx = pic1
<> listField "images" (bodyField "url" <> aboutInfo <> defaultContext) (return pics)
<> listField "posts" shortItemContext (return $ take numNewsItems newsItems) <> defaultContext
<> aboutInfo
<> defaultContext
sCtx <- skeletonContext Home
pandocCompiler
>>= loadAndApplyTemplate "templates/index.html" ctx
>>= loadAndApplyTemplate "templates/skeleton.html" sCtx
>>= relativizeUrls
-- Content pages
compileMarkdown "content/pages/organisation.md" (replaceExtFilename "html") Organisation
match "content/studentships/**/*.md" $ compile pandocCompiler
create ["events.html"] $ do
route idRoute
compile $ do
sCtx <- skeletonContext Events
regEvents <- loadAll "content/events/regular/*.md"
spliEvents <- loadAll ("content/events/spli/*.md" .&&. hasNoVersion)
seminars <- loadAll "content/events/seminars/*.md"
let eventsCtx =
listField "regevents" defaultContext (return regEvents)
<> listField "splievents" defaultContext (return spliEvents)
<> listField "seminars" defaultContext (return seminars)
<> defaultContext
makeItem ""
>>= loadAndApplyTemplate "templates/events.html" eventsCtx
>>= loadAndApplyTemplate "templates/skeleton.html" sCtx
>>= relativizeUrls
create ["supervisors.html"] $ do
route idRoute
compile $ do
sCtx <- skeletonContext Supervisors
institutionsHtml <- renderInstitutions
let supervisorCtx = constField "institutions" (renderHtml institutionsHtml) <> defaultContext
makeItem ""
>>= loadAndApplyTemplate "templates/supervisors.html" supervisorCtx
>>= loadAndApplyTemplate "templates/skeleton.html" sCtx
>>= relativizeUrls
--------------------------------------------------------------------------------
-- Supervisors page
-- Renders the supervisors for a given institution
renderInstitution :: String -> [SupervisorData] -> Html
renderInstitution institution supervisors =
div ! class_ "row gy-4" $ do
h2 ! class_ "mt-5 mb-0" $ toHtml institution
supervisorsHtml
where
supervisorsHtml = mapM_ renderSupervisor supervisors
renderSupervisor sup =
let supUrl = fromMaybe "#" (supSiteUrl sup) in
let supBox = div ! class_ "team-member d-flex align-items-start" $ do
div ! class_ "pic" $ img ! src (stringValue (supPicUrl sup)) ! class_ "img-fluid"
div ! class_ "member-info" $ do
h4 $ a ! href (stringValue supUrl) ! class_ "stretched-link" $ toHtml (supName sup)
preEscapedToHtml (supInterests sup)
in
div ! class_ "col-lg-6" $ supBox
-- Loads all supervisors for an institution, returning name and supervisor details
loadInstitution :: (String, String) -> Compiler (String, [SupervisorData])
loadInstitution (name, path) = do
supervisors <- loadAll $ fromGlob ("content/studentships/" ++ path ++ "/*")
supervisorDetails <- mapM (\itm -> do
let ident = itemIdentifier itm
name <- getMetadataField' ident "title"
pic <- getMetadataField ident "pic"
url <- getMetadataField ident "url"
let picUrl = fromMaybe "/assets/img/default-avatar.svg" pic
return $ MkSupervisor name url picUrl (itemBody itm)) supervisors
return (name, supervisorDetails)
-- Generates HTML for supervisor at each institution
renderInstitutions :: Compiler Html
renderInstitutions = do
loadedInstitutions <- mapM loadInstitution institutions
return $ div $ mapM_ (uncurry renderInstitution) loadedInstitutions
--------------------------------------------------------------------------------
-- Contexts
aboutInfo :: Context String
aboutInfo =
constField "spli-header" "SPLI: The Scottish Programming Languages Institute"
<> constField "spli-subheader" "The Scottish Programming Languages Institute (SPLI) co-ordinates community events that enhance programming languages research in Scotland."
defaultEvents :: [EventInfo]
defaultEvents = [("All events", "/events.html"), ("SPLS", "/spls"), ("SPLV", "/splv")]
menuItems :: [EventInfo] -> [(Page, String, Children)]
menuItems eventDetails = [(Home, "/", []), (News, "/news", []), (Organisation, "/organisation.html", []),
(Events, "/events.html", eventDetails),
(Supervisors, "/supervisors.html", []), (Zulip, "https://spls.zulipchat.com/", [])]
menuHTML :: [EventInfo] -> Page -> Html
menuHTML events activePage = H.ul entries
where entries = mapM_ (\(pg, url, children) -> mkEntry pg (stringValue url) children) (menuItems events)
renderChild (name, url) = H.li $ a ! href (stringValue url) $ toHtml name
mkEntry pg url children =
let link contents = if activePage == pg then
a ! href url ! class_ "active" $ contents
else
a ! href url $ contents
in
if null children then
H.li (link $ toHtml (show pg))
else
H.li ! class_ "dropdown" $ do
link $ do
H.span $ toHtml (show pg)
i ! class_ "bi bi-chevron-down toggle-dropdown" $ toHtml ("" :: String)
H.ul $ mapM_ renderChild children
skeletonContext :: Page -> Compiler (Context String)
skeletonContext currentPage = do
spliEvents <- loadAll ("content/events/spli/*.md" .&&. hasVersion "navItems") :: Compiler [Item String]
eventInfo <- mapM (\itm -> do
let ident = itemIdentifier itm
let pageUrl = "/" ++ (replaceExt "html" (itemIdentifier itm))
desc <- getMetadataField' ident "title"
extUrl <- getMetadataField ident "externalUrl"
let url = fromMaybe pageUrl extUrl
return (desc, url)) spliEvents
let events = defaultEvents ++ eventInfo
return $
constField "menu" (renderHtml $ menuHTML events currentPage)
<> defaultContext
shortItemContext :: Context String
shortItemContext =
field "content-short" (\itm ->
return $
fromMaybe "" (listToMaybe (lines (itemBody itm))))
<> field "url" (\itm -> do
let changeExt path = "/content/news/" ++ (takeFileName (toFilePath path) -<.> "html")
return $ changeExt $ itemIdentifier itm)
<> dateField "date" "%B %e, %Y"
<> defaultContext
postCtx :: Context String
postCtx =
dateField "date" "%B %e, %Y" `mappend`
defaultContext
grouper :: (MonadMetadata m, MonadFail m) => [Identifier] -> m [[Identifier]]
grouper ids = (fmap (paginateEvery 5) . sortRecentFirst) ids
makeId :: PageNumber -> Identifier
makeId 1 = fromFilePath $ "news/index.html"
makeId pageNum = fromFilePath $ "news/page/" ++ (show pageNum) ++ "/index.html"