Overview
Comment: | Add blog |
---|---|
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
8145bdb72882bf39c50903fde11b0551 |
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 | "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 ~~~ | > > | 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | "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 ~~~ |
︙ | ︙ | |||
172 173 174 175 176 177 178 | (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 ""])) | | | | 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 | (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 | ;; ------------------------------------------------------------------------- ;; 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 | > > | > | 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 | ;; ------------------------------------------------------------------------- ;; 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"> |
︙ | ︙ | |||
157 158 159 160 161 162 163 | </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>}) | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | </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. ~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>…</li>"]))) (define prev-link (if (eq? 1 pagenum) "<li class=\"nav-text inactive-link\">←Newer</li>" (page-func (- pagenum 1) "← Newer" "nav-text"))) (define next-link (if (eq? pagecount pagenum) "<li class=\"nav-text inactive-link\">Older→</li>" (page-func (+ pagenum 1) "Older →" "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 | } @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]; | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | } @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]; |
︙ | ︙ | |||
583 584 585 586 587 588 589 590 591 592 593 594 595 596 | main { margin: 0 auto; padding-left: 1rem; padding-right: 1rem; width: 30rem; max-width: 90%; } @supports (grid-area: auto) { main { width: 42rem; background: none; } | > > | 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 | 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; } |
︙ | ︙ |