◊(Local Yarn Code "Check-in [e52e53c8]")

Overview
Comment:Merge updates from trunk
Timelines: family | ancestors | descendants | both | doc-expansion
Files: files | file ages | folders
SHA3-256: e52e53c80aeef022515d121a19b92ec91bc79ba580e15ff1a080b727287a73ea
User & Date: joel on 2020-02-10 21:15:32
Other Links: branch diff | manifest | tags
Context
2020-02-12
21:12
Don’t clobber the footer when rebuilding code docs check-in: c8354d62 user: joel tags: doc-expansion
2020-02-10
21:15
Merge updates from trunk check-in: e52e53c8 user: joel tags: doc-expansion
17:28
Improved footer check-in: 75502f96 user: joel tags: trunk
2020-01-19
20:49
scribble edits check-in: ead156b1 user: joel tags: doc-expansion
Changes

Modified blog.rkt from [1dda01dc] to [75b9d900].

1
2
3
4
5
6
7
8
9
10

11
12
13
14
15
16
17
#lang pollen/mode racket/base

; SPDX-License-Identifier: BlueOak-1.0.0
; This file is licensed under the Blue Oak Model License 1.0.0.

;; Builds the paginated “blog” HTML files (blog-pg1.html ...) from the SQLite cache
;; The files will be written out every time this module is evaluated! (see end)

(require "crystalize.rkt"
         "snippets-html.rkt"

         racket/file
         sugar/list)

(provide main)

;; How many items per blog page
(define per-page 5)








|

>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#lang pollen/mode racket/base

; SPDX-License-Identifier: BlueOak-1.0.0
; This file is licensed under the Blue Oak Model License 1.0.0.

;; Builds the paginated “blog” HTML files (blog-pg1.html ...) from the SQLite cache
;; The files will be written out every time this module is evaluated! (see end)

(require "cache.rkt"
         "snippets-html.rkt"
         "dust.rkt"
         racket/file
         sugar/list)

(provide main)

;; How many items per blog page
(define per-page 5)
34
35
36
37
38
39
40

41
42
43
44
45
46
47
 <nav id="bottom-nav"><ul>◊|page-nav|</ul></nav>

 ◊html$-page-body-close[]
 </html>})

