Skip to content

Commit fe3d386

Browse files
committed
Eliminate special renderer support for footnotes
This commit reimplements footnotes “properly,” as Scribble references, rather than handling them separately in `footnotes-render-mixin`.
1 parent ef85796 commit fe3d386

File tree

6 files changed

+198
-133
lines changed

6 files changed

+198
-133
lines changed

blog/build/render/scribble.rkt

Lines changed: 36 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
threading
2424
(only-in xml write-xexpr xexpr/c)
2525

26+
"../../lang/metadata.rkt"
2627
"../../lang/post-language.rkt"
2728
"../../paths.rkt"
2829
"../metadata.rkt"
@@ -37,7 +38,6 @@
3738
(implementation?/c render<%>))])
3839

3940
blog-render-mixin
40-
footnotes-render-mixin
4141
pygments-render-mixin)
4242

4343
;; -----------------------------------------------------------------------------
@@ -59,7 +59,7 @@
5959
(for/list ([(k v) (in-hash attrs)])
6060
(list k v)))
6161

62-
(define (style->tag-name+attributes s)
62+
(define (style->tag-name+attributes s ri)
6363
(for/fold ([tag-name #f]
6464
[attrs (if (string? (style-name s))
6565
(hasheq 'class (style-name s))
@@ -72,6 +72,9 @@
7272
(values (string->symbol name) attrs)]
7373
[(attributes attrs*)
7474
(values tag-name (attributes-union attrs (make-immutable-hasheq attrs*)))]
75+
[(link-target tag)
76+
(values tag-name (attributes-union attrs
77+
(hasheq 'id (tag->anchor-name (resolve-tag tag ri)))))]
7578
[_
7679
(values tag-name attrs)])))
7780

@@ -89,7 +92,7 @@
8992
(serializable-struct blog-page (title path) #:transparent
9093
#:guard (struct-guard/c content? site-path?))
9194
(serializable-struct blog-page-anchor (title path anchor) #:transparent
92-
#:guard (struct-guard/c content? site-path? string?))
95+
#:guard (struct-guard/c (or/c content? #f) site-path? string?))
9396

9497
(define header-depth->html-tag
9598
(match-lambda
@@ -217,14 +220,29 @@
217220
(get-current-site-path)
218221
(tag->anchor-name (add-current-tag-prefix key)))))))
219222

223+
(define/public (collect-link-target tag ci)
224+
(define key (generate-tag tag ci))
225+
(collect-put! ci key (blog-page-anchor #f
226+
(get-current-site-path)
227+
(tag->anchor-name (add-current-tag-prefix key)))))
228+
229+
(define/override (collect-nested-flow i ci)
230+
(cond
231+
[(findf link-target? (style-properties (nested-flow-style i)))
232+
=> (λ (target) (collect-link-target (link-target-tag target) ci))])
233+
(super collect-nested-flow i ci))
234+
235+
(define/override (collect-paragraph i ci)
236+
(cond
237+
[(findf link-target? (style-properties (paragraph-style i)))
238+
=> (λ (target) (collect-link-target (link-target-tag target) ci))])
239+
(super collect-paragraph i ci))
240+
220241
(define/override (collect-target-element i ci)
221-
(define key (generate-tag (target-element-tag i) ci))
222242
(when (redirect-target-element? i)
223243
(raise-arguments-error 'collect-target-element "redirect targets not supported"
224244
"element" i))
225-
(collect-put! ci key (blog-page-anchor #f
226-
(get-current-site-path)
227-
(tag->anchor-name (add-current-tag-prefix key)))))
245+
(collect-link-target (target-element-tag i) ci))
228246

229247
(define/override (resolve-content i d ri)
230248
(cond
@@ -274,17 +292,20 @@
274292
,@(append-map (λ~> (render-part ri)) (part-parts part))])
275293

276294
(define/override (render-nested-flow i part ri starting-item?)
277-
`[(blockquote ,@(super render-nested-flow i part ri starting-item?))])
295+
(define-values [tag-name attrs] (style->tag-name+attributes (nested-flow-style i) ri))
296+
`[(,(or tag-name 'blockquote)
297+
,(attributes->list attrs)
298+
,@(super render-nested-flow i part ri starting-item?))])
278299

279300
(define/override (render-paragraph e part ri)
280-
(define-values [tag-name attrs] (style->tag-name+attributes (paragraph-style e)))
301+
(define-values [tag-name attrs] (style->tag-name+attributes (paragraph-style e) ri))
281302
`[(,(or tag-name 'p)
282303
,(attributes->list attrs)
283304
,@(super render-paragraph e part ri))])
284305

285306
(define/override (render-itemization e part ri)
286307
(define style (itemization-style e))
287-
(define-values [tag-name attrs] (style->tag-name+attributes style))
308+
(define-values [tag-name attrs] (style->tag-name+attributes style ri))
288309
`[(,(or tag-name
289310
(if (eq? (style-name style) 'ordered) 'ol 'ul))
290311
,(attributes->list attrs)
@@ -305,7 +326,7 @@
305326
[(string? elem) (list elem)]
306327
[else
307328
(define style (normalize-element-style (if (element? elem) (element-style elem) #f)))
308-
(define-values [tag-name attrs] (style->tag-name+attributes style))
329+
(define-values [tag-name attrs] (style->tag-name+attributes style ri))
309330

310331
(define (wrap-for-style rendered #:attrs [attrs attrs])
311332
; First, we determine what element wrappers are needed by the style.
@@ -361,9 +382,6 @@
361382
[(link-element _ _ tag)
362383
(define indirect? (memq 'indirect-link (style-properties style)))
363384
(define dest (and (not indirect?) (resolve-get part ri tag)))
364-
(unless (or dest indirect?)
365-
(error 'render "unknown link destination\n tag: ~e" tag))
366-
367385
(define href
368386
(match dest
369387
; racket doc reference
@@ -388,76 +406,6 @@
388406

389407
(super-new)))
390408

391-
;; Adds support for footnote definitions and references to a renderer.
392-
;; Specifically, the renderer is adjusted as follows:
393-
;;
394-
;; * An `element` with a `footnote-reference` style property is treated as a
395-
;; /footnote references/. The content of footnote references is ignored, and
396-
;; the element is replaced with a link to the corresponding footnote
397-
;; definition.
398-
;;
399-
;; * A `nested-flow` with a `footnote-definition` style property is treated as
400-
;; a /footnote definition/. During the render pass, footnote definitions are
401-
;; removed from the document flow in `render-flow` and rendered separately.
402-
;; The collected footnote definitions can be retrieved using the
403-
;; `get-rendered-footnote-definitions` method in a subclassing renderer.
404-
;;
405-
;; * The `render-footnote-definition` method can be overridden to customize the
406-
;; way footnote definitions are rendered.
407-
(define (footnotes-render-mixin %)
408-
(class/hijack %
409-
#:hijack-methods [render-one]
410-
411-
(define footnote-ids '())
412-
(define footnote-definitions '())
413-
414-
(define/override (start-collect ds fns ci)
415-
(set! footnote-ids '())
416-
(super start-collect ds fns ci))
417-
418-
(define/override (collect-nested-flow b ci)
419-
(cond
420-
[(findf footnote-definition? (style-properties (nested-flow-style b)))
421-
=> (λ (defn) (set! footnote-ids (cons (footnote-definition-note-id defn) footnote-ids)))])
422-
(super collect-nested-flow b ci))
423-
424-
(define/public (get-rendered-footnote-definitions)
425-
(reverse footnote-definitions))
426-
427-
(define/public-final (super-render-one part ri output-file)
428-
(super render-one part ri output-file))
429-
(define/overment (render-one part ri output-file)
430-
(set! footnote-ids (reverse footnote-ids))
431-
(set! footnote-definitions '())
432-
(inner/hijack render-one part ri output-file))
433-
434-
(define/override (render-content elem part ri)
435-
(match elem
436-
[(element (style #f (list (footnote-reference note-id))) '())
437-
(define note-index (and~> (index-of footnote-ids note-id) add1))
438-
`[(sup (a ([href ,(if note-index
439-
(~a "#footnote-" note-index)
440-
"#")])
441-
,(or (~a note-index) "???")))]]
442-
[_ (super render-content elem part ri)]))
443-
444-
(define/override (render-flow blocks part ri starting-item?)
445-
(define-values [footnote-blocks other-blocks]
446-
(partition (λ (block) (and (nested-flow? block)
447-
(~>> (nested-flow-style block)
448-
style-properties
449-
(memf footnote-definition?))))
450-
blocks))
451-
(for ([footnote-block (in-list footnote-blocks)])
452-
(define rendered (render-footnote-definition footnote-block part ri))
453-
(set! footnote-definitions (cons rendered footnote-definitions)))
454-
(super render-flow other-blocks part ri starting-item?))
455-
456-
(define/public (render-footnote-definition block part ri)
457-
(render-flow (nested-flow-blocks block) part ri #t))
458-
459-
(super-new)))
460-
461409
;; Recognizes `paragraph`s and `element`s with a `pygments-content` style
462410
;; property and replaces them with the result of running Pygments on the content
463411
;; in the property. Body content and other style properties are ignored.
@@ -501,11 +449,10 @@
501449

502450
;; The renderer used for actual blog posts, with all the bells and whistles.
503451
(define blog-post-render%
504-
(class (pygments-render-mixin (footnotes-render-mixin (blog-render-mixin base-render%)))
452+
(class (pygments-render-mixin (blog-render-mixin base-render%))
505453
(inherit render-content
506454
render-flow
507-
render-part
508-
get-rendered-footnote-definitions)
455+
render-part)
509456

510457
; We don’t render blog posts directly to HTML because we need to be able to
511458
; render them in different ways on different pages, so we generate `.info`
@@ -521,19 +468,14 @@
521468
(define props (style-properties (part-style part)))
522469
(define title-str (content->string (strip-aux (part-title-content part)) this part ri))
523470
(define title-content (render-content (part-title-content part) part ri))
524-
(define body-main-content (append (render-flow (part-blocks part) part ri #t)
471+
(define body-content (append (render-flow (part-blocks part) part ri #t)
525472
(append-map (λ~> (render-part ri)) (part-parts part))))
526-
(define footnotes-block
527-
`(div ([class "footnotes"])
528-
(ol ,@(for/list ([footnote-element (in-list (get-rendered-footnote-definitions))]
529-
[footnote-index (in-naturals 1)])
530-
`(li ([id ,(~a "footnote-" footnote-index)]) ,@footnote-element)))))
531473
(define post
532474
(rendered-post title-str
533475
title-content
534476
(get-required-style-prop post-date? props)
535477
(post-tags-tags (get-required-style-prop post-tags? props))
536-
(append body-main-content (list footnotes-block))))
478+
body-content))
537479
(write (serialize post))
538480
post)
539481

blog/build/render/util.rkt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@
4242
(define tag->anchor-name
4343
(match-lambda
4444
[(literal-anchor anchor-name) anchor-name]
45-
[(cons 'part tag) (tag->anchor-name tag)]
46-
[(? list? elements)
45+
[(list 'part tag) (to-slug (~a tag))]
46+
[(list sym (cons 'prefixable tag)) (tag->anchor-name (list sym tag))]
47+
[(list sym tag)
4748
; This anchor naming scheme does not in any way create unique anchors, but
4849
; that should be okay for internal references in this use case, and having
4950
; pretty URLs is a nice feature.
50-
(to-slug (string-join (map ~a elements) " "))]))
51+
(to-slug (~a sym tag))]))

blog/lang/footnote.rkt

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#lang racket/base
2+
3+
(require (for-syntax racket/base
4+
racket/syntax)
5+
racket/contract
6+
racket/format
7+
racket/list
8+
racket/match
9+
scribble/core
10+
scribble/decode
11+
scribble/html-properties
12+
syntax/parse/define
13+
threading
14+
15+
"metadata.rkt")
16+
17+
(provide define-footnote
18+
(contract-out
19+
[make-footnote-tag (->* [string?]
20+
[#:post (or/c string? #f)
21+
#:tag-prefixes (listof string?)]
22+
tag?)]
23+
[footnote-ref (-> string? content?)]
24+
[footnote-decl (-> string? pre-flow? ... part-collect-decl?)]
25+
[footnotes-section (-> part?)]))
26+
27+
(define (make-footnote-tag tag-str
28+
#:post [post-path #f]
29+
#:tag-prefixes [tag-prefixes '()])
30+
`(footnote ,(taglet-add-prefix (cons 'prefixable
31+
(if post-path
32+
(cons post-path tag-prefixes)
33+
tag-prefixes))
34+
tag-str)))
35+
36+
(define footnote-refs-key (gensym 'footnote-refs))
37+
(define footnote-flows-key (gensym 'footnote-flows))
38+
39+
(define (footnote-ref tag)
40+
(traverse-element
41+
(λ (get set)
42+
(define refs (get footnote-refs-key '()))
43+
(define number (cond
44+
[(index-of refs tag)
45+
=> (λ (idx) (- (length refs) idx))]
46+
[else
47+
(set footnote-refs-key (cons tag refs))
48+
(add1 (length refs))]))
49+
(link-element (style 'superscript '())
50+
(~a number)
51+
(make-footnote-tag tag)))))
52+
53+
(define (footnote-decl tag . pre-flows)
54+
(part-collect-decl
55+
(element
56+
#f
57+
(traverse-element
58+
(λ (get set)
59+
(set footnote-flows-key
60+
(cons (cons tag (decode-flow pre-flows))
61+
(get footnote-flows-key '())))
62+
'())))))
63+
64+
(define (footnotes-section)
65+
(define (generate get set)
66+
(define refs (get footnote-refs-key '()))
67+
(define-values [referenced-flows other-flows]
68+
(partition (λ~> car (member refs)) (get footnote-flows-key '())))
69+
70+
(define (build-item number tag+blocks)
71+
(nested-flow
72+
(style #f (list* (alt-tag "li")
73+
(link-target (make-footnote-tag (car tag+blocks)))
74+
(if number
75+
(list (attributes `([value . ,(~a number)])))
76+
'())))
77+
(cdr tag+blocks)))
78+
79+
; Build the footnote items, starting with the ones that were actually
80+
; referenced. If a footnote was referenced that doesn’t actually exist, we
81+
; want to skip its number in the output list, which requires we set the
82+
; `value` attribute on the generated `li`. The `skipped?` argument tracks
83+
; whether or not this is necessary for the next `li`.
84+
(define items
85+
(let loop ([refs (reverse refs)]
86+
[number 1]
87+
[skipped? #f])
88+
(match refs
89+
[(cons ref refs)
90+
(match (assoc ref referenced-flows)
91+
[(? pair? flow)
92+
(cons (build-item (and skipped? number) flow)
93+
(loop refs (add1 number) #f))]
94+
[#f (loop refs (add1 number) #t)])]
95+
96+
; We’re done outputting the referenced footnotes. If there were any
97+
; unreferenced ones, just tack them onto the end.
98+
['() (match other-flows
99+
['() '()]
100+
[(cons flow flows)
101+
(cons (build-item (and skipped? number) flow)
102+
(map (λ~>> (build-item #f)) flows))])])))
103+
104+
(nested-flow (style #f (list (alt-tag "ol")
105+
(attributes '([class . "footnotes"]))))
106+
items))
107+
(part #f
108+
(list `(part ,(generated-tag)))
109+
#f
110+
(style #f '(unnumbered hidden toc-hidden))
111+
'()
112+
(list (traverse-block generate))
113+
'()))
114+
115+
(define-syntax-parser define-footnote
116+
[(_ ref-id:id {~optional {~seq #:tag tag-e}} pre-flow ...)
117+
#:declare tag-e (expr/c #'string? #:name "tag")
118+
#:with note-ref-id (format-id #'ref-id "note:~a" #'ref-id #:subs? #t)
119+
#`(begin
120+
(define note-tag {~? tag-e.c '#,(symbol->string (syntax-e #'ref-id))})
121+
(define (note-ref-id) (footnote-ref note-tag))
122+
(footnote-decl note-tag pre-flow ...))])

0 commit comments

Comments
 (0)