~zainab/blog

blog/src/structure.rkt -rw-r--r-- 6.2 KiB
d9e5070ezainab-ali Add question aside 8 months ago
                                                                                
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
#lang racket

(require pollen/core)
(require pollen/pagetree)
(require pollen/tag)
(require pollen/file)

(require racket/dict)

;; Utils

(define (html-list type els #:class [maybe-class ""])
  `(,type [(class ,maybe-class)]
          ,@(map (λ (e) `(li ,@e)) els)))

(define (html-list-of type els #:class [maybe-class ""])
  (html-list type (map list els) #:class maybe-class))

(define (anchor ref title #:class [maybe-class ""])
  `(a ([class ,maybe-class] [href ,ref]) ,title))

(define (main-content els)
  `(main ([id "main-content"]) ,@els))

(define (page-key node)
  ;; Given a symbol (a page node) such as '/chapter/fs2/overview.html.pm
  ;; extract the string "overview"
  (define pagename (last (string-split (symbol->string node) "/")))
  (string->symbol (car (string-split pagename "."))))


(define (chapter-time-str n)
  (match-define-values (hours quarters) (quotient/remainder n 4))
  (define hours-str
    (match hours
      [0 ""]
      [1 "1 hour"]
      [_ (string-append (number->string hours) " hours")]))
  (define minutes-str
    (match quarters
      [0 ""]
      [_ (string-append (number->string (* 15 quarters)) " minutes")]
      ))
  (string-trim (string-append hours-str " " minutes-str)))

(define (time->html time)
  `(div ([class "time"])
       (span ([class "icon"]) (img ([alt ""]
                                    [src "/assets/time.svg"])))
       (span ,(string-append (chapter-time-str time)))))

;; Common header

(define book-title "kebab-case")


(define book-title-h1
  `(h1
    ([class "bookTitle"])
    (a ([href "/index.html"])
       (img ([src "/assets/logo-href.svg"] [alt ""] [class "logo"]))
       (img ([src "/assets/logo-href-hover.svg"] [alt ""] [class "logo-hover"])))))

(define skip-nav
  `(a ([href "#main-content"] [class "sr-only"])
      "Skip to main content"))


;; Chapter pages
(define (chapter-abstract-node node)
  (define chapter-dir (string-join (drop-right (string-split (symbol->string node) "/") 1) "/"))
  (string->symbol (format "~a/abstract.html.pm" chapter-dir)))

(define (chapter-info node)
  ;; Given a page node (a symbol) such as 'chapters/fs2/overview.html.pm
  ;; extract the metadata associated with 'fs2
  (define abstract-node (chapter-abstract-node node))
  ;; This metadata is the title and time in 1/4 hour units
  (match-define (list hr q) (select-from-metas 'time abstract-node))
  (define time (+ q (* hr 4)))
  (define title (select-from-metas 'title abstract-node))
  (cons title time))

(define (chapter-page-title node)
  (define ch-name (car (chapter-info node)))
  `(title ,(string-append book-title ": " ch-name "- " (chapter-page-name node))))

(define (chapter-page-name page)
(string-replace (symbol->string (page-key page)) "-" " "))

(define (chapter-page-header node doc)
  (match-define (cons ch-name time) (chapter-info node))
  (define ch-heading
    `(h1 ([class "chapter-page-heading"]) ,ch-name))
  (define surrounding-nodes
    (if (equal? (page-key node) 'overview)
        (cons node (children node))
        (cons (parent node) (siblings node))))
  (define ch-page-nav
    (let* ([here? (λ (page) (equal? node page))]
           [page-link
            (λ (page)
              (anchor
               (string-append "/" (symbol->string page))
               (chapter-page-name page)
               #:class (if (here? page) "here" "")))]
           [page-ol (html-list-of 'ol (map page-link surrounding-nodes))])
      `(nav ([class "chapter-page-nav"]) ,page-ol)))

  (define internal-nav
    (let* ([names (select* 'h2 doc)]
           [internal-id (lambda (name) (anchor (string-append "#" (page-internal-id name)) name))])
      (if (and names (chapter-page-has-internal-nav node))
          `(nav ([class "chapter-page-internal-nav"]) ,(html-list-of 'ol (map internal-id names)))
          #f)))

  `(header
    ,book-title-h1
    ,ch-heading
    ,(time->html time)
    ,ch-page-nav
    ,(when/splice internal-nav internal-nav)))

(define (chapter-page-has-internal-nav here)
  (not (or (equal? (page-key here) 'recap)
           (equal? (page-key here) 'overview)
           (equal? (page-key here) 'introduction))))

(define (chapter-page-main-content doc)
  (main-content (cdr doc)))

(define (chapter-page-footer here)
  (define nextPage (next here))
  (when/splice nextPage
               (let ([link (string-append "/" (symbol->string nextPage))])
                 `(footer ([class "navigation"])
                          ,(anchor link "next")))))

;; Page internals

(define (page-internal-id text)
  (string-replace text " " "-"))

(define (summary)
  (define overviews (filter
              (λ (node)
                (string-suffix? (symbol->string node) "overview.html"))
              (pagetree->list (get-pagetree "index.ptree"))))
  (define cards (map
   (λ (node)
     (match-define (cons title time) (chapter-info node))
     (define abstract (cdr (get-doc (chapter-abstract-node node))))
     `(button ([class "summary-card"]
               [onclick ,(format "window.location.href='~s';" node)])
       (article ([class "summary-card"])
                (header ([class "header"])
                        (h1 (span ([href ,(symbol->string node)]) ,title))
                        ,(time->html time))
                ,@abstract)))
   overviews))
  `(div ([class "summary"]) ,@cards))

(define index-page-header
  `(header ([class "index-page-header"]) (img ([src "/assets/logo.svg"] [alt "kebab case"]))))

(define (index-page-main-content doc)
  (main-content (append (cdr doc) (list (summary)))))

(define (external-link #:href href . text)
  `(a ([class "external-link"] [href ,href] [target "_blank"] ) ,@text))

(define index-page-footer
  `(footer ([class "index-page-footer"])
           (p
            "This blog is a program. It was written in "
            ,(external-link #:href "https://docs.racket-lang.org/pollen/" "Pollen")
            " and built using "
            ,(external-link #:href "https://nixos.org/" "Nix")
            ". You can find its source code on "
            ,(external-link #:href "https://git.sr.ht/~zainab/blog" "SourceHut")
            ".")))

(provide
 skip-nav
 page-internal-id
 chapter-page-title
 chapter-page-header
 chapter-page-main-content
 chapter-page-footer
 book-title
 index-page-header
 index-page-footer
 index-page-main-content)