;; Grabs all the articles+notes from the cache and writes out all the blog page files
(define (build-blog)

  (define arts-n-notes (slice-at (listing-htmls (articles+notes 'full #:series #f)) per-page))
  (define pagecount (length arts-n-notes))
  
  (for ([pagenum (in-range 1 (+ 1 pagecount))]
        [page    (in-list arts-n-notes)])
    (define filename (format "blog-pg~a.html" pagenum))
    (displayln (format "Writing: ~a" filename))







>







35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
 <nav id="bottom-nav"><ul>◊|page-nav|</ul></nav>

 ◊html$-page-body-close[]
 </html>})

;; Grabs all the articles+notes from the cache and writes out all the blog page files
(define (build-blog)
  (listing-context 'blog) ; honor conceal directives for the blog
  (define arts-n-notes (slice-at (listing-htmls (articles+notes 'full #:series #f)) per-page))
  (define pagecount (length arts-n-notes))
  
  (for ([pagenum (in-range 1 (+ 1 pagecount))]
        [page    (in-list arts-n-notes)])
    (define filename (format "blog-pg~a.html" pagenum))
    (displayln (format "Writing: ~a" filename))

Added cache.rkt version [42c939a6].











































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
#lang racket/base

; SPDX-License-Identifier: BlueOak-1.0.0
; This file is licensed under the Blue Oak Model License 1.0.0.

(require deta
         db/base
         db/sqlite3
         threading
         pollen/setup
         racket/match
         (rename-in racket/list
                    (group-by group-list-by))
         "dust.rkt"
         (except-in pollen/core select))

(provide init-cache-db!
         cache-conn                     ; The most eligible bachelor in Neo Yokyo
         (schema-out cache:article)
         (schema-out cache:note)
         (schema-out cache:series)
         (schema-out cache:index-entry)
         delete-article!
         delete-notes!
         current-plain-title
         articles
         articles+notes
         listing-htmls
         <listing-full>
         <listing-excerpt>
         <listing-short>
         unfence
         series-grouped-list)

;; Cache DB and Schemas

(define DBFILE (build-path (current-project-root) "vitreous.sqlite"))
(define cache-conn (sqlite3-connect #:database DBFILE #:mode 'create))

(define current-plain-title (make-parameter "void"))

(define-schema cache:article #:table "articles"
  ([id                   id/f #:primary-key #:auto-increment]
   [page                 symbol/f]
   [title-plain          string/f]
   [title-html-flow      string/f]
   [title-specified?     boolean/f]
   [published            string/f]
   [updated              string/f]
   [author               string/f]
   [conceal              string/f]
   [series-page          symbol/f]
   [noun-singular        string/f]
   [note-count           integer/f]
   [doc-html             string/f]
   [disposition          string/f]
   [disp-html-anchor     string/f]
   [listing-full-html    string/f]   ; full content but without notes
   [listing-excerpt-html string/f]   ; Not used for now
   [listing-short-html   string/f])) ; Date and title only

(define-schema cache:note #:table "notes"
  ([id                   id/f #:primary-key #:auto-increment]
   [page                 symbol/f]
   [html-anchor          string/f]
   [title-html-flow      string/f] ; No block-level HTML elements
   [title-plain          string/f]
   [author               string/f]
   [author-url           string/f]
   [published            string/f]
   [disposition          string/f]
   [content-html         string/f]
   [series-page          symbol/f]
   [conceal              string/f]
   [listing-full-html    string/f]
   [listing-excerpt-html string/f]   ; Not used for now
   [listing-short-html   string/f])) ; Date and title only

(define-schema cache:series #:table "series"
  ([id            id/f #:primary-key #:auto-increment]
   [page          symbol/f]
   [title         string/f]
   [published     string/f]
   [noun-plural   string/f]
   [noun-singular string/f]))

(define-schema cache:index-entry #:table "index_entries"
  ([id          id/f #:primary-key #:auto-increment]
   [entry       string/f]
   [subentry    string/f]
   [page        symbol/f]
   [html-anchor string/f]))

(define-schema listing
  #:virtual
  ([html        string/f]
   [published   date/f]
   [series-page symbol/f]))

(define (init-cache-db!)
  (create-table! cache-conn 'cache:article)
  (create-table! cache-conn 'cache:note)
  (create-table! cache-conn 'cache:series)
  (create-table! cache-conn 'cache:index-entry))

(define (delete-article! page)
  (query-exec cache-conn
              (~> (from cache:article #:as a)
                  (where (= a.page ,(format "~a" page)))
                  delete)))

(define (delete-notes! page)
  (query-exec cache-conn
              (~> (from cache:note #:as n)
                  (where (= n.page ,(format "~a" page)))
                  delete)))

;;
;;  ~~~ Fetching articles and notes ~~~
;;

;; (Private use) Conveniece function for the WHERE `series-page` clause
(define (where-series q s)
  (define (s->p x) (format "~a/~a.html" series-folder x))
  (match s
    [(list series ...)
     (where q (in a.series-page ,(map s->p series)))] ; WHERE series-page IN (item1 ...)
    [(or (? string? series) (? symbol? series))
     (where q (= a.series-page ,(s->p series)))]      ; WHERE series-page = "item"
    [#t
     (where q (= a.series-page ,(path->string (here-output-path))))]
    [_ q]))

;; (Private use) Convenience for the WHERE `conceal` NOT LIKE clause
(define (where-not-concealed q)
  (define base-clause (where q (not (like a.conceal "%all%"))))
  (match (listing-context)
    ["" base-clause]
    [(var context) (where base-clause (not (like a.conceal ,(format "%~a%" context))))]))

;; Needed to "parameterize" column names
;; see https://github.com/Bogdanp/deta/issues/14#issuecomment-573344928
(require (prefix-in ast: deta/private/ast))

;; Builds a query to fetch articles
(define (articles type #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  (define html-field (format "listing_~a_html" type))
  (~> (from cache:article #:as a)
      (select (fragment (ast:as (ast:qualified "a" html-field) "html"))
              a.published
              a.series-page
              a.conceal)
      (where-series s)
      (where-not-concealed)
      (limit ,lim)
      (order-by ([a.published ,ord]))
      (project-onto listing-schema)))

;; Builds a query that returns articles and notes intermingled chronologically
(define (articles+notes type #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  (define html-field (format "listing_~a_html" type))
  (~> (from (subquery
             (~> (from cache:article #:as A)
                 (select (fragment (ast:as (ast:qualified "A" html-field) "html"))
                         A.published
                         A.series-page
                         A.conceal)
                 (union
                  (~> (from cache:note #:as N)
                      (select (fragment (ast:as (ast:qualified "N" html-field) "html"))
                              N.published
                              N.series-page
                              N.conceal)))))
            #:as a)
      (where-series s)
      (where-not-concealed)
      (limit ,lim)
      (order-by ([a.published ,ord]))
      (project-onto listing-schema)))

;; Get all the a list of the HTML all the results in a query
(define (listing-htmls list-query)
  (for/list ([l (in-entities cache-conn list-query)])
    (listing-html l)))

;; Return cached HTML of articles and/or notes, fenced within a style txexpr to prevent it being
;; escaped by ->html. See also: definition of `unfence`

;; E.g.: (<listing-full> articles+notes)
(define (<listing-full> query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  `(style ,@(listing-htmls (query-func 'full #:series s #:limit lim #:order ord))))
;;                                     ^^^^^

(define (<listing-excerpt> query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  `(style ,@(listing-htmls (query-func 'excerpt #:series s #:limit lim #:order ord))))
;;                                     ^^^^^^^^

(define (<listing-short> query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  `(style "<ul class=\"article-list\">"
          ,@(listing-htmls (query-func 'short #:series s #:limit lim #:order ord))
          "</ul>")) ;;                 ^^^^^^

;; Remove "<style>" and "</style>" introduced by using ->html on docs containing output from
;; listing functions
(define (unfence html-str)
  (regexp-replace* #px"<[\\/]{0,1}style>" html-str ""))

;;
;;  ~~~ Fetching series ~~~
;;
(define (series-grouped-list)
  (~> (for/list ([row (in-entities cache-conn (from cache:series #:as s))]) row)
      (group-list-by cache:series-noun-plural _ string-ci=?)))

Modified crystalize.rkt from [cb633ea2] to [c41782ea].

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
#lang racket/base

; SPDX-License-Identifier: BlueOak-1.0.0
; This file is licensed under the Blue Oak Model License 1.0.0.

(require deta db/base db/sqlite3 threading txexpr gregor)



(require racket/match
         racket/string
         pollen/pagetree
         pollen/template
         (except-in pollen/core select) ; avoid conflict with deta
         pollen/setup)


(require "dust.rkt" "snippets-html.rkt")

(provide init-cache-db!
         cache-conn            ; The most eligible bachelor in Neo Yokyo
         parse-and-cache-article!
         cache-series!
         current-plain-title
         (schema-out cache:article)
         (schema-out cache:note)
         (schema-out cache:series)
         (schema-out cache:index-entry)
         articles
         articles+notes
         listing-htmls
         <listing-full>
         <listing-excerpt>
         <listing-short>
         unfence)

;; Cache DB and Schemas

(define DBFILE (build-path (current-project-root) "vitreous.sqlite"))
(define cache-conn (sqlite3-connect #:database DBFILE #:mode 'create))

(define current-plain-title (make-parameter "void"))

(define-schema cache:article #:table "articles"
  ([id                   id/f #:primary-key #:auto-increment]
   [page                 symbol/f]
   [title-plain          string/f]
   [title-html-flow      string/f]
   [title-specified?     boolean/f]
   [published            string/f]
   [updated              string/f]
   [author               string/f]
   [conceal              string/f]
   [series-page          symbol/f]
   [noun-singular        string/f]
   [note-count           integer/f]
   [doc-html             string/f]
   [disposition          string/f]
   [disp-html-anchor     string/f]
   [listing-full-html    string/f]   ; full content but without notes
   [listing-excerpt-html string/f]   ; Not used for now
   [listing-short-html   string/f])) ; Date and title only

(define-schema cache:note #:table "notes"
  ([id                   id/f #:primary-key #:auto-increment]
   [page                 symbol/f]
   [html-anchor          string/f]
   [title-html-flow      string/f] ; No block-level HTML elements
   [title-plain          string/f]
   [author               string/f]
   [author-url           string/f]
   [published            string/f]
   [disposition          string/f]
   [content-html         string/f]
   [series-page          symbol/f]
   [listing-full-html    string/f]
   [listing-excerpt-html string/f]   ; Not used for now
   [listing-short-html   string/f])) ; Date and title only

(define-schema cache:series #:table "series"
  ([id            id/f #:primary-key #:auto-increment]
   [page          symbol/f]
   [title         string/f]
   [published     string/f]
   [noun-plural   string/f]
   [noun-singular string/f]))

(define-schema cache:index-entry #:table "index_entries"
  ([id          id/f #:primary-key #:auto-increment]
   [entry       string/f]
   [subentry    string/f]
   [page        symbol/f]
   [html-anchor string/f]))

(define-schema listing
  #:virtual
  ([html        string/f]
   [published   date/f]
   [series-page symbol/f]))

(define (init-cache-db!)
  (create-table! cache-conn 'cache:article)
  (create-table! cache-conn 'cache:note)
  (create-table! cache-conn 'cache:series)
  (create-table! cache-conn 'cache:index-entry))

;; Save an article and its notes (if any) to the database, and return the
;; rendered HTML of the complete article.
;;
(define (parse-and-cache-article! pagenode doc)
  (define-values (doc-no-title maybe-title)
    (splitf-txexpr doc (make-tag-predicate 'title)))
  (define-values (body-txpr note-txprs)
    (splitf-txexpr doc-no-title (make-tag-predicate 'note)))
  (define-values (disposition disp-note-id)
    (notes->last-disposition-values note-txprs))





|
|
>
>
|

|


<
|
>
|

<
<
|
|
<
<
<
<
<
<
<
<
<
<
<
<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


<







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
#lang racket/base

; SPDX-License-Identifier: BlueOak-1.0.0
; This file is licensed under the Blue Oak Model License 1.0.0.

(require deta
         db/base
         db/sqlite3
         threading
         racket/match
         racket/string
         txexpr
         pollen/template
         (except-in pollen/core select) ; avoid conflict with deta

)

(require "dust.rkt" "cache.rkt" "snippets-html.rkt")



(provide parse-and-cache-article!
         cache-series!)



















































































;; Save an article and its notes (if any) to the database, and return the
;; rendered HTML of the complete article.

(define (parse-and-cache-article! pagenode doc)
  (define-values (doc-no-title maybe-title)
    (splitf-txexpr doc (make-tag-predicate 'title)))
  (define-values (body-txpr note-txprs)
    (splitf-txexpr doc-no-title (make-tag-predicate 'note)))
  (define-values (disposition disp-note-id)
    (notes->last-disposition-values note-txprs))
127
128
129
130
131
132
133

134
135
136
137
138
139
140
                                               disp-note-id
                                               (length note-txprs))]
         [footer (html$-article-close footertext)]
         [listing-short (html$-article-listing-short pagenode pubdate title-html)]
         [notes-section-html (cache-notes! pagenode title-plain note-txprs)])
    (cache-index-entries! pagenode doc) ; note original doc is used here
    (current-plain-title title-plain)

    (insert-one! cache-conn
                 (make-cache:article
                  #:page pagenode
                  #:title-plain title-plain
                  #:title-html-flow title-html
                  #:title-specified? title-specified?
                  #:published pubdate







>







44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
                                               disp-note-id
                                               (length note-txprs))]
         [footer (html$-article-close footertext)]
         [listing-short (html$-article-listing-short pagenode pubdate title-html)]
         [notes-section-html (cache-notes! pagenode title-plain note-txprs)])
    (cache-index-entries! pagenode doc) ; note original doc is used here
    (current-plain-title title-plain)
    (delete-article! pagenode)
    (insert-one! cache-conn
                 (make-cache:article
                  #:page pagenode
                  #:title-plain title-plain
                  #:title-html-flow title-html
                  #:title-specified? title-specified?
                  #:published pubdate
253
254
255
256
257
258
259

260
261
262
263
264
265
266
                  #:title-html-flow title-html
                  #:title-plain (tx-strs title-tx)
                  #:published note-date
                  #:author author
                  #:author-url author-url
                  #:disposition disposition-attr
                  #:series-page (metas-series-pagenode)

                  #:content-html content-html
                  #:listing-full-html (html$-note-listing-full pagenode
                                                               note-id
                                                               title-html
                                                               note-date
                                                               content-html
                                                               author







>







171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
                  #:title-html-flow title-html
                  #:title-plain (tx-strs title-tx)
                  #:published note-date
                  #:author author
                  #:author-url author-url
                  #:disposition disposition-attr
                  #:series-page (metas-series-pagenode)
                  #:conceal (or (maybe-attr 'conceal attrs #f) (maybe-meta 'conceal))
                  #:content-html content-html
                  #:listing-full-html (html$-note-listing-full pagenode
                                                               note-id
                                                               title-html
                                                               note-date
                                                               content-html
                                                               author
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
                                     (where (= entry.page ,(symbol->string pagenode))))))
  (unless (null? entry-txs)
    (void
     (apply insert! cache-conn
            (for/list ([etx (in-list entry-txs)])
              (txexpr->index-entry etx pagenode))))))

;;
;;  ~~~ Fetching articles and notes ~~~
;;

;; (Private use) Conveniece function for the WHERE `series-page` clause
(define (where-series q s)
  (define (s->p x) (format "~a/~a.html" series-folder x))
  (match s
    [(list series ...)
     (where q (in a.series-page ,(map s->p series)))] ; WHERE series-page IN (item1 ...)
    [(or (? string? series) (? symbol? series))
     (where q (= a.series-page ,(s->p series)))]      ; WHERE series-page = "item"
    [#t
     (where q (= a.series-page ,(path->string (here-output-path))))]
    [_ q]))

;; Needed to "parameterize" column names
;; see https://github.com/Bogdanp/deta/issues/14#issuecomment-573344928
(require (prefix-in ast: deta/private/ast))

;; Builds a query to fetch articles
(define (articles type #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  (define html-field (format "listing_~a_html" type))
  (~> (from cache:article #:as a)
      (select (fragment (ast:as (ast:qualified "a" html-field) "html"))
              a.published
              a.series-page)
      (where-series s)
      (limit ,lim)
      (order-by ([a.published ,ord]))
      (project-onto listing-schema)))

;; Builds a query that returns articles and notes intermingled chronologically
(define (articles+notes type #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  (define html-field (format "listing_~a_html" type))
  (~> (from (subquery
             (~> (from cache:article #:as A)
                 (select (fragment (ast:as (ast:qualified "A" html-field) "html"))
                         A.published
                         A.series-page)
                 (union
                  (~> (from cache:note #:as N)
                      (select (fragment (ast:as (ast:qualified "N" html-field) "html"))
                              N.published
                              N.series-page)))))
            #:as a)
      (where-series s)
      (limit ,lim)
      (order-by ([a.published ,ord]))
      (project-onto listing-schema)))

;; Get all the a list of the HTML all the results in a query
(define (listing-htmls list-query)
  (for/list ([l (in-entities cache-conn list-query)])
    (listing-html l)))

;; Return cached HTML of articles and/or notes, fenced within a style txexpr to prevent it being
;; escaped by ->html. See also: definition of `unfence`

;; E.g.: (<listing-full> articles+notes)
(define (<listing-full> query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  `(style ,@(listing-htmls (query-func 'full #:series s #:limit lim #:order ord))))
;;                                     ^^^^^

(define (<listing-excerpt> query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  `(style ,@(listing-htmls (query-func 'excerpt #:series s #:limit lim #:order ord))))
;;                                     ^^^^^^^^

(define (<listing-short> query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  `(style "<ul class=\"article-list\">"
          ,@(listing-htmls (query-func 'short #:series s #:limit lim #:order ord))
          "</ul>")) ;;                 ^^^^^^

;; Remove "<style>" and "</style>" introduced by using ->html on docs containing output from
;; listing functions
(define (unfence html-str)
  (regexp-replace* #px"<[\\/]{0,1}style>" html-str ""))

;; Save the current article to the `series` table of the SQLite cache
;; Should be called from a template for series pages
(define (cache-series!)
  (define here-page (path->string (here-output-path)))
  (query-exec cache-conn
              (delete (~> (from cache:series #:as s)







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







228
229
230
231
232
233
234













































































235
236
237
238
239
240
241
                                     (where (= entry.page ,(symbol->string pagenode))))))
  (unless (null? entry-txs)
    (void
     (apply insert! cache-conn
            (for/list ([etx (in-list entry-txs)])
              (txexpr->index-entry etx pagenode))))))















































































;; Save the current article to the `series` table of the SQLite cache
;; Should be called from a template for series pages
(define (cache-series!)
  (define here-page (path->string (here-output-path)))
  (query-exec cache-conn
              (delete (~> (from cache:series #:as s)

Modified dust.rkt from [c096eb0e] to [dffdbbe2].

17
18
19
20
21
22
23

24
25
26
27
28
29
30

;; Provides common helper functions used throughout the project

(provide maybe-meta     ; Select from (current-metas) or default value ("") if not available
         maybe-attr     ; Return an attribute’s value or a default ("") if not available
         here-output-path
         here-id

         series-metas-noun    ; Retrieve noun-singular from current 'series meta, or ""
         series-metas-title   ; Retrieve title of series in current 'series meta, or ""
         metas-series-pagenode
         invalidate-series
         make-tag-predicate
         tx-strs
         ymd->english







>







17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

;; Provides common helper functions used throughout the project

(provide maybe-meta     ; Select from (current-metas) or default value ("") if not available
         maybe-attr     ; Return an attribute’s value or a default ("") if not available
         here-output-path
         here-id
         listing-context
         series-metas-noun    ; Retrieve noun-singular from current 'series meta, or ""
         series-metas-title   ; Retrieve title of series in current 'series meta, or ""
         metas-series-pagenode
         invalidate-series
         make-tag-predicate
         tx-strs
         ymd->english
62
63
64
65
66
67
68


69
70
71
72
73
74
75
(define (here-output-path)
  (cond [(current-metas)
         (define-values (_ rel-path-parts)
           (drop-common-prefix (explode-path (current-project-root))
                               (explode-path (string->path (select-from-metas 'here-path (current-metas))))))
         (->output-path (apply build-path rel-path-parts))]
        [else (string->path ".")]))



;; Checks current-metas for a 'series meta and returns the pagenode of that series,
;; or '|| if no series is specified.
(define (metas-series-pagenode)
  (define maybe-series (or (select-from-metas 'series (current-metas)) ""))
  (cond
    [(non-empty-string? maybe-series)







>
>







63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
(define (here-output-path)
  (cond [(current-metas)
         (define-values (_ rel-path-parts)
           (drop-common-prefix (explode-path (current-project-root))
                               (explode-path (string->path (select-from-metas 'here-path (current-metas))))))
         (->output-path (apply build-path rel-path-parts))]
        [else (string->path ".")]))

(define listing-context (make-parameter ""))

;; Checks current-metas for a 'series meta and returns the pagenode of that series,
;; or '|| if no series is specified.
(define (metas-series-pagenode)
  (define maybe-series (or (select-from-metas 'series (current-metas)) ""))
  (cond
    [(non-empty-string? maybe-series)

Modified index.html.pp from [6ce20187] to [511f6fe5].

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
#lang pollen

◊; SPDX-License-Identifier: BlueOak-1.0.0
◊; This file is licensed under the Blue Oak Model License 1.0.0.

◊(require pollen/template db/base racket/list racket/match)

◊(define (fetch-series)
  (define q "SELECT noun_plural, page, title FROM series ORDER BY noun_plural DESC")
  (query-rows cache-conn q))

◊(define (series-item->txpr s)
  (match-define (list n pagenode title) s)
  `(li (a [[href ,pagenode]] (i ,title))))

◊(define (series-grouped-list)
  ;; Produces '((("noun1" "p.html" "Title") ("noun1" "q.html" "Title")) (("noun2" ...) ...))
  (define init-group
    (group-by first (map vector->list (fetch-series)) string-ci=?))
  
  (define series-list-items
    (for/list ([group (in-list init-group)])
      `(div (h2 ,(first (first group))) (ul ,@(map series-item->txpr group)))))
  `(section [[class "series-list"] [style "margin-top: 1.3rem"]] ,@series-list-items))
            

<!DOCTYPE html>
<html lang="en">
◊html$-page-head["The Local Yarn" #f]
<style>
  header#front-page {
    text-align: center;





|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1
2
3
4
5
6



















7
8
9
10
11
12
13
#lang pollen

◊; SPDX-License-Identifier: BlueOak-1.0.0
◊; This file is licensed under the Blue Oak Model License 1.0.0.

◊(require pollen/template racket/file)




















<!DOCTYPE html>
<html lang="en">
◊html$-page-head["The Local Yarn" #f]
<style>
  header#front-page {
    text-align: center;
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
  @keyframes slideFromRight {
    0%   { margin-right: -200%; }
    100% { margin-right: 0%; }
  }

  #front-page-logo {
    shape-outside: url(web-extra/mark.svg);
    filter: brightness(0) saturate(100%) invert(20%) sepia(16%) saturate(903%) hue-rotate(153deg) brightness(93%) contrast(90%);

    shape-margin: 1rem;
    float: left;
    margin-left: -20vw;
    margin-right: 1rem;
    z-index: -100;
  }

  main {
    background: transparent;
    font-size: 1.3em;
    line-height: 1.2em;
  }
</style>
</head>

<body style="overflow-x:hidden; hyphens: auto">
 <header id="front-page"><div>







|
>









<







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
  @keyframes slideFromRight {
    0%   { margin-right: -200%; }
    100% { margin-right: 0%; }
  }

  #front-page-logo {
    shape-outside: url(web-extra/mark.svg);
    /* filter: brightness(0) saturate(100%) invert(20%) sepia(16%) saturate(903%) hue-rotate(153deg)
     * brightness(93%) contrast(90%); */
    shape-margin: 1rem;
    float: left;
    margin-left: -20vw;
    margin-right: 1rem;
    z-index: -100;
  }

  main {
    background: transparent;

    line-height: 1.2em;
  }
</style>
</head>

<body style="overflow-x:hidden; hyphens: auto">
 <header id="front-page"><div>
85
86
87
88
89
90
91

92
93
94
95
96
97
98
(almost) is ◊link[1]{arranged in time order, newest first}. There are also a few arranged into named
collections:

◊url[1]{/blog-pg1.html}

}) 
◊; stop for now: (crystalize-index-entries! '|index.html| front-page-body)


<main> 
  ◊(->html front-page-body #:splice? #t)
  ◊(->html (series-grouped-list))
</main> 
</body>
</html>







>



|



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
(almost) is ◊link[1]{arranged in time order, newest first}. There are also a few arranged into named
collections:

◊url[1]{/blog-pg1.html}

}) 
◊; stop for now: (crystalize-index-entries! '|index.html| front-page-body)
◊(display-to-file (html$-page-footer) "scribbled/site-footer.html" #:exists 'replace)

<main> 
  ◊(->html front-page-body #:splice? #t)
  ◊(html$-series-list)
</main> 
</body>
</html>

Modified keyword-index.rkt from [50258609] to [d37c4752].

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
         racket/list
         racket/file
         racket/string
         db/base
         net/uri-codec
         pollen/template)

(require "crystalize.rkt"
         "dust.rkt"
         "snippets-html.rkt")

(provide main)

;; Terminology (because these things get confusing fast)
;;







|







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
         racket/list
         racket/file
         racket/string
         db/base
         net/uri-codec
         pollen/template)

(require "cache.rkt"
         "dust.rkt"
         "snippets-html.rkt")

(provide main)

;; Terminology (because these things get confusing fast)
;;

Modified makefile from [f9d9c092] to [9872e7c1].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# SPDX-License-Identifier: BlueOak-1.0.0
# This file is licensed under the Blue Oak Model License 1.0.0.

SHELL = /bin/bash

# ~~~ Variables used by rules ~~~
#

core-files := pollen.rkt dust.rkt
html-deps  := snippets-html.rkt tags-html.rkt crystalize.rkt

article-sources := $(wildcard articles/*.poly.pm)
articles-html   := $(patsubst %.poly.pm, %.html, $(article-sources))
articles-pdf    := $(patsubst %.poly.pm, %.pdf,  $(article-sources))

series-sources  := $(wildcard series/*.poly.pm)
series-html     := $(patsubst %.poly.pm, %.html, $(series-sources))









|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# SPDX-License-Identifier: BlueOak-1.0.0
# This file is licensed under the Blue Oak Model License 1.0.0.

SHELL = /bin/bash

# ~~~ Variables used by rules ~~~
#

core-files := pollen.rkt dust.rkt
html-deps  := snippets-html.rkt tags-html.rkt crystalize.rkt cache.rkt

article-sources := $(wildcard articles/*.poly.pm)
articles-html   := $(patsubst %.poly.pm, %.html, $(article-sources))
articles-pdf    := $(patsubst %.poly.pm, %.pdf,  $(article-sources))

series-sources  := $(wildcard series/*.poly.pm)
series-html     := $(patsubst %.poly.pm, %.html, $(series-sources))
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
# the Pollen cache watchlist (see pollen.rkt)
vitreous.sqlite: $(core-files) $(html-deps) template.html.p
	raco pollen setup -p articles/
	raco pollen render -p -t html articles/*.poly.pm
	raco pollen setup -p series/
	raco pollen render -p -t html series/*.poly.pm
	rm -f template.html series/template.html
	# tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no articles/*.html || true
	# tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no series/*.html || true

# If the rule for vitreous.sqlite was triggered, all the article HTML files will already have been
# re-rendered.  (That rule comes before this one in the list of dependencies for "all") But if not,
# any individual files that have been edited will get re-rendered.
$(articles-html): %.html: %.poly.pm 
	raco pollen render $@
	# tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no $@ || true

# Note that if any article is part of a series, it will touch its series .poly.pm file during its
# render, triggering this rule for that series.
$(series-html): %.html: %.poly.pm
	raco pollen render $@
	# tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no $@ || true

index.html: $(core-files) $(html-deps) $(series-html) index.html.pp
	raco pollen render index.html


# This target will also rebuild pg2, pg3, etc. as needed
blog-pg1.html: $(core-files) $(html-deps) $(articles-html) blog.rkt
	rm -f blog*.html
	racket -tm blog.rkt
	# tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no blog*.html || true

keyword-index.html: $(core-files) $(html-deps) $(articles-html) keyword-index.rkt
	racket -tm keyword-index.rkt
	# tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no $@ || true

web-extra/martin.css: web-extra/martin.css.pp
	raco pollen render $@

feed.xml: vitreous.sqlite rss-feed.rkt
	racket -tm rss-feed.rkt








|
|


|



|





|



>





|



|







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
# the Pollen cache watchlist (see pollen.rkt)
vitreous.sqlite: $(core-files) $(html-deps) template.html.p
	raco pollen setup -p articles/
	raco pollen render -p -t html articles/*.poly.pm
	raco pollen setup -p series/
	raco pollen render -p -t html series/*.poly.pm
	rm -f template.html series/template.html
	tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no articles/*.html || true
	tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no series/*.html || true

# If the rule for vitreous.sqlite was triggered, all the article HTML files will already have been
# re-rendered.  (That rule comes before this one in the list of dependencies for "web") But if not,
# any individual files that have been edited will get re-rendered.
$(articles-html): %.html: %.poly.pm 
	raco pollen render $@
	tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no $@ || true

# Note that if any article is part of a series, it will touch its series .poly.pm file during its
# render, triggering this rule for that series.
$(series-html): %.html: %.poly.pm
	raco pollen render $@
	tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no $@ || true

index.html: $(core-files) $(html-deps) $(series-html) index.html.pp
	raco pollen render index.html
	fossil uv add scribbled/site-footer.html

# This target will also rebuild pg2, pg3, etc. as needed
blog-pg1.html: $(core-files) $(html-deps) $(articles-html) blog.rkt
	rm -f blog*.html
	racket -tm blog.rkt
	tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no blog*.html || true

keyword-index.html: $(core-files) $(html-deps) $(articles-html) keyword-index.rkt
	racket -tm keyword-index.rkt
	tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no $@ || true

web-extra/martin.css: web-extra/martin.css.pp
	raco pollen render $@

feed.xml: vitreous.sqlite rss-feed.rkt
	racket -tm rss-feed.rkt

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
		--exclude=.fslckout \
		--exclude='*.ltx' \
		--exclude='*.swp' \
		--exclude=.DS_Store \
	    --exclude='template*.*' \
		--exclude=makefile 
	rm -rf ~/Desktop/publish


scribble: ## Rebuild code documentation and update Fossil repo
	scribble --htmls +m --redirect https://docs.racket-lang.org/local-redirect/ code-docs/main.scrbl
	fossil uv rm scribbled/*
	rm -rf scribbled/*
	mv main/* scribbled/
	cp code-docs/scribble-iframe.html scribbled/scribble.html
	rm -rf main
	fossil uv add scribbled/*
	fossil uv sync

# Self-documenting makefile (http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html)
help: ## Displays this help screen
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'

article: ## Start a new article from a template
	racket -tm util/newpost.rkt

.PHONY: all scribble help zap article publish check-env

.DEFAULT_GOAL := help

check-env:
ifndef LOCALYARN_SRV
	$(error LOCALYARN_SRV env variable not set, should be a destination valid for rsync)
endif
ifndef WEB_SRV_PORT
	$(error WEB_SRV_PORT env variable not set, should be SSH port number for web server)
endif







>


















|










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
		--exclude=.fslckout \
		--exclude='*.ltx' \
		--exclude='*.swp' \
		--exclude=.DS_Store \
	    --exclude='template*.*' \
		--exclude=makefile 
	rm -rf ~/Desktop/publish
	fossil uv sync

scribble: ## Rebuild code documentation and update Fossil repo
	scribble --htmls +m --redirect https://docs.racket-lang.org/local-redirect/ code-docs/main.scrbl
	fossil uv rm scribbled/*
	rm -rf scribbled/*
	mv main/* scribbled/
	cp code-docs/scribble-iframe.html scribbled/scribble.html
	rm -rf main
	fossil uv add scribbled/*
	fossil uv sync

# Self-documenting makefile (http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html)
help: ## Displays this help screen
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'

article: ## Start a new article from a template
	racket -tm util/newpost.rkt

.PHONY: web scribble help zap article publish check-env

.DEFAULT_GOAL := help

check-env:
ifndef LOCALYARN_SRV
	$(error LOCALYARN_SRV env variable not set, should be a destination valid for rsync)
endif
ifndef WEB_SRV_PORT
	$(error WEB_SRV_PORT env variable not set, should be SSH port number for web server)
endif

Modified pollen.rkt from [ec6e30b5] to [5f7205ca].

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
                     racket/syntax
                     syntax/parse
                     pollen/setup))

(require pollen/tag
         pollen/setup
         racket/function

         "tags-html.rkt"
         "snippets-html.rkt"
         "crystalize.rkt")

(provide (all-defined-out)
         (all-from-out "crystalize.rkt" "snippets-html.rkt"))

(module setup racket/base
  (require syntax/modresolve
           racket/runtime-path
           pollen/setup)
  (provide (all-defined-out))
  (define poly-targets '(html))
  (define block-tags (append '(title style dt note) default-block-tags))

  (define-runtime-path tags-html.rkt     "tags-html.rkt")
  (define-runtime-path snippets-html.rkt "snippets-html.rkt")
  (define-runtime-path dust.rkt          "dust.rkt")
  (define-runtime-path crystalize.rkt    "crystalize.rkt")

  (define cache-watchlist
    (map resolve-module-path
         (list tags-html.rkt
               snippets-html.rkt
               dust.rkt

               crystalize.rkt))))

(case (current-poly-target)
  [(html) (init-cache-db!)])

;; Macro for defining tag functions that automatically branch based on the 
;; current output format and the list of poly-targets in the setup module.







>





|













>





>







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
                     racket/syntax
                     syntax/parse
                     pollen/setup))

(require pollen/tag
         pollen/setup
         racket/function
         "cache.rkt"
         "tags-html.rkt"
         "snippets-html.rkt"
         "crystalize.rkt")

(provide (all-defined-out)
         (all-from-out "crystalize.rkt" "snippets-html.rkt" "cache.rkt"))

(module setup racket/base
  (require syntax/modresolve
           racket/runtime-path
           pollen/setup)
  (provide (all-defined-out))
  (define poly-targets '(html))
  (define block-tags (append '(title style dt note) default-block-tags))

  (define-runtime-path tags-html.rkt     "tags-html.rkt")
  (define-runtime-path snippets-html.rkt "snippets-html.rkt")
  (define-runtime-path dust.rkt          "dust.rkt")
  (define-runtime-path crystalize.rkt    "crystalize.rkt")
  (define-runtime-path cache.rkt         "cache.rkt")
  (define cache-watchlist
    (map resolve-module-path
         (list tags-html.rkt
               snippets-html.rkt
               dust.rkt
               cache.rkt
               crystalize.rkt))))

(case (current-poly-target)
  [(html) (init-cache-db!)])

;; Macro for defining tag functions that automatically branch based on the 
;; current output format and the list of poly-targets in the setup module.

Modified rss-feed.rkt from [9efceb86] to [502871b7].

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(require txexpr
         racket/match
         racket/file
         racket/date
         racket/string
         db/base
         "dust.rkt"
         "crystalize.rkt")

(provide main)

(define feed-author default-authorname)
(define feed-author-email "joel@jdueck.net")
(define feed-title "The Local Yarn (Beta)")
(define feed-site-url "https://thelocalyarn.com")







|







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(require txexpr
         racket/match
         racket/file
         racket/date
         racket/string
         db/base
         "dust.rkt"
         "cache.rkt")

(provide main)

(define feed-author default-authorname)
(define feed-author-email "joel@jdueck.net")
(define feed-title "The Local Yarn (Beta)")
(define feed-site-url "https://thelocalyarn.com")
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
     SELECT `path`, `title`, `published`, `updated`, `author`, `entry_contents` FROM
       (SELECT `page` AS `path`,
               `title_plain` AS `title`,
               `published`,
               `updated`,
               `author`,
               `doc_html` AS `entry_contents`
        FROM `articles`
        UNION
        SELECT `page` || '#' || `html_anchor` AS `path`,
               `title_plain` AS `title`,
               `published`,
               "" AS `updated`,
               `author`,
               `content_html` as `entry_contents`
        FROM `notes`)
        ORDER BY `published` DESC LIMIT ~a
---
    )
  (query-rows cache-conn (format select feed-item-limit)))

(define (vector->rss-item vec)
  (match-define







|







|







50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
     SELECT `path`, `title`, `published`, `updated`, `author`, `entry_contents` FROM
       (SELECT `page` AS `path`,
               `title_plain` AS `title`,
               `published`,
               `updated`,
               `author`,
               `doc_html` AS `entry_contents`
        FROM `articles` WHERE (NOT (`conceal` LIKE "%all%")) AND (NOT (`conceal` LIKE "%feed%"))
        UNION
        SELECT `page` || '#' || `html_anchor` AS `path`,
               `title_plain` AS `title`,
               `published`,
               "" AS `updated`,
               `author`,
               `content_html` as `entry_contents`
        FROM `notes` WHERE (NOT (`conceal` LIKE "%all%")) AND (NOT (`conceal` LIKE "%feed%")))
        ORDER BY `published` DESC LIMIT ~a
---
    )
  (query-rows cache-conn (format select feed-item-limit)))

(define (vector->rss-item vec)
  (match-define

Modified snippets-html.rkt from [9386bc7c] to [f524862a].

9
10
11
12
13
14
15

16
17
18
19

20
21
22

23
24
25
26
27
28
29
         pollen/decode
         pollen/private/version
         racket/string
         racket/function
         racket/list
         txexpr
         openssl/sha1

         "dust.rkt")

(provide html$-page-head
         html$-page-body-open

         html$-article-open
         html$-article-close
         html$-article-listing-short

         html$-page-body-close
         html$-note-contents
         html$-note-listing-full
         html$-note-in-article
         html$-notes-section
         html$-paginate-navlinks)








>




>



>







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
         pollen/decode
         pollen/private/version
         racket/string
         racket/function
         racket/list
         txexpr
         openssl/sha1
         "cache.rkt"
         "dust.rkt")

(provide html$-page-head
         html$-page-body-open
         html$-series-list
         html$-article-open
         html$-article-close
         html$-article-listing-short
         html$-page-footer
         html$-page-body-close
         html$-note-contents
         html$-note-listing-full
         html$-note-in-article
         html$-notes-section
         html$-paginate-navlinks)

69
70
71
72
73
74
75
76












77
78
79


80
81
82
83
84
85
86

(define (html$-article-listing-short pagenode pubdate title)
  ◊string-append{
 <li><a href="/◊(symbol->string pagenode)">
 <div class="article-list-date caps">◊(ymd->english pubdate)</div>
 <div class="article-list-title">◊|title|</div>
 </a></li>})













(define (html$-page-body-close)
  ◊string-append{<footer>By Joel Dueck</footer>
 </main></body>})



;; Notes
;;
(define (html$-note-contents disposition-mark disposition-verb elems)
  (define disposition
    (cond [(non-empty-string? disposition-mark)
           `(abbr [[class "disposition-mark-in-note"]








>
>
>
>
>
>
>
>
>
>
>
>

|
|
>
>







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

(define (html$-article-listing-short pagenode pubdate title)
  ◊string-append{
 <li><a href="/◊(symbol->string pagenode)">
 <div class="article-list-date caps">◊(ymd->english pubdate)</div>
 <div class="article-list-title">◊|title|</div>
 </a></li>})

(define (html$-page-footer)
  ◊string-append{
<footer id="main">
 <p class="title">The Local Yarn</p>
 <nav><a href="/">Home</a> •
    <a href="/blog-pg1.html">Blog</a> •
    <a href="/keyword-index.html">Keyword Index</a> •
    <a href="/code"><i><code>◊"◊"(Source&nbsp;Code)</code></i></a>
 </nav>
 ◊(html$-series-list)
 </footer>})

(define (html$-page-body-close)
  ◊string-append{
 </main>
 ◊(html$-page-footer)
 </body>})

;; Notes
;;
(define (html$-note-contents disposition-mark disposition-verb elems)
  (define disposition
    (cond [(non-empty-string? disposition-mark)
           `(abbr [[class "disposition-mark-in-note"]
194
195
196
197
198
199
200











  (define next-link
    (if (eq? pagecount pagenum)
        "<li class=\"nav-text inactive-link\">Older&rarr;</li>"
        (page-func (+ pagenum 1) "Older&thinsp;&rarr;" "nav-text")))

  (string-join `(,prev-link ,@page-group ,next-link)))

















>
>
>
>
>
>
>
>
>
>
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227

  (define next-link
    (if (eq? pagecount pagenum)
        "<li class=\"nav-text inactive-link\">Older&rarr;</li>"
        (page-func (+ pagenum 1) "Older&thinsp;&rarr;" "nav-text")))

  (string-join `(,prev-link ,@page-group ,next-link)))

(define (series->txpr s)
  `(li (a [[href ,(symbol->string (cache:series-page s))]]
          (i ,(cache:series-title s)))))

(define (html$-series-list)
  (define series-list-items
    (for/list ([group (in-list (series-grouped-list))])
      `(div (h2 ,(cache:series-noun-plural (first group))) (ul ,@(map series->txpr group)))))
  (->html `(section [[class "column-list"] [style "margin-top: 1.3rem"]] ,@series-list-items)))

Modified util/newpost.rkt from [89e8b78f] to [d7b70e83].

31
32
33
34
35
36
37

38
39
40
41
42
43
44
45
(define (make-template-contents title)
  ◊string-append{
 #lang pollen

 ◊comment{Copyright ◊(substring date-string 0 4) by ◊|default-authorname|. All Rights Reserved.}

 ◊"◊"(define-meta published "◊date-string")

 ◊"◊"(define-meta series "seriesname")

 ◊"◊"title{◊title}

 Write here!})

(define (main)
  (display "Enter title: ")







>
|







31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
(define (make-template-contents title)
  ◊string-append{
 #lang pollen

 ◊comment{Copyright ◊(substring date-string 0 4) by ◊|default-authorname|. All Rights Reserved.}

 ◊"◊"(define-meta published "◊date-string")
 ◊"◊"(define-meta conceal   "blog,feed")  ; Edit/delete this line when ready to publish
 ◊"◊;"(define-meta series "seriesname")

 ◊"◊"title{◊title}

 Write here!})

(define (main)
  (display "Enter title: ")

Modified web-extra/font.css from [fb953054] to [81f7d56c].

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






  font-weight:  400;
}

@font-face {
  font-family: 'Fabiol';
  src: url('LDFabiolPro-Bold.woff2') format('woff2'),
       url('LDFabiolPro-Bold.woff') format('woff');
  font-style:   bold;
  font-weight:  700;
}

@font-face {
  font-family: 'Triplicate T4c';
  src: url('Triplicate-T4c.woff') format('woff');
  font-style: normal;
  font-weight: 400;
  }

@font-face {
  font-family: 'Triplicate T4c';
  src: url('Triplicate-T4c-italic.woff') format('woff');
  font-style: italic;
  font-weight: 400;
  }














|

















>
>
>
>
>
>
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
  font-weight:  400;
}

@font-face {
  font-family: 'Fabiol';
  src: url('LDFabiolPro-Bold.woff2') format('woff2'),
       url('LDFabiolPro-Bold.woff') format('woff');
  font-style:   normal;
  font-weight:  700;
}

@font-face {
  font-family: 'Triplicate T4c';
  src: url('Triplicate-T4c.woff') format('woff');
  font-style: normal;
  font-weight: 400;
  }

@font-face {
  font-family: 'Triplicate T4c';
  src: url('Triplicate-T4c-italic.woff') format('woff');
  font-style: italic;
  font-weight: 400;
  }

@font-face {
  font-family: 'Triplicate T4c';
  src: url('Triplicate-T4c-bold.woff') format('woff');
  font-style: normal;
  font-weight: bold;
  }

Modified web-extra/martin.css.pp from [62e09f38] to [6da40fde].

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

/* Let us first address the matter of font size in different screen sizes. */

/* Mobile portrait screens will see a minimum 18px font. */
html { font-size: 22px; } 

/* Start increasing type size dynamically at screen widths of 768px */
@media only screen and (min-width: 768px) { 
    html { font-size: 2.8vw; } 
}

/* Top out at 23px for screens up to 800px TALL */
◊; @media only screen and (min-width: 1000px) and (max-height: 800px) {
◊;    html { font-size: 26px; } /* =  2.6% of 1000px (min-width) */
◊; }

/* Top out at 28px for screens 801px-1000px TALL */
@media only screen and (min-width: 1000px) and (max-height: 920px) { 
   html { font-size: 28px; }     /* =  2.8% of 1000px (min-width) */
}
/* For screens taller than 1000 px, top out at 32px */
@media only screen and (min-width: 1178px) and (min-height: 921px) {
    html { font-size: 33px; }    /* =  2.8% of 1178px (min-width) */
}

◊; Since line height is used in so many places...
◊(define LINEHEIGHT 1.3)
◊(define lineheight (string-append (number->string LINEHEIGHT) "rem"))
◊(define (x-lineheight multiple) 







|









|



|







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

/* Let us first address the matter of font size in different screen sizes. */

/* Mobile portrait screens will see a minimum 18px font. */
html { font-size: 22px; } 

/* Start increasing type size dynamically at screen widths of 768px */
@media (min-width: 768px) { 
    html { font-size: 2.8vw; } 
}

/* Top out at 23px for screens up to 800px TALL */
◊; @media only screen and (min-width: 1000px) and (max-height: 800px) {
◊;    html { font-size: 26px; } /* =  2.6% of 1000px (min-width) */
◊; }

/* Top out at 28px for screens 801px-1000px TALL */
@media (min-width: 1000px) and (max-height: 920px) { 
   html { font-size: 28px; }     /* =  2.8% of 1000px (min-width) */
}
/* For screens taller than 1000 px, top out at 32px */
@media (min-width: 1178px) and (min-height: 921px) {
    html { font-size: 33px; }    /* =  2.8% of 1178px (min-width) */
}

◊; Since line height is used in so many places...
◊(define LINEHEIGHT 1.3)
◊(define lineheight (string-append (number->string LINEHEIGHT) "rem"))
◊(define (x-lineheight multiple) 
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
 **** 1. Mobile-first layout ***
      1.1 All Pages
      1.2 Front page
      1.3 Individual post body markup
      1.4 Journal views (article listings)  
 */

@media screen {
    body {
        margin: 0;
        background: ◊color-background;

        /* Typography: `line-height` is important!
           All verticle rhythm based on this value. */
        line-height: ◊lineheight;







<







70
71
72
73
74
75
76

77
78
79
80
81
82
83
 **** 1. Mobile-first layout ***
      1.1 All Pages
      1.2 Front page
      1.3 Individual post body markup
      1.4 Journal views (article listings)  
 */


    body {
        margin: 0;
        background: ◊color-background;

        /* Typography: `line-height` is important!
           All verticle rhythm based on this value. */
        line-height: ◊lineheight;
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
        100% {background: transparent;}
    }

    main > aside {
        text-align: center;
    }

    nav {
        font-family: 'Triplicate T4c', monospace;
        font-feature-settings: "onum" off;
        font-size: 0.7rem;
        margin: 0;
        margin-top: 0.5rem;
        text-align: center;
    }

    nav ul {
        list-style-type: none;
        margin: 0.2em auto;
        padding: 0;
    }

    nav li {
        display: none; /* Numbers not displayed on mobile */
        color: gray;
    }

    nav li.nav-text {
        display: inline;
        text-transform: uppercase;
        letter-spacing: 0.05rem;
        font-size: 0.6rem;
    }

    nav li.inactive-link {
        color: #545454; /* Accessibility (contrast) */
    }

    nav li.current-page {
        color: ◊color-bodytext;
        padding: 0.2rem 0.5rem;
        border-bottom: dotted ◊color-bodytext 2px;
    }

    nav li a {
        padding: 0.2rem 0.5rem;
    }

    nav li a:link, nav li a:visited {
        color: ◊color-bodytext;
    }

    nav li a:hover, nav li a:active {
        color: ◊color-linkhover;
        background: #ebebeb;
    }
    
    i > em { font-style: normal; }
    
    /* On mobile, an <ARTICLE> is just a box. Later we'll do fancy stuff if grid support is detected. */







|








|





|




|






|



|





|



|



|







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
        100% {background: transparent;}
    }

    main > aside {
        text-align: center;
    }

main nav {
        font-family: 'Triplicate T4c', monospace;
        font-feature-settings: "onum" off;
        font-size: 0.7rem;
        margin: 0;
        margin-top: 0.5rem;
        text-align: center;
    }

main nav ul {
        list-style-type: none;
        margin: 0.2em auto;
        padding: 0;
    }

main nav li {
        display: none; /* Numbers not displayed on mobile */
        color: gray;
    }

main nav li.nav-text {
        display: inline;
        text-transform: uppercase;
        letter-spacing: 0.05rem;
        font-size: 0.6rem;
    }

main nav li.inactive-link {
        color: #545454; /* Accessibility (contrast) */
    }

main nav li.current-page {
        color: ◊color-bodytext;
        padding: 0.2rem 0.5rem;
        border-bottom: dotted ◊color-bodytext 2px;
    }

main nav li a {
        padding: 0.2rem 0.5rem;
    }

main nav li a:link, nav li a:visited {
        color: ◊color-bodytext;
    }

main nav li a:hover, nav li a:active {
        color: ◊color-linkhover;
        background: #ebebeb;
    }
    
    i > em { font-style: normal; }
    
    /* On mobile, an <ARTICLE> is just a box. Later we'll do fancy stuff if grid support is detected. */
631
632
633
634
635
636
637
638
639


640
641

642



643
644



645
646
647
648
649
650
651
652
653
654
655
656
657
658
659

660
661
662
663
664
665
666
    div.article-list-title {
        font-size: 1.2rem;
    }

    /* ******* (Mobile first) Columnar series list styling *******
     */

    .series-list {
        column-width: 9rem;


    }


    .series-list div {



        -webkit-column-break-inside: avoid; /* Chrome, Safari, Opera */
        page-break-inside: avoid; /* Firefox */



        break-inside: avoid; /* IE 10+ */
    }

    .series-list h2 {
        font-feature-settings: "smcp" on;
        text-transform: lowercase;
        font-weight: normal;
        font-size: 1em;
        margin: 0;
    }

    .series-list ul {
        margin-top: 0;
        list-style-type: none;
        padding: 0;

    }

    /* ******* (Mobile first) Keyword Index styling *******
     */

    #keywordindex {
        column-width: 7rem;







|
|
>
>


>
|
>
>
>
|
<
>
>
>
|


|







|



>







630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648

649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
    div.article-list-title {
        font-size: 1.2rem;
    }

    /* ******* (Mobile first) Columnar series list styling *******
     */

.column-list {
    max-width: 17rem;
    margin: 0;
    columns: 8rem auto;
    }

@media (min-width: 667px) {
  .column-list {
    margin: 0 auto;
  }
}


.column-list div {
    padding-left: 0.25em; /* Keeps some italic descenders inside the box */
    text-align: left;
    break-inside: avoid;
    }

.column-list h2 {
        font-feature-settings: "smcp" on;
        text-transform: lowercase;
        font-weight: normal;
        font-size: 1em;
        margin: 0;
    }

.column-list ul {
        margin-top: 0;
        list-style-type: none;
        padding: 0;
    margin-bottom: 0.5rem;
    }

    /* ******* (Mobile first) Keyword Index styling *******
     */

    #keywordindex {
        column-width: 7rem;
684
685
686
687
688
689
690

691






692
693




























694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
    }

    #keywordindex ul ul {
        margin-left: 0.5em;
        font-size: smaller;
    }


    /* End of mobile-first typography and layout */






}






























/* Here’s where we start getting funky for any viewport wider than mobile portrait.
   An iPhone 6 is 667px wide in landscape mode, so that’s our breakpoint. */

@media only screen and (min-width: 667px) {
    main {
        margin: 0 auto;
        padding-left: 1rem;
        padding-right: 1rem;
        width: 30rem;
        max-width: 90%;
    }

    nav li { display: inline; } /* Display page numbers on larger screens */

    @supports (grid-area: auto) {
        main { 
            width: 42rem;
            background: none;
        }








>
|
>
>
>
>
>
>


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




|








|







692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
    }

    #keywordindex ul ul {
        margin-left: 0.5em;
        font-size: smaller;
    }

/* Footer ***** */

footer#main {
  text-align: left;
  font-size: 0.8rem;
  line-height: 1rem;
  width: 90%;
  margin: 0 auto;
}

@media (min-width: 667px) {
  footer#main {
    text-align: center;
    width: 100%;
    background: linear-gradient(◊color-background 5%, #dedede 100%);
  }
}

footer#main p.title {
  font-size: 1rem;
  font-feature-settings: "smcp" on;
  text-transform: lowercase;
  margin-top: ◊x-lineheight[2];
  margin-bottom: ◊x-lineheight[0.5];
}

footer#main nav {
  font-feature-settings: "smcp" on;
  text-transform: lowercase;
}

footer#main nav code {
  font-size: 0.6rem;
}
  

/* End of mobile-first typography and layout */


/* Here’s where we start getting funky for any viewport wider than mobile portrait.
   An iPhone 6 is 667px wide in landscape mode, so that’s our breakpoint. */

@media (min-width: 667px) {
    main {
        margin: 0 auto;
        padding-left: 1rem;
        padding-right: 1rem;
        width: 30rem;
        max-width: 90%;
    }

    main nav li { display: inline; } /* Display page numbers on larger screens */

    @supports (grid-area: auto) {
        main { 
            width: 42rem;
            background: none;
        }

741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
            border-radius: 2px;
        }
        
        article>h1 {
            grid-area: margin;
            font-size: 1.1rem;
            text-align: right;
            /* padding-right: 1rem; */
            color: ◊color-bodytext;
        }

        article>h1.entry-title {
            grid-area: title;
            font-size: ◊x-lineheight[1];
            text-align: left;
            padding-right: 0;







<
<







784
785
786
787
788
789
790


791
792
793
794
795
796
797
            border-radius: 2px;
        }
        
        article>h1 {
            grid-area: margin;
            font-size: 1.1rem;
            text-align: right;


        }

        article>h1.entry-title {
            grid-area: title;
            font-size: ◊x-lineheight[1];
            text-align: left;
            padding-right: 0;
859
860
861
862
863
864
865
















        }

        div.content-block-main > :first-child {
            margin-top: 0 !important;
        }
    }
}























>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
        }

        div.content-block-main > :first-child {
            margin-top: 0 !important;
        }
    }
}

@media print {
    html { font-size: 13pt; }
    body { background: white; color: black; }
    main { width: 100%; }
    footer.article-info { color: black; }
    article, section.content-block {
        box-shadow: none;
        border: none;
        }
    a:link, a:visited, a:hover, a:active {
        color: black;
        border-bottom: dotted 1px black;
    }
    footer#main { display: none; }
}