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

Overview
Comment:Add keyword index page, make index links bidirectional (addresses [5daecde7])
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: ae6010c03735fca282826d874a0b3f68a296a3fd15a4809f5a28df83f7d66eef
User & Date: joel on 2019-05-15 01:13:16
Other Links: manifest | tags
Context
2019-05-19
22:11
Merge license changes check-in: bfde8715 user: joel tags: trunk
20:24
Merge updates from trunk check-in: cf26929b user: joel tags: licensing
2019-05-15
01:13
Add keyword index page, make index links bidirectional (addresses [5daecde7]) check-in: ae6010c0 user: joel tags: trunk
2019-05-10
12:40
Mockup → prototype! check-in: 6034ec3d user: joel tags: trunk
Changes

Modified crystalize.rkt from [17c941d5] to [e8e2afa2].

49
50
51
52
53
54
55

56
57
58
59
60
61
62
...
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
         article-plain-title
         list/articles
         list/articles+notes
         listing<>-short/articles
         listing<>-full/articles
         listing<>-full/articles+notes
         unfence

         preheat-series!)

;; ~~~ Private use ~~~

(define DBFILE (build-path (current-project-root) "vitreous.sqlite"))

;; Since the DB exists to serve as a high-speed cache, the tables are constructed so that
................................................................................
         [title-plain (tx-strs title-tx)]
         [series-node (series-pagenode)]
         [header      (html$-article-open pagenode title-specified? title-tx pubdate)]
         [footertext (make-article-footertext pagenode series-node disposition disp-note-id (length note-txprs))]
         [footer (html$-article-close footertext)]
         [notes-section-html (crystalize-notes! pagenode title-plain note-txprs)])

    (crystalize-index-entries! pagenode body-txpr)

    ;; Values must come in the order defined in table_article_fields
    (define article-record
      (list (symbol->string pagenode)
            title-plain
            title-html
            (bool->int title-specified?)







>







 







|







49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
...
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
         article-plain-title
         list/articles
         list/articles+notes
         listing<>-short/articles
         listing<>-full/articles
         listing<>-full/articles+notes
         unfence
         sqltools:dbc
         preheat-series!)

;; ~~~ Private use ~~~

(define DBFILE (build-path (current-project-root) "vitreous.sqlite"))

;; Since the DB exists to serve as a high-speed cache, the tables are constructed so that
................................................................................
         [title-plain (tx-strs title-tx)]
         [series-node (series-pagenode)]
         [header      (html$-article-open pagenode title-specified? title-tx pubdate)]
         [footertext (make-article-footertext pagenode series-node disposition disp-note-id (length note-txprs))]
         [footer (html$-article-close footertext)]
         [notes-section-html (crystalize-notes! pagenode title-plain note-txprs)])

    (crystalize-index-entries! pagenode doc) ; Note the original doc is used here

    ;; Values must come in the order defined in table_article_fields
    (define article-record
      (list (symbol->string pagenode)
            title-plain
            title-html
            (bool->int title-specified?)

Added keyword-index.rkt version [9aa8d227].











































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
#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 an HTML page containing the keyword index for all ◊index entries in
;; the articles, by pulling them out of the SQLite cache DB.

(require racket/match
         racket/list
         racket/file
         db/base
         net/uri-codec
         pollen/template)

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

(provide main)

;; Get the index entries from the SQLite cache, return them as a list of vectors
(define (fetch-entries)
  (define q
    ◊string-append{
 SELECT entry, subentry, a.rowid, "/" || k.pagenode || "#" || anchor AS href, title_plain
 FROM keywordindex k INNER JOIN articles a
 ON a.pagenode = k.pagenode
 ORDER BY entry COLLATE NOCASE ASC;})
  (query-rows (sqltools:dbc) q))

;; Convert a vector (row) into a txexpr representing a link to the original article
(define (make-link row)
  `(a [[href ,(vector-ref row 3)]
       [title ,(vector-ref row 4)]]
      ,(number->string (vector-ref row 2))))

;(require sugar/debug)

;; Convert a list of vectors from the cache DB into a list of the form:
;; (list (cons FIRST-LETTER
;;             (list (cons KEYWORD
;;                         (list LINKS ...))
;;                   ...))
;;       ...)
(define (group-entries data)
  (define collated-list
    (for/fold ([entry-table null]
               #:result (reverse entry-table))
              ([row (in-list data)])
      (define this-entry (vector-ref row 0))
      (cond [(and (not (null? entry-table))
                  (string-ci=? (first (first entry-table)) this-entry))
             (match-define (cons (list last-entry last-list) rest-entries) entry-table)
             (cons `(,last-entry ,(append last-list (list (make-link row)))) rest-entries)]
            [else
             (cons `(,this-entry ,(list (make-link row)))
                   entry-table)])))
  (define (first-letter entry)
    (char-upcase (first (string->list (first entry)))))
  
  (for/list ([letter-group (in-list (group-by first-letter collated-list))])
    (list (first-letter (first letter-group)) letter-group)))

(define (entry+links->txexpr entry)
  (match-define (list entry-word links) entry)
  `(li [[id ,(uri-encode (string-downcase entry-word))]]
       ,entry-word nbsp
       ,@(add-between links ", ")))

;; Return the complete HTML for the keyword index. Each letter group begins with a heading for the
;; letter, followed by a definition list for its entries.
(define (html$-index entries)
  (define groups
    (for/list ([letter-group (in-list entries)])
      (match-define (list letter-char entries) letter-group)
      `(section (h2 ,(list->string (list letter-char)))
                (ul ,@(map entry+links->txexpr entries)))))
  (apply string-append (map ->html groups)))

(define (html$-keywordindex-page the-index)
  ◊string-append{
 <!DOCTYPE html>
 <html lang="en">
 ◊html$-page-head{The Local Yarn: Keyword Index}
 ◊html$-page-body-open[]

 <div id="keywordindex">
 ◊the-index
 </div>
 ◊html$-page-body-close[]
 </html>})

(define (main)
  (spell-of-summoning!) ; Turn on DB
  (displayln "Writing keyword-index.html…")
  (display-to-file (html$-keywordindex-page (html$-index (group-entries (fetch-entries))))
                   "keyword-index.html"
                   #:mode 'text
                   #:exists 'replace))

Modified makefile from [62911e27] to [5a376d4e].

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
..
45
46
47
48
49
50
51
52
53
54
55



56
57
58
59
60
61
62
series-sources  := $(wildcard series/*.poly.pm)
series-html     := $(patsubst %.poly.pm, %.html, $(series-sources))

# ~~~ Rules ~~
#

# The order of these dependencies is important. They will be processed left to right.
web: _article_htmls.mark $(articles-html) $(series-html) blog-pg1.html
web: ## Rebuild all web content (not PDFs)

# The file article_htmls.mark is a zero-byte file that serves only as a marker. If it is older than
# any of its dependencies (or missing) all of the articles will be rebuilt. Its dependencies are
# also on the Pollen cache watchlist (see pollen.rkt)
_article_htmls.mark: $(core-files) $(html-deps) template.html.p
	raco pollen setup articles/
................................................................................
	raco pollen render $@

# 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 $@

# This target will also rebuild pg2, pg3, etc.
blog-pg1.html: $(core-files) $(html-deps) $(articles-html) blog.rkt
	rm -f blog*.html
	racket -tm blog.rkt




spritz: ## Clear Pollen and Scribble cache
	rm -rf compiled code-docs/compiled articles/compiled series/compiled
	fossil clean code-docs/

publish: check-env
publish: ## Sync all HTML and PDF stuff to the public web server (does not rebuild any files)







|







 







|



>
>
>







21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
..
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
series-sources  := $(wildcard series/*.poly.pm)
series-html     := $(patsubst %.poly.pm, %.html, $(series-sources))

# ~~~ Rules ~~
#

# The order of these dependencies is important. They will be processed left to right.
web: _article_htmls.mark $(articles-html) $(series-html) blog-pg1.html keyword-index.html
web: ## Rebuild all web content (not PDFs)

# The file article_htmls.mark is a zero-byte file that serves only as a marker. If it is older than
# any of its dependencies (or missing) all of the articles will be rebuilt. Its dependencies are
# also on the Pollen cache watchlist (see pollen.rkt)
_article_htmls.mark: $(core-files) $(html-deps) template.html.p
	raco pollen setup articles/
................................................................................
	raco pollen render $@

# 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 $@

# 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

keyword-index.html: $(core-files) $(html-deps) $(articles-html) keyword-index.rkt
	racket -tm keyword-index.rkt

spritz: ## Clear Pollen and Scribble cache
	rm -rf compiled code-docs/compiled articles/compiled series/compiled
	fossil clean code-docs/

publish: check-env
publish: ## Sync all HTML and PDF stuff to the public web server (does not rebuild any files)

Modified tags-html.rkt from [5a9bf33f] to [43d9d1e5].

116
117
118
119
120
121
122

123
124
125
126
127
128
129
  (define file (or (assoc 'filename attrs) ""))
  (define codeblock `(pre [[class "code"]] (code ,@elems)))
  (cond [(string>? file "") `(@ (div [[class "listing-filename"]] 128196 " " ,file) ,codeblock)]
        [else codeblock]))

(define (html-index . elems)
  `(a [[id ,(here-id (list "_idx-" (uri-encode (car elems))))]

       [data-index-entry ,(car elems)]
       [class "index-link"]]
      ,@(cdr elems)))

(define (html-say . elems)
  `(@ (dt ,(car elems) (span [[class "x"]] ": ")) (dd ,@(cdr elems))))








>







116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
  (define file (or (assoc 'filename attrs) ""))
  (define codeblock `(pre [[class "code"]] (code ,@elems)))
  (cond [(string>? file "") `(@ (div [[class "listing-filename"]] 128196 " " ,file) ,codeblock)]
        [else codeblock]))

(define (html-index . elems)
  `(a [[id ,(here-id (list "_idx-" (uri-encode (car elems))))]
       [href ,(string-append "/keyword-index.html#" (uri-encode (string-downcase (car elems))))]
       [data-index-entry ,(car elems)]
       [class "index-link"]]
      ,@(cdr elems)))

(define (html-say . elems)
  `(@ (dt ,(car elems) (span [[class "x"]] ": ")) (dd ,@(cdr elems))))

Modified web-extra/martin.css.pp from [48a09b2d] to [96d16067].

185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
...
638
639
640
641
642
643
644
























645
646
647
648
649
650
651
        color: inherit;
    }
    
    span.links-footnote {
        display: inline-block; /* allows keyframe animation to work */
    }
    
    li:target, a:target, span.links-footnote:target {
        animation: hilite 2.5s;
    }
    @keyframes hilite {
        0% {background: transparent;}
        10% {background: #feffc1;}
        100% {background: transparent;}
    }
................................................................................
    div.article-list-date {
        color: #999;
    }

    div.article-list-title {
        font-size: 1.2rem;
    }

























    /* 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. */







|







 







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







185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
...
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
675
        color: inherit;
    }
    
    span.links-footnote {
        display: inline-block; /* allows keyframe animation to work */
    }
    
    :target {
        animation: hilite 2.5s;
    }
    @keyframes hilite {
        0% {background: transparent;}
        10% {background: #feffc1;}
        100% {background: transparent;}
    }
................................................................................
    div.article-list-date {
        color: #999;
    }

    div.article-list-title {
        font-size: 1.2rem;
    }
    
    /* ******* (Mobile first) Keyword Index styling *******
     */

    #keywordindex {
        column-width: 7rem;
        margin-top: ◊x-lineheight[2];
    }
    
    #keywordindex section {
        -webkit-column-break-inside: avoid; /* Chrome, Safari, Opera */
        page-break-inside: avoid; /* Firefox */
        break-inside: avoid; /* IE 10+ */
    }

    #keywordindex h2 {
        margin: 0;
    }

    #keywordindex ul {
        margin-top: 0;
        list-style-type: none;
        padding: 0;
    }

    /* 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. */