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

Overview
Comment:Add blog
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 8145bdb72882bf39c50903fde11b055146fafed1b6d24611e4bf26570ab6e512
User & Date: joel on 2019-04-18 03:08:21
Other Links: manifest | tags
Context
2019-04-27
03:11
Undo my terrible idea for page-number notation check-in: 9a7cf2fd user: joel tags: trunk
2019-04-18
03:08
Add blog check-in: 8145bdb7 user: joel tags: trunk
2019-04-16
03:02
Tweak dialogue/say tag output check-in: 9922a46b user: joel tags: trunk
Changes

Added blog.rkt version [ca9ab776].

















































































































































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

;; Copyright (c) 2019 Joel Dueck.
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
;; A copy of the License is included with this source code, in the
;; file "LICENSE.txt".
;; You may also obtain a copy of the License at
;;
;;       http://www.apache.org/licenses/LICENSE-2.0
;;
;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;; See the License for the specific language governing permissions and
;; limitations under the License.
;;
;; Author contact information:
;;   joel@jdueck.net
;;   https://joeldueck.com
;; -------------------------------------------------------------------------

;; 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)

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

;; Returns a string containing the entire HTML contents of a given blog page
(define (blog-page posts-str pagenum total-pages)
  (define page-nav (html$-paginate-navlinks pagenum total-pages "blog"))
  ◊string-append{
<!DOCTYPE html>
<html lang="en">
◊html$-page-head[(format "The Local Yarn: Blog, p. ~a" pagenum)]
◊html$-page-body-open[]

<aside><i>Everything, in reverse time order. Well, almost everything.</i></aside>

<nav id="top-nav"><ul>◊|page-nav|</ul></nav>

◊posts-str

<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)
  (spell-of-summoning!) ; Turn on the DB
  
  (define articles+notes (slice-at (list/articles+notes 'listing_full_html #:series #f) per-page))
  (define pagecount (length articles+notes))
  
  (for ([pagenum (in-range 1 (+ 1 pagecount))]
        [page    (in-list articles+notes)])
    (define filename (format "blog-pg~a.html" pagenum))
    (println (format "Writing: ~a" filename))
    (display-to-file (blog-page (apply string-append page) pagenum pagecount)
                     filename
                     #:mode 'text
                     #:exists 'replace)))

;; Do it!
(build-blog)

Modified code-docs/crystalize.scrbl from [c9cfb91f] to [36b67736].

44
45
46
47
48
49
50




























51
52
53
54
55
56
57
my convention of using a prefix of @tt{html$-} for functions that return strings of HTML.}
Privately, it does a lot of other work. The article is saved to the SQLite cache. If the article
specifies a @racket['series] meta, information about that series is fetched and used in the
rendering of the article. If there are @racket[note]s in the doc, they are parsed and saved
individually to the SQLite cache. If any of the notes use the @code{#:disposition} attribute,
information about the disposition is parsed out and used in the rendering of the article.





























@deftogether[(@defproc[(list-short/articles [#:series series (or/c string? boolean?) #t]
                                            [#:limit limit stringish? -1]
                                            [order string? "DESC"]) txexpr?]
              @defproc[(list-full/articles  [#:series series (or/c string? boolean?) #t]
                                            [#:limit limit stringish? -1]
                                            [order string? "DESC"]) txexpr?])]








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







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
my convention of using a prefix of @tt{html$-} for functions that return strings of HTML.}
Privately, it does a lot of other work. The article is saved to the SQLite cache. If the article
specifies a @racket['series] meta, information about that series is fetched and used in the
rendering of the article. If there are @racket[note]s in the doc, they are parsed and saved
individually to the SQLite cache. If any of the notes use the @code{#:disposition} attribute,
information about the disposition is parsed out and used in the rendering of the article.

@deftogether[(@defproc[(list/articles [type (or/c 'listing_full_html 
                                                  'listing_short_html
                                                  'listing_excerpt_html)]
                                      [#:series series (or/c string? boolean?) #t]
                                      [#:limit limit stringish? -1]
                                      [order string? "DESC"]) (listof string?)]
              @defproc[(list/articles+notes [type (or/c 'listing_full_html 
                                                        'listing_short_html
                                                        'listing_excerpt_html)]
                                            [#:series series (or/c string? boolean?) #t]
                                            [#:limit limit stringish? -1]
                                            [order string? "DESC"]) (listof string?)])]

Fetches the HTML for all articles from the SQLite cache and returns a list of strings containing the
HTML for each. The articles will be ordered by publish date according to @racket[_order] and
optionally limited to the series specified in @racket[_series].

If @racket[_series] expression evaluates to @racket[#f], articles will not be filtered by series. If
it evaluates to @racket[#t] (the default), articles will be filtered by those that specify the
current output of @racket[here-output-path] in their @tt{series_pagenode} column in the SQLite
cache. If a string is supplied, articles will be filtered by those containing that exact value in
their @tt{series_pagenode} column in the SQLite cache.

The @racket[_order] expression must evaluate to either @racket["ASC"] or @racket["DESC"] and the
@racket[_limit] expressions must evaluate to a value suitable for use in the @tt{LIMIT} clause of
@ext-link["https://sqlite.org/lang_select.html"]{a SQLite @tt{SELECT} statement}. An expression that
evaluates to a negative integer (the default) is the same as having no limit.

@deftogether[(@defproc[(list-short/articles [#:series series (or/c string? boolean?) #t]
                                            [#:limit limit stringish? -1]
                                            [order string? "DESC"]) txexpr?]
              @defproc[(list-full/articles  [#:series series (or/c string? boolean?) #t]
                                            [#:limit limit stringish? -1]
                                            [order string? "DESC"]) txexpr?])]

Modified code-docs/snippets-html.scrbl from [8b5eb568] to [80a0bc1d].

129
130
131
132
133
134
135



136






Like @racket[html$-note-listing-full], but returns HTML for a @racket[note] suitable for display
inside its parent article.

@defproc[(html$-notes-section [note-htmls string?]) non-empty-string?]

Returns the complete HTML for the @italic{Further Notes} section of an article.


















>
>
>

>
>
>
>
>
>
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
Like @racket[html$-note-listing-full], but returns HTML for a @racket[note] suitable for display
inside its parent article.

@defproc[(html$-notes-section [note-htmls string?]) non-empty-string?]

Returns the complete HTML for the @italic{Further Notes} section of an article.

@defproc[(html$-paginate-navlinks [current-page exact-positive-integer?] 
                                  [pagecount exact-positive-integer?]
                                  [basename string?]) string?]

On the “blog”, the articles are split across multiple files: @filepath{blog-pg1.html},
@filepath{blog-pg2.html}, etc. This function provides a string containing HTML for a group of links
that can be given within each file, to link to the pages that come before/after it. 

The links are enclosed within @tt{<li>} tags. It’s up to the calling site to provide the enclosing
@tt{<ul>} tag (in case any custom styling or IDs need to be applied).

Modified crystalize.rkt from [b88a4287] to [013cbdf1].

43
44
45
46
47
48
49


50
51
52
53
54
55
56
...
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
         "dust.rkt")

;; ~~~ Provides ~~~

(provide spell-of-summoning!
         crystalize-article!
         article-plain-title


         list-short/articles
         list-full/articles
         list-full/articles+notes
         unfence
         preheat-series!)

;; ~~~ Private use ~~~
................................................................................
           (format "WHERE `series_pagenode` IN ~a" (list->sql-values series)))]
        [(string? s)
         (format "WHERE `series_pagenode` IS \"~a/~a.html\"" series-folder s)]
        [(equal? s #t)
         (format "WHERE `series_pagenode` IS \"~a\"" (here-output-path))]
        [else ""]))

;; (private) Return a combined list of articles and notes sorted by date
(define (list/articles+notes type #:series [s #t] #:limit [limit -1] [order "DESC"])
  (define select #<<@@@@@
     SELECT `~a` FROM
       (SELECT `~a`, `published` FROM `articles`
        UNION SELECT
        `~a`,`date` AS `published` FROM `notes`
        ~a ORDER BY `published` ~a LIMIT ~a)
@@@@@
    )
  (query-list (sqltools:dbc) (format select type type type (where/series s) order limit)))

;; (private) Return a list of articles only, sorted by date
(define (list/articles type #:series [s #t] #:limit [limit -1] [order "DESC"])
  (define select "SELECT `~a` FROM `articles` ~a ORDER BY `published` ~a LIMIT ~a")
  (query-list (sqltools:dbc) (format select type (where/series s) order limit)))

;; ~~~~
;; 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`







>
>







 







|











|







43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
...
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
         "dust.rkt")

;; ~~~ Provides ~~~

(provide spell-of-summoning!
         crystalize-article!
         article-plain-title
         list/articles
         list/articles+notes
         list-short/articles
         list-full/articles
         list-full/articles+notes
         unfence
         preheat-series!)

;; ~~~ Private use ~~~
................................................................................
           (format "WHERE `series_pagenode` IN ~a" (list->sql-values series)))]
        [(string? s)
         (format "WHERE `series_pagenode` IS \"~a/~a.html\"" series-folder s)]
        [(equal? s #t)
         (format "WHERE `series_pagenode` IS \"~a\"" (here-output-path))]
        [else ""]))

;; Return a combined list of articles and notes sorted by date
(define (list/articles+notes type #:series [s #t] #:limit [limit -1] [order "DESC"])
  (define select #<<@@@@@
     SELECT `~a` FROM
       (SELECT `~a`, `published` FROM `articles`
        UNION SELECT
        `~a`,`date` AS `published` FROM `notes`
        ~a ORDER BY `published` ~a LIMIT ~a)
@@@@@
    )
  (query-list (sqltools:dbc) (format select type type type (where/series s) order limit)))

;; Return a list of articles only, sorted by date
(define (list/articles type #:series [s #t] #:limit [limit -1] [order "DESC"])
  (define select "SELECT `~a` FROM `articles` ~a ORDER BY `published` ~a LIMIT ~a")
  (query-list (sqltools:dbc) (format select type (where/series s) order limit)))

;; ~~~~
;; 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`

Modified snippets-html.rkt from [f7e3f3ed] to [1579b523].

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
...
157
158
159
160
161
162
163
















































;; -------------------------------------------------------------------------

;; Provides functions for displaying content in HTML templates.
(require pollen/core
         pollen/template
         pollen/decode
         racket/string


         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-title
         html$-note-contents
         html$-note-listing-full
         html$-note-in-article
         html$-notes-section)


(define (html$-page-head [title #f])
  ◊string-append{<head>
 <title>◊if[title title ""] </title>
 <meta charset="utf-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="stylesheet" type="text/css" href="/web-extra/martin.css">
................................................................................
 </div>})

(define (html$-notes-section note-htmls)
  ◊string-append{<div class="further-notes" id="furthernotes">
 <h2>Further Notes</h2>
 ◊(apply string-append note-htmls)
 </div>})























































>
>







 







|
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
...
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
;; -------------------------------------------------------------------------

;; Provides functions for displaying content in HTML templates.
(require pollen/core
         pollen/template
         pollen/decode
         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-title
         html$-note-contents
         html$-note-listing-full
         html$-note-in-article
         html$-notes-section
         html$-paginate-navlinks)

(define (html$-page-head [title #f])
  ◊string-append{<head>
 <title>◊if[title title ""] </title>
 <meta charset="utf-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="stylesheet" type="text/css" href="/web-extra/martin.css">
................................................................................
 </div>})

(define (html$-notes-section note-htmls)
  ◊string-append{<div class="further-notes" id="furthernotes">
 <h2>Further Notes</h2>
 ◊(apply string-append note-htmls)
 </div>})

;; (private) Returns HTML for a list-item link to a particular page in a set of numbered pages
(define (html$-paginate-link basename pagenum [linktext (format "p.&numsp;~a" (number->string pagenum))] [class ""])
  (define cstr (if (non-empty-string? class) (format " class=\"~a\"" class) ""))
  (format "<li~a><a href=\"/~a-pg~a.html\">~a</a></li>" cstr basename pagenum linktext))

;; Returns HTML for a series of list items with links to numbered pages
(define (html$-paginate-navlinks pagenum pagecount basename)
  (define slots 9)
  (define on-first-group? (<= pagenum (- slots 4)))
  (define on-last-group? (>= pagenum (- pagecount slots -4)))
  (define only-one-group? (<= pagecount slots))
  (define group-start (- pagenum (quotient (- slots 4) 2))) ; not always used!
  (define page-func (curry html$-paginate-link basename))

  (define page-group-syms
    (cond [only-one-group?
           `(,@(range 1 (+ 1 pagecount)))]
          [on-first-group?
           `(,@(range 1 (min (+ 1 pagecount) (- slots 1))) "..." ,pagecount)]
          [on-last-group?
           `(1 "..." ,@(range (- pagecount slots -3) (+ pagecount 1)))]
          [else
           `(1
             "..."
             ,@(range group-start (min (+ 1 pagecount) (+ group-start (- slots 4))))
             "..."
             ,pagecount)]))
 
  (define page-group
    (for/list ([psym (in-list page-group-syms)])
      (cond
        [(and (number? psym) (equal? psym pagenum))
         (format "<li class=\"current-page\">~a</li>" psym)]
        [(number? psym) (page-func psym)]
        [else "<li>&#8230;</li>"])))
  
  (define prev-link
    (if (eq? 1 pagenum)
        "<li class=\"nav-text inactive-link\">&larr;Newer</li>"
        (page-func (- pagenum 1) "&larr;&thinsp;Newer" "nav-text")))

  (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)))

Modified web-extra/martin.css.pp from [8dbe8d2a] to [f15478ac].

179
180
181
182
183
184
185
























































186
187
188
189
190
191
192
...
583
584
585
586
587
588
589


590
591
592
593
594
595
596
    }
    @keyframes hilite {
        0% {background: transparent;}
        10% {background: #feffc1;}
        100% {background: transparent;}
    }

























































    /* On mobile, an <ARTICLE> is just a box. Later we'll do fancy stuff if grid support is detected. */
    article {
        margin: ◊x-lineheight[2] 0 ◊x-lineheight[1] 0;
        padding-top: ◊x-lineheight[1];
    }
    article:first-of-type {
        margin-top: ◊x-lineheight[1];
................................................................................
    main {
        margin: 0 auto;
        padding-left: 1rem;
        padding-right: 1rem;
        width: 30rem;
        max-width: 90%;
    }



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








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







 







>
>







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
...
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
    }
    @keyframes hilite {
        0% {background: transparent;}
        10% {background: #feffc1;}
        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: gray;
    }

    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. */
    article {
        margin: ◊x-lineheight[2] 0 ◊x-lineheight[1] 0;
        padding-top: ◊x-lineheight[1];
    }
    article:first-of-type {
        margin-top: ◊x-lineheight[1];
................................................................................
    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;
        }