datalog-theme 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +175 -0
  3. data/CITATION.cff +2 -2
  4. data/README.md +26 -19
  5. data/_data/cdn-integrity.yml +0 -30
  6. data/_includes/analytics/dashboard.html +3 -1
  7. data/_includes/components/api-function.html +20 -1
  8. data/_includes/components/author-bio.html +2 -2
  9. data/_includes/components/enhanced-code-block.html +1 -1
  10. data/_includes/components/hero.html +19 -1
  11. data/_includes/csp-meta.html +115 -11
  12. data/_includes/footer.html +19 -20
  13. data/_includes/head.html +61 -29
  14. data/_includes/header/navigation.html +12 -15
  15. data/_includes/header.html +29 -20
  16. data/_includes/layouts/default/article.html +5 -1
  17. data/_includes/meta/math-config.html +27 -1
  18. data/_includes/meta/schema.html +5 -2
  19. data/_includes/meta/scripts-loader.html +16 -31
  20. data/_includes/post/related-posts.html +4 -7
  21. data/_includes/scripts.html +1 -9
  22. data/_includes/search/index-data.json +131 -0
  23. data/_includes/search/page.html +136 -0
  24. data/_layouts/dataset.html +1 -0
  25. data/_layouts/default.html +16 -8
  26. data/_layouts/home.html +6 -0
  27. data/_layouts/notebook.html +1 -0
  28. data/_layouts/package.html +1 -0
  29. data/_layouts/portfolio.html +1 -0
  30. data/_layouts/post.html +20 -5
  31. data/_layouts/project.html +2 -1
  32. data/_plugins/analytics_dashboard.rb +9 -3
  33. data/_plugins/config_validator.rb +12 -7
  34. data/_plugins/csp_generator.rb +18 -28
  35. data/_plugins/datalog_bibliography.rb +9 -7
  36. data/_plugins/datalog_comments.rb +8 -5
  37. data/_plugins/datalog_slides.rb +9 -8
  38. data/_plugins/i18n.rb +7 -8
  39. data/_plugins/image_optimizer.rb +17 -6
  40. data/_plugins/math_preprocessor.rb +36 -3
  41. data/_plugins/notebook_converter.rb +23 -43
  42. data/_plugins/plugin_loader.rb +3 -1
  43. data/_plugins/publications_generator.rb +8 -2
  44. data/_plugins/rouge_highlight_filter.rb +42 -0
  45. data/_plugins/search_code_blocks.rb +30 -0
  46. data/_plugins/search_normalizer.rb +15 -53
  47. data/_plugins/search_pages.rb +72 -0
  48. data/_sass/_academic-dashboard.scss +262 -0
  49. data/_sass/_base.scss +19 -1
  50. data/_sass/_components.scss +77 -1144
  51. data/_sass/_features.scss +17 -0
  52. data/_sass/_font-fallbacks.scss +43 -0
  53. data/_sass/_header.scss +10 -0
  54. data/_sass/_layout.scss +18 -12
  55. data/_sass/_mathematical.scss +1 -1
  56. data/_sass/_notebooks.scss +322 -0
  57. data/_sass/_open-science-badges.scss +56 -0
  58. data/_sass/{_phase1-enhancements.scss → _post-components.scss} +46 -1
  59. data/_sass/_search-page.scss +530 -0
  60. data/_sass/_search.scss +47 -1
  61. data/_sass/_syntax-highlighting.scss +212 -97
  62. data/_sass/_theme.scss +42 -18
  63. data/_sass/_typography.scss +14 -0
  64. data/_sass/_variables.scss +7 -4
  65. data/assets/css/main.scss +14 -0
  66. data/assets/img/hero-detail-640.webp +0 -0
  67. data/assets/img/hero-detail.webp +0 -0
  68. data/assets/img/social-card.png +0 -0
  69. data/assets/js/dist/academic.js +1 -0
  70. data/assets/js/dist/analytics-dashboard.js +1 -0
  71. data/assets/js/dist/chunks/chunk-225H5YXE.js +1 -0
  72. data/assets/js/dist/core.js +1 -0
  73. data/assets/js/dist/loader.js +1 -0
  74. data/assets/js/dist/math.js +1 -0
  75. data/assets/js/dist/notebook.js +1 -0
  76. data/assets/js/dist/search.js +1 -0
  77. data/assets/js/dist/visualizations.js +11 -0
  78. data/assets/js/loader.js +3 -1
  79. data/datalog-theme.gemspec +43 -22
  80. data/lib/datalog/cli.rb +53 -15
  81. data/lib/datalog/plugin_system/dependency_resolver.rb +0 -2
  82. data/lib/datalog/theme/package.rb +57 -0
  83. data/lib/datalog/theme/repository_checkout.rb +90 -0
  84. data/lib/datalog/theme/version.rb +1 -1
  85. data/lib/datalog/warning_filter.rb +5 -11
  86. data/lib/datalog-theme.rb +21 -0
  87. metadata +75 -146
  88. data/_data/academic.yml +0 -217
  89. data/_data/config/author.yml +0 -121
  90. data/_data/config/features.yml +0 -262
  91. data/_data/config/site.yml +0 -42
  92. data/_data/config/theme.yml +0 -181
  93. data/_data/datasets.yml +0 -28
  94. data/_data/js_meta.json +0 -371
  95. data/_data/navigation.yml +0 -145
  96. data/_data/projects.yml +0 -41
  97. data/_data/publications.yml +0 -28
  98. data/_data/social.yml +0 -73
  99. data/_data/visualizations.yml +0 -51
  100. data/_includes/components/advanced-search.html +0 -682
  101. data/_includes/components/bookmark-system.html +0 -96
  102. data/_includes/components/comments.html +0 -244
  103. data/_includes/components/content-recommendations.html +0 -228
  104. data/_includes/components/email-preferences.html +0 -200
  105. data/_includes/components/enhanced-metadata.html +0 -228
  106. data/_includes/components/language-switcher.html +0 -396
  107. data/_includes/components/navigation-enhancements.html +0 -454
  108. data/_includes/components/newsletter-signup.html +0 -178
  109. data/_includes/components/popular-posts.html +0 -233
  110. data/_includes/components/reading-progress.html +0 -133
  111. data/_includes/components/reading-time.html +0 -121
  112. data/_includes/components/series-navigation.html +0 -124
  113. data/_includes/components/social-proof.html +0 -34
  114. data/_includes/components/user-preferences.html +0 -566
  115. data/_layouts/archive.html +0 -282
  116. data/_layouts/post-sidebar.html +0 -183
  117. data/_sass/_phase3-enhancements.scss +0 -874
  118. data/_sass/_phase4-enhancements.scss +0 -1214
  119. data/_sass/_phase5-enhancements.scss +0 -414
  120. data/assets/img/20220607123041_detail.001.png +0 -0
  121. data/assets/js/academic.js +0 -262
  122. data/assets/js/analytics-dashboard.js +0 -382
  123. data/assets/js/core/dark-mode.js +0 -79
  124. data/assets/js/core/github-cards.js +0 -123
  125. data/assets/js/core/language-filter.js +0 -69
  126. data/assets/js/core/navigation.js +0 -184
  127. data/assets/js/core/scroll-progress.js +0 -45
  128. data/assets/js/core/search-hotkeys.js +0 -62
  129. data/assets/js/core/skip-links.js +0 -62
  130. data/assets/js/main.js +0 -23
  131. data/assets/js/math.js +0 -818
  132. data/assets/js/notebook.js +0 -158
  133. data/assets/js/search/analytics.js +0 -91
  134. data/assets/js/search/app.js +0 -271
  135. data/assets/js/search/autocomplete.js +0 -120
  136. data/assets/js/search/engine.js +0 -260
  137. data/assets/js/search/filters.js +0 -38
  138. data/assets/js/search/render.js +0 -217
  139. data/assets/js/search/utils.js +0 -99
  140. data/assets/js/search.js +0 -354
  141. data/assets/js/visualizations.js +0 -816
  142. data/assets/publications/datalog-publications.bib +0 -8
  143. data/assets/publications/datalog-publications.ris +0 -9
  144. data/assets/publications/publications.bib +0 -30
  145. data/assets/templates/diogo-ribeiro-cv.md +0 -31
  146. data/assets/templates/diogo-ribeiro-cv.tex +0 -32
  147. data/lib/datalog/theme/theme.rb +0 -18
@@ -1,17 +1,121 @@
1
1
  {% assign csp_registry = site.data.csp | default: {} %}
2
2
  {% assign nonce_source = page.csp_nonce | default: csp_registry.nonces[page.path] | default: csp_registry.nonces[page.url] | default: '' %}
3
- {% assign script_hashes = page.csp_hashes | default: [] %}
4
- {% capture script_hash_directives -%}
5
- {%- for hash in script_hashes -%}
6
- 'sha256-{{ hash }}'{% unless forloop.last %} {% endunless %}
7
- {%- endfor -%}
8
- {%- endcapture -%}
3
+ {%- comment -%}
4
+ Sources a site adds for its own embeds, listed under `csp` in _config.yml:
5
+ frame_src for iframes (Shiny apps, slide decks, videos), and script_src,
6
+ style_src, font_src and connect_src for anything else.
7
+ {%- endcomment -%}
8
+ {% assign frame_hosts = site.csp.frame_src | join: ' ' %}
9
+ {% assign extra_script_src = site.csp.script_src | join: ' ' %}
10
+ {% assign extra_style_src = site.csp.style_src | join: ' ' %}
11
+ {% assign extra_font_src = site.csp.font_src | join: ' ' %}
12
+ {% assign extra_connect_src = site.csp.connect_src | join: ' ' %}
13
+ {%- comment -%}
14
+ Plotly, the Jupyter widget manager and MathJax can't run under the strict
15
+ policy. Plotly inserts its rules into a <style> element it creates; the widget
16
+ manager adds <style> elements too, and compiles widget schemas with
17
+ new Function; MathJax's CHTML output adds the stylesheet its layout depends
18
+ on. Only the pages that run them get inline styles, and widget pages eval. A
19
+ page that loads such a library another way sets csp.unsafe_inline_styles or
20
+ csp.unsafe_eval in its front matter. A browser ignores 'unsafe-inline' in a
21
+ directive that also lists a nonce, so those pages drop the style nonce.
22
+ {%- endcomment -%}
23
+ {% assign csp_inline_styles = page.csp.unsafe_inline_styles %}
24
+ {% assign csp_eval = page.csp.unsafe_eval %}
25
+ {%- comment -%}
26
+ jsDelivr serves any npm package, so the policy names the packages this page
27
+ loads rather than the whole host: the math engine, from the URLs the head's
28
+ script tags use (math-config.html and head.html set them), and the chart
29
+ libraries the visualization and notebook scripts load for the blocks on the
30
+ page. Keep these in step with the URLs in assets/js/visualizations.js,
31
+ assets/js/notebook.js and _includes/analytics/dashboard.html;
32
+ tests/test_csp.rb checks that they are.
33
+ {%- endcomment -%}
34
+ {% assign cdn_scripts = '' %}
35
+ {% assign cdn_styles = '' %}
36
+ {% assign cdn_fonts = '' %}
37
+ {% assign cdn_connect = '' %}
38
+ {% assign cdn_frames = '' %}
39
+ {% if math_enabled %}
40
+ {% if math_engine == 'katex' %}
41
+ {% assign cdn_scripts = cdn_scripts | append: ' ' | append: katex_cdn %}
42
+ {% assign cdn_styles = cdn_styles | append: ' ' | append: katex_cdn %}
43
+ {% assign cdn_fonts = cdn_fonts | append: ' ' | append: katex_cdn %}
44
+ {% else %}
45
+ {%- comment -%} MathJax loads its components, fonts and speech data from the directory of its main script. {%- endcomment -%}
46
+ {% assign mathjax_file = mathjax_url | split: '/' | last %}
47
+ {% assign mathjax_dir_length = mathjax_url.size | minus: mathjax_file.size %}
48
+ {% assign mathjax_dir = mathjax_url | slice: 0, mathjax_dir_length %}
49
+ {% assign cdn_scripts = cdn_scripts | append: ' ' | append: mathjax_dir %}
50
+ {% assign cdn_fonts = cdn_fonts | append: ' ' | append: mathjax_dir %}
51
+ {% assign cdn_connect = cdn_connect | append: ' ' | append: mathjax_dir %}
52
+ {% assign csp_inline_styles = true %}
53
+ {% endif %}
54
+ {% endif %}
55
+ {% if content contains 'data-viz-type="plotly"' or content contains "data-viz-type='plotly'" or content contains 'notebook-output-plotly' %}
56
+ {% assign csp_inline_styles = true %}
57
+ {% assign cdn_scripts = cdn_scripts | append: ' https://cdn.jsdelivr.net/npm/plotly.js-dist-min@2.27.0/' %}
58
+ {% endif %}
59
+ {% if content contains 'data-viz-type="d3"' or content contains "data-viz-type='d3'" %}
60
+ {% assign cdn_scripts = cdn_scripts | append: ' https://cdn.jsdelivr.net/npm/d3@7/' %}
61
+ {% endif %}
62
+ {% if content contains 'data-viz-type="bokeh"' or content contains "data-viz-type='bokeh'" %}
63
+ {% assign cdn_scripts = cdn_scripts | append: ' https://cdn.jsdelivr.net/npm/@bokeh/bokehjs@3.3.3/' %}
64
+ {% endif %}
65
+ {% if content contains 'notebook-output-vega' %}
66
+ {% assign cdn_scripts = cdn_scripts | append: ' https://cdn.jsdelivr.net/npm/vega@5 https://cdn.jsdelivr.net/npm/vega-lite@5 https://cdn.jsdelivr.net/npm/vega-embed@6' %}
67
+ {% endif %}
68
+ {% if content contains 'npm/chart.js@4.4.0/' %}
69
+ {% assign cdn_scripts = cdn_scripts | append: ' https://cdn.jsdelivr.net/npm/chart.js@4.4.0/' %}
70
+ {% endif %}
71
+ {%- comment -%}
72
+ Widget pages load require.js from cdnjs, and allow all of jsDelivr: the widget
73
+ manager loads any widget package a notebook names from it, and its icon fonts.
74
+ {%- endcomment -%}
75
+ {% if content contains 'data-viz-type="ipywidgets"' or content contains "data-viz-type='ipywidgets'" or content contains 'application/vnd.jupyter.widget-state+json' %}
76
+ {% assign csp_inline_styles = true %}
77
+ {% assign csp_eval = true %}
78
+ {% assign cdn_scripts = cdn_scripts | append: ' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/' %}
79
+ {% assign cdn_fonts = cdn_fonts | append: ' https://cdn.jsdelivr.net' %}
80
+ {% endif %}
81
+ {%- comment -%}
82
+ The Disqus embed loads its script from the site's shortname subdomain of
83
+ disqus.com and its assets from disquscdn.com, shows the thread in an iframe
84
+ from disqus.com, and sizes that iframe with an inline style. The policy
85
+ allowed none of them.
86
+ {%- endcomment -%}
87
+ {% if content contains 'id="disqus_thread"' %}
88
+ {% assign csp_inline_styles = true %}
89
+ {% assign cdn_scripts = cdn_scripts | append: ' https://*.disqus.com https://*.disquscdn.com' %}
90
+ {% assign cdn_styles = cdn_styles | append: ' https://*.disquscdn.com' %}
91
+ {% assign cdn_connect = cdn_connect | append: ' https://*.disqus.com' %}
92
+ {% assign cdn_frames = cdn_frames | append: ' https://disqus.com' %}
93
+ {% endif %}
94
+ {%- comment -%}
95
+ Google's tag loads only on a site that sets google_analytics, and the policy
96
+ allowed its hosts on every site. These are the hosts Google documents for GA4,
97
+ which reports to regional subdomains such as region1.google-analytics.com.
98
+ {%- endcomment -%}
99
+ {% assign csp_analytics = false %}
100
+ {% if site.google_analytics and site.google_analytics != "" %}
101
+ {% assign csp_analytics = true %}
102
+ {% endif %}
103
+ {%- comment -%}
104
+ object-src, base-uri and form-action do not fall back to default-src. An
105
+ injected <base> tag could repoint the page's relative script URLs, which the
106
+ nonce would then authorise; every theme form submits to the site itself.
107
+ Observable serves classic notebook embeds from old.observablehq.com, and an
108
+ observablehq.com embed URL redirects there.
109
+ {%- endcomment -%}
9
110
  <meta http-equiv="Content-Security-Policy" content="
10
111
  default-src 'self';
11
- script-src 'self' 'nonce-{{ nonce_source }}'{% if script_hash_directives != '' %} {{ script_hash_directives | strip }}{% endif %} https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://www.googletagmanager.com https://www.google-analytics.com;
12
- style-src 'self' 'nonce-{{ nonce_source }}' https://fonts.googleapis.com https://cdn.jsdelivr.net;
112
+ script-src 'self' 'nonce-{{ nonce_source }}'{% if csp_eval %} 'unsafe-eval'{% endif %}{{ cdn_scripts }}{% if csp_analytics %} https://*.googletagmanager.com{% endif %}{% if extra_script_src != '' %} {{ extra_script_src }}{% endif %};
113
+ style-src 'self' {% if csp_inline_styles %}'unsafe-inline'{% else %}'nonce-{{ nonce_source }}'{% endif %} https://fonts.googleapis.com{{ cdn_styles }}{% if extra_style_src != '' %} {{ extra_style_src }}{% endif %};
13
114
  img-src 'self' data: https:;
14
- font-src 'self' https://fonts.gstatic.com data:;
15
- connect-src 'self' https://api.github.com https://www.google-analytics.com https://cdn.jsdelivr.net;
16
- frame-src 'self' https://observablehq.com;
115
+ font-src 'self' https://fonts.gstatic.com data:{{ cdn_fonts }}{% if extra_font_src != '' %} {{ extra_font_src }}{% endif %};
116
+ connect-src 'self' https://api.github.com{{ cdn_connect }}{% if csp_analytics %} https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com{% endif %}{% if extra_connect_src != '' %} {{ extra_connect_src }}{% endif %};
117
+ frame-src 'self' https://observablehq.com https://old.observablehq.com{{ cdn_frames }}{% if frame_hosts != '' %} {{ frame_hosts }}{% endif %};
118
+ object-src 'none';
119
+ base-uri 'self';
120
+ form-action 'self';
17
121
  " />
@@ -2,10 +2,11 @@
2
2
  {% assign footer_year = site.time | date: '%Y' %}
3
3
  {% assign navigation_data = site.data.navigation %}
4
4
  {% assign social_data = site.data.social %}
5
+ {%- comment -%} The column titles are <h2>: the footer follows the page's own outline, which starts at its <h1>. {%- endcomment %}
5
6
  <footer class="site-footer" role="contentinfo">
6
7
  <div class="container footer-container">
7
8
  <section class="footer-about">
8
- <h3>{{ site.name | default: 'DataLog' }}</h3>
9
+ <h2>{{ site.name | default: site.title }}</h2>
9
10
  <p class="footer-description">{{ site.description | strip_html | truncate: 160 }}</p>
10
11
  <p class="footer-author">
11
12
  <strong>{{ site.author.name }}</strong>
@@ -20,30 +21,24 @@
20
21
  {% endif %}
21
22
  </section>
22
23
 
23
- <nav class="footer-navigation">
24
- <h3>Explore</h3>
25
- {%- comment -%} `footer:` in _data/navigation.yml drives this column; the demo's sections are the fallback. {%- endcomment -%}
26
- {% assign footer_links = navigation_data.footer %}
24
+ {%- comment -%} `footer:` in _data/navigation.yml drives this column, which is left out without it. {%- endcomment -%}
25
+ {% assign footer_links = navigation_data.footer %}
26
+ {% if footer_links.size > 0 %}
27
+ <nav class="footer-navigation" aria-labelledby="footer-explore-heading">
28
+ <h2 id="footer-explore-heading">Explore</h2>
27
29
  <ul class="footer-link-list">
28
- {% if footer_links and footer_links.size > 0 %}
29
30
  {% for item in footer_links %}
30
31
  <li><a href="{{ item.url | relative_url }}">{{ item.title }}</a></li>
31
32
  {% endfor %}
32
- {% else %}
33
- <li><a href="{{ '/blog/' | relative_url }}">Blog</a></li>
34
- <li><a href="{{ '/research/' | relative_url }}">Research</a></li>
35
- <li><a href="{{ '/portfolio/' | relative_url }}">Projects</a></li>
36
- <li><a href="{{ '/datasets/' | relative_url }}">Datasets</a></li>
37
- <li><a href="{{ '/visualizations/' | relative_url }}">Visualizations</a></li>
38
- <li><a href="{{ '/about/' | relative_url }}">About</a></li>
39
- {% endif %}
40
33
  </ul>
41
34
  </nav>
35
+ {% endif %}
42
36
 
37
+ {% assign primary_social = social_data.primary %}
38
+ {% if primary_social.size > 0 or site.author.email %}
43
39
  <section class="footer-connect">
44
- <h3>Connect</h3>
45
- {% assign primary_social = social_data.primary %}
46
- {% if primary_social %}
40
+ <h2>Connect</h2>
41
+ {% if primary_social.size > 0 %}
47
42
  <ul class="footer-link-list">
48
43
  {% for profile in primary_social %}
49
44
  <li><a href="{{ profile.url }}" target="_blank" rel="{{ profile.rel | default: 'noopener' }}">{{ profile.label }}</a></li>
@@ -56,18 +51,22 @@
56
51
  </p>
57
52
  {% endif %}
58
53
  </section>
54
+ {% endif %}
59
55
 
60
56
  <section class="footer-subscribe">
61
- <h3>Stay updated</h3>
57
+ <h2>Stay updated</h2>
62
58
  <p>Follow via RSS or check back for new research and tutorials.</p>
59
+ {%- comment -%} jekyll-feed is a dependency of the theme, so /feed.xml exists even when _data/social.yml lists no feeds. {%- endcomment -%}
63
60
  {% assign rss_links = social_data.rss %}
64
- {% if rss_links %}
65
61
  <ul class="footer-link-list">
62
+ {% if rss_links.size > 0 %}
66
63
  {% for feed in rss_links %}
67
64
  <li><a href="{{ feed.url | relative_url }}">{{ feed.label }}</a></li>
68
65
  {% endfor %}
66
+ {% else %}
67
+ <li><a href="{{ '/feed.xml' | relative_url }}">RSS feed</a></li>
68
+ {% endif %}
69
69
  </ul>
70
- {% endif %}
71
70
  </section>
72
71
  </div>
73
72
  <div class="container footer-bottom">
data/_includes/head.html CHANGED
@@ -10,23 +10,16 @@
10
10
  {% elsif raw_keywords %}
11
11
  {% assign keyword_list = raw_keywords | replace: ';', ',' | split: ',' %}
12
12
  {% endif %}
13
- {% assign math_settings = site.theme_options.math %}
14
- {% assign math_engine = page.math_engine | default: math_settings.engine | default: 'mathjax' %}
15
- {% assign math_enabled = page.mathjax %}
16
- {% if math_enabled == nil %}
17
- {% assign math_enabled = page.math %}
18
- {% endif %}
19
- {% if math_enabled == nil %}
20
- {% assign math_enabled = math_settings.render_on_load | default: site.features.mathjax %}
21
- {% endif %}
13
+ {% include meta/math-config.html page=page %}
14
+ {%- comment -%} The math engine's URLs, which both the script tags below and the Content Security Policy use. {%- endcomment -%}
15
+ {% assign default_katex_cdn = 'https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/' %}
16
+ {% assign katex_cdn = math_settings.katex_cdn | default: default_katex_cdn %}
17
+ {% assign default_mathjax_url = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js' %}
18
+ {% assign mathjax_url = math_settings.mathjax_cdn | default: default_mathjax_url %}
22
19
  {% assign js_manifest = site.data.js_manifest %}
23
- {% assign syntax_config = site.theme_options.syntax_highlighting %}
24
- {% assign prism_cdn = syntax_config.cdn | default: 'https://cdn.jsdelivr.net/npm/prismjs@1.29.0' %}
25
- {% assign prism_theme = syntax_config.themes.dark | default: syntax_config.themes.light | default: 'prism-tomorrow' %}
26
- {% assign prism_theme_url = prism_cdn | append: '/themes/' | append: prism_theme | append: '.css' %}
27
- {% assign prism_line_numbers_url = prism_cdn | append: '/plugins/line-numbers/prism-line-numbers.min.css' %}
28
20
  <meta charset="utf-8" />
29
21
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
22
+ {%- comment -%} Not include_cached: the CSP nonce is different on every page. {%- endcomment -%}
30
23
  {% include csp-meta.html %}
31
24
  <meta name="viewport" content="width=device-width, initial-scale=1" />
32
25
  <meta name="theme-color" content="#0b1d3d" />
@@ -37,7 +30,8 @@
37
30
  {% endif %}
38
31
  <meta name="generator" content="Jekyll" />
39
32
  <meta name="referrer" content="no-referrer-when-downgrade" />
40
- <meta name="robots" content="index,follow" />
33
+ {%- comment -%} `robots:` in front matter keeps a page out of search results, as the 404, search and admin pages do. {%- endcomment %}
34
+ <meta name="robots" content="{{ page.robots | default: 'index,follow' }}" />
41
35
  <meta name="format-detection" content="telephone=no" />
42
36
  <meta name="application-name" content="{{ site.title }}" />
43
37
  <meta property="og:site_name" content="{{ site.title }}" />
@@ -68,12 +62,31 @@
68
62
  <link rel="canonical" href="{{ canonical_url }}" />
69
63
  {% if js_manifest and js_manifest.core %}
70
64
  <link rel="modulepreload" href="{{ js_manifest.core | relative_url }}" crossorigin="anonymous" />
71
- {% if js_manifest.features and js_manifest.features.search and site.features.search %}
65
+ {%- comment -%} Only a page that renders the search app uses the search bundle. {%- endcomment -%}
66
+ {% if js_manifest.features and js_manifest.features.search and site.features.search and content contains 'data-search-app' %}
72
67
  <link rel="modulepreload" href="{{ js_manifest.features.search | relative_url }}" crossorigin="anonymous" />
73
68
  {% endif %}
74
69
  {% if js_manifest.features and js_manifest.features.math and math_enabled and math_engine == 'mathjax' %}
75
70
  <link rel="modulepreload" href="{{ js_manifest.features.math | relative_url }}" crossorigin="anonymous" />
76
71
  {% endif %}
72
+ {%- comment -%}
73
+ The loader requests a feature bundle only after the core bundle has loaded.
74
+ Preloading the bundles this page will use, on the same tests the loader
75
+ applies, starts those downloads alongside core.
76
+ {%- endcomment -%}
77
+ {% if js_manifest.features and js_manifest.features.visualizations and site.features.visualizations and content contains 'data-viz-type' %}
78
+ <link rel="modulepreload" href="{{ js_manifest.features.visualizations | relative_url }}" crossorigin="anonymous" />
79
+ {% endif %}
80
+ {% if js_manifest.features and js_manifest.features.notebook and page.notebook_post %}
81
+ <link rel="modulepreload" href="{{ js_manifest.features.notebook | relative_url }}" crossorigin="anonymous" />
82
+ {% endif %}
83
+ {% if js_manifest.features and js_manifest.features.academic %}
84
+ {% if site.features.academic_dashboard or site.features.citation_tracking %}
85
+ {% if content contains 'data-citation-metric' or content contains 'data-citation-table' or content contains 'data-citation-chart' %}
86
+ <link rel="modulepreload" href="{{ js_manifest.features.academic | relative_url }}" crossorigin="anonymous" />
87
+ {% endif %}
88
+ {% endif %}
89
+ {% endif %}
77
90
  {% endif %}
78
91
  {% assign alternate_translations = page.translations %}
79
92
  {% if alternate_translations %}
@@ -100,17 +113,20 @@
100
113
  <title>{{ site.title }}</title>
101
114
  {% endif %}
102
115
 
116
+ {%- comment -%}
117
+ Only the weights the stylesheet renders. Sans 700, Serif 400 and Mono 500
118
+ were requested as well, and no page of the demo site used them.
119
+ {%- endcomment -%}
120
+ {% assign google_fonts_url = 'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600&family=IBM+Plex+Serif:wght@600;700&family=IBM+Plex+Mono:wght@400;600&display=swap' %}
103
121
  <link rel="preconnect" href="https://fonts.googleapis.com" />
104
122
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
105
- <link rel="preload" href="{{ 'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=IBM+Plex+Serif:wght@400;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap' | add_sri }}" as="style" />
106
- <link rel="stylesheet" href="{{ 'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=IBM+Plex+Serif:wght@400;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap' | add_sri }}" media="print" data-async-style />
123
+ <link rel="preload" href="{{ google_fonts_url | add_sri }}" as="style" />
124
+ <link rel="stylesheet" href="{{ google_fonts_url | add_sri }}" media="print" data-async-style />
107
125
  <noscript>
108
- <link rel="stylesheet" href="{{ 'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=IBM+Plex+Serif:wght@400;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap' | add_sri }}" />
126
+ <link rel="stylesheet" href="{{ google_fonts_url | add_sri }}" />
109
127
  </noscript>
110
128
 
111
129
  <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
112
- <link rel="stylesheet" href="{{ prism_theme_url | add_sri }}" />
113
- <link rel="stylesheet" href="{{ prism_line_numbers_url | add_sri }}" />
114
130
  {% assign critical_config = site.critical_css | default: {} %}
115
131
  {% assign critical_enabled = critical_config.enabled | default: false %}
116
132
  {% assign main_stylesheet = '/assets/css/main.css' | relative_url %}
@@ -171,13 +187,9 @@
171
187
  </script>
172
188
 
173
189
  {% if math_enabled %}
174
- {% assign default_katex_cdn = 'https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/' %}
175
- {% assign katex_cdn = math_settings.katex_cdn | default: default_katex_cdn %}
176
190
  {% assign katex_stylesheet_url = katex_cdn | append: 'katex.min.css' %}
177
191
  {% assign katex_js_url = katex_cdn | append: 'katex.min.js' %}
178
192
  {% assign katex_autorender_url = katex_cdn | append: 'contrib/auto-render.min.js' %}
179
- {% assign default_mathjax_url = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js' %}
180
- {% assign mathjax_url = math_settings.mathjax_cdn | default: default_mathjax_url %}
181
193
  {% if math_engine == 'katex' %}
182
194
  <link rel="stylesheet" href="{{ katex_stylesheet_url | add_sri }}" />
183
195
  <script defer src="{{ katex_js_url | add_sri }}"></script>
@@ -205,7 +217,10 @@
205
217
  displayMath: [['$$', '$$'], ['\\[', '\\]']],
206
218
  processEscapes: true,
207
219
  processEnvironments: true,
208
- packages: { '[+]': ['ams', 'mathtools', 'physics', 'mhchem', 'require', 'boldsymbol', 'braket', 'cancel'] },
220
+ // No 'require': MathJax 3.2 stopped starting up with "Illegal characters
221
+ // used in \require prefix", and nothing was typeset. Every extension the
222
+ // pages use is loaded below, so authors never need \require.
223
+ packages: { '[+]': ['ams', 'mathtools', 'physics', 'mhchem', 'boldsymbol', 'braket', 'cancel'] },
209
224
  tags: 'all',
210
225
  tagSide: 'right',
211
226
  tagIndent: '1.25em',
@@ -231,7 +246,7 @@
231
246
  },
232
247
  loader: {
233
248
  load: [
234
- '[tex]/require', '[tex]/ams', '[tex]/mathtools', '[tex]/physics', '[tex]/mhchem',
249
+ '[tex]/ams', '[tex]/mathtools', '[tex]/physics', '[tex]/mhchem',
235
250
  '[tex]/boldsymbol', '[tex]/braket', '[tex]/cancel', 'ui/menu', 'a11y/semantic-enrich', 'a11y/assistive-mml'
236
251
  ]
237
252
  },
@@ -244,7 +259,10 @@
244
259
  settings: {
245
260
  assistiveMml: true,
246
261
  collapsible: true,
247
- explorer: true
262
+ // MathJax's explorer gives each equation tabindex="1" when it
263
+ // attaches, which takes the equations out of the page's tab order.
264
+ // The assistive MathML above is what screen readers read.
265
+ explorer: false
248
266
  }
249
267
  }
250
268
  },
@@ -273,7 +291,11 @@
273
291
  if (window.DatalogMath && typeof window.DatalogMath.onPageReady === 'function') {
274
292
  window.DatalogMath.onPageReady();
275
293
  }
276
- return window.MathJax.startup.defaultPageReady();
294
+ // Scripts that act on typeset math wait for this event: until MathJax
295
+ // loads, window.MathJax is only this configuration object.
296
+ return window.MathJax.startup.defaultPageReady().then(() => {
297
+ document.dispatchEvent(new CustomEvent('datalog:math-ready'));
298
+ });
277
299
  }
278
300
  }
279
301
  };
@@ -324,14 +346,24 @@
324
346
 
325
347
  <link rel="alternate" type="application/atom+xml" title="{{ site.title }}" href="{{ '/feed.xml' | absolute_url }}" />
326
348
  <meta http-equiv="Permissions-Policy" content="interest-cohort=()" />
349
+ {%- comment -%} A verification tag renders only when the site sets its code: an empty one verifies nothing. {%- endcomment %}
350
+ {% if site.google_site_verification and site.google_site_verification != "" %}
327
351
  <meta name="google-site-verification" content="{{ site.google_site_verification }}" />
352
+ {% endif %}
353
+ {% if site.bing_site_verification and site.bing_site_verification != "" %}
328
354
  <meta name="msvalidate.01" content="{{ site.bing_site_verification }}" />
355
+ {% endif %}
356
+ {% if site.yandex_site_verification and site.yandex_site_verification != "" %}
329
357
  <meta name="yandex-verification" content="{{ site.yandex_site_verification }}" />
358
+ {% endif %}
359
+ {% if site.baidu_site_verification and site.baidu_site_verification != "" %}
330
360
  <meta name="baidu-site-verification" content="{{ site.baidu_site_verification }}" />
361
+ {% endif %}
331
362
  <meta name="plausible-domain" content="{{ site.url | replace: 'https://', '' | replace: 'http://', '' }}" />
332
363
 
333
364
  <script nonce="{{ page.csp_nonce }}">
334
365
  document.documentElement.classList.add('js');
335
366
  </script>
336
367
 
368
+ {%- comment -%} Not include_cached: its inline script carries the page's CSP nonce. {%- endcomment -%}
337
369
  {% include analytics.html %}
@@ -3,37 +3,34 @@
3
3
  required:
4
4
  - items
5
5
  optional:
6
- - page
7
- - contexts
6
+ - active
7
+ - lang
8
8
  ---
9
- Renders the primary navigation list with active state awareness.
9
+ Renders the primary navigation list. `active` lists the indexes of the items that
10
+ mark the current section, each between commas (",2,"); header.html works them out
11
+ from the page. Nothing else here depends on the page, which is what lets
12
+ header.html render this with include_cached. `lang` is passed only so that each
13
+ page language gets its own copy of any translated descriptions.
10
14
  {%- endcomment -%}
11
15
  {%- assign nav_items = include.items | default: '' -%}
12
16
  {%- if nav_items == '' -%}
13
17
  {%- assign nav_items = '' | split: '' -%}
14
18
  {%- endif -%}
15
- {%- assign active_page = include.page | default: page -%}
16
- {%- assign contexts = include.contexts | default: '' -%}
17
- {%- if contexts == '' -%}
18
- {%- assign contexts = '' | split: '' -%}
19
- {%- endif -%}
19
+ {%- assign active_items = include.active | default: '' -%}
20
20
  <ul class="nav-list">
21
21
  {%- for item in nav_items -%}
22
22
  {%- assign item_url = item.url | relative_url -%}
23
+ {%- capture item_marker -%},{{ forloop.index0 }},{%- endcapture -%}
23
24
  {%- assign is_active = false -%}
25
+ {%- if active_items contains item_marker -%}
26
+ {%- assign is_active = true -%}
27
+ {%- endif -%}
24
28
  {%- assign nav_title = item.title | default: 'Untitled' | strip -%}
25
29
  {%- assign nav_section = item.section | default: nav_title | replace: '_', ' ' | replace: '-', ' ' | strip -%}
26
30
  {%- assign nav_description = item.description | default: '' -%}
27
31
  {%- if nav_description == '' and item.description_key -%}
28
32
  {%- capture nav_description -%}{% t item.description_key %}{%- endcapture -%}
29
33
  {%- endif -%}
30
- {%- if active_page.url == item.url or active_page.url == item_url -%}
31
- {%- assign is_active = true -%}
32
- {%- elsif item.section and contexts contains item.section -%}
33
- {%- assign is_active = true -%}
34
- {%- elsif item.url != '/' and active_page.url contains item.url -%}
35
- {%- assign is_active = true -%}
36
- {%- endif -%}
37
34
  {%- if nav_description == '' -%}
38
35
  {%- assign nav_description = nav_section | default: nav_title -%}
39
36
  {%- assign nav_description = nav_description | append: ' resources' -%}
@@ -1,10 +1,31 @@
1
1
  {% assign localization_config = site.theme_options.localization %}
2
2
  {% assign navigation_data = site.data.navigation %}
3
+ {%- comment -%} `header:` (or `main:`) in _data/navigation.yml lists the links. Without it the header has no navigation, rather than links to sections the site may not have. {%- endcomment -%}
4
+ {% assign nav_items = navigation_data.header | default: navigation_data.main %}
5
+ {%- comment -%}
6
+ The navigation is the same on every page except for which links mark the current
7
+ section, so header/navigation.html renders from the indexes of those links (",2,")
8
+ and is cached for each distinct set.
9
+ {%- endcomment -%}
10
+ {% assign page_contexts = '' | split: '' %}
11
+ {% if page.section %}{% assign page_contexts = page_contexts | push: page.section %}{% endif %}
12
+ {% if page.layout %}{% assign page_contexts = page_contexts | push: page.layout %}{% endif %}
13
+ {% if page.collection %}{% assign page_contexts = page_contexts | push: page.collection %}{% endif %}
14
+ {% if page.type %}{% assign page_contexts = page_contexts | push: page.type %}{% endif %}
15
+ {%- capture active_items -%}
16
+ {%- for item in nav_items -%}
17
+ {%- assign item_url = item.url | relative_url -%}
18
+ {%- if page.url == item.url or page.url == item_url -%},{{ forloop.index0 }}
19
+ {%- elsif item.section and page_contexts contains item.section -%},{{ forloop.index0 }}
20
+ {%- elsif item.url != '/' and page.url contains item.url -%},{{ forloop.index0 }}
21
+ {%- endif -%}
22
+ {%- endfor -%},
23
+ {%- endcapture -%}
3
24
  <header class="site-header" role="banner" data-sticky-header>
4
25
  <div class="header-progress" aria-hidden="true">
5
26
  <progress class="header-progress__bar" value="0" max="100" data-scroll-progress></progress>
6
27
  </div>
7
- <div class="visually-hidden" aria-live="polite" id="progress-announcement">Reading progress: <span data-scroll-progress-value>0</span>%</div>
28
+ {%- comment -%} No live region for the percentage: screen readers announced it on every scroll event. {%- endcomment %}
8
29
  <div class="container header-container">
9
30
  <div class="header-primary">
10
31
  {% assign brand_symbol = site.brand.accent_symbol | default: '∀ data · Σ insights' | strip %}
@@ -18,7 +39,7 @@
18
39
  </svg>
19
40
  </span>
20
41
  <span class="site-title-group">
21
- <span class="site-title">{{ site.name | default: 'DataLog' }}</span>
42
+ <span class="site-title">{{ site.name | default: site.title }}</span>
22
43
  <span class="site-tagline">{{ brand_symbol }}</span>
23
44
  </span>
24
45
  </a>
@@ -41,30 +62,18 @@
41
62
  </svg>
42
63
  </button>
43
64
  {% endif %}
65
+ {% if nav_items.size > 0 %}
44
66
  <button class="header-control header-control--icon nav-toggle" type="button" aria-expanded="false" aria-controls="site-nav" aria-label="Toggle navigation">
45
67
  <span aria-hidden="true">☰</span>
46
68
  </button>
69
+ {% endif %}
47
70
  </div>
48
71
  </div>
49
72
  </div>
50
- {% assign nav_items = navigation_data.header | default: navigation_data.main %}
51
- {% if nav_items == nil %}
52
- {% assign nav_items = '' | split: '' %}
53
- {% assign nav_items = nav_items | push: {"title": "Blog", "url": "/blog/", "section": "blog"} %}
54
- {% assign nav_items = nav_items | push: {"title": "Research", "url": "/research/", "section": "research"} %}
55
- {% assign nav_items = nav_items | push: {"title": "Projects", "url": "/portfolio/", "section": "portfolio"} %}
56
- {% assign nav_items = nav_items | push: {"title": "Datasets", "url": "/datasets/", "section": "datasets"} %}
57
- {% assign nav_items = nav_items | push: {"title": "Visualizations", "url": "/visualizations/", "section": "visualizations"} %}
58
- {% assign nav_items = nav_items | push: {"title": "Academic Ops", "url": "/academic/", "section": "academic"} %}
59
- {% assign nav_items = nav_items | push: {"title": "About", "url": "/about/", "section": "about"} %}
60
- {% endif %}
61
- {% assign page_contexts = '' | split: '' %}
62
- {% if page.section %}{% assign page_contexts = page_contexts | push: page.section %}{% endif %}
63
- {% if page.layout %}{% assign page_contexts = page_contexts | push: page.layout %}{% endif %}
64
- {% if page.collection %}{% assign page_contexts = page_contexts | push: page.collection %}{% endif %}
65
- {% if page.type %}{% assign page_contexts = page_contexts | push: page.type %}{% endif %}
66
- <nav id="site-nav" class="site-nav" role="navigation" aria-label="Primary navigation" data-open="true">
67
- {% include header/navigation.html items=nav_items page=page contexts=page_contexts %}
73
+ {% if nav_items.size > 0 %}
74
+ <nav id="site-nav" class="site-nav" role="navigation" aria-label="Primary navigation" data-open="false">
75
+ {% include_cached header/navigation.html items=nav_items active=active_items lang=page.lang %}
68
76
  </nav>
77
+ {% endif %}
69
78
  </div>
70
79
  </header>
@@ -19,7 +19,11 @@ Renders the standard article scaffold used by the default layout.
19
19
  {%- endif -%}
20
20
  <header class="article-header">
21
21
  {%- unless hero_overlay_title -%}
22
- {%- if article_page.title -%}
22
+ {%- comment -%}
23
+ `show_title: false`, in a page's front matter or a layout's, leaves the <h1> to a
24
+ page or layout that renders its own heading, so the page has one.
25
+ {%- endcomment -%}
26
+ {%- if article_page.title and article_page.show_title != false and layout.show_title != false -%}
23
27
  <h1 class="article-title" itemprop="headline">{{ article_page.title }}</h1>
24
28
  {%- endif -%}
25
29
  {%- if article_page.subtitle -%}
@@ -7,10 +7,20 @@ optional:
7
7
  ---
8
8
  Computes math rendering settings shared by layouts.
9
9
  Exposes math_enabled, math_engine, math_status_id, and math_has_expressions variables.
10
+
11
+ math_enabled resolves, in order: the page's `mathjax` or `math` front matter,
12
+ then `theme_options.math.render_on_load` (falling back to `features.mathjax`).
13
+ With `render_on_load: auto` the engine is only loaded on pages with math: the
14
+ math preprocessor records each expression it wraps in `math_expressions`, and
15
+ leaves code alone. Kramdown's `math/tex` scripts and the preprocessor's
16
+ `math-expression--source` wrappers in the rendered page count too: notebook
17
+ pages carry the wrappers but record no expressions of their own. Searching the
18
+ rendered page for `$`, `\(` or `\[` instead loaded MathJax for shell variables,
19
+ prices and escaped parentheses in code. `true` loads it on every page; `false`
20
+ only on pages that opt in.
10
21
  {%- endcomment -%}
11
22
  {%- assign math_settings = site.theme_options.math | default: {} -%}
12
23
  {%- assign math_engine = include.page.math_engine | default: math_settings.engine | default: 'mathjax' -%}
13
- {%- assign math_enabled = include.page.mathjax | default: include.page.math | default: math_settings.render_on_load | default: site.features.mathjax -%}
14
24
  {%- assign math_status_id = include.status_id | default: 'math-status' -%}
15
25
  {%- assign math_expressions = include.page.math_expressions | default: '' -%}
16
26
  {%- assign math_has_expressions = false -%}
@@ -19,3 +29,19 @@ Exposes math_enabled, math_engine, math_status_id, and math_has_expressions vari
19
29
  {%- assign math_has_expressions = true -%}
20
30
  {%- endif -%}
21
31
  {%- endif -%}
32
+ {%- assign math_load = math_settings.render_on_load -%}
33
+ {%- if math_load == nil -%}
34
+ {%- assign math_load = site.features.mathjax -%}
35
+ {%- endif -%}
36
+ {%- if include.page.mathjax != nil -%}
37
+ {%- assign math_enabled = include.page.mathjax -%}
38
+ {%- elsif include.page.math != nil -%}
39
+ {%- assign math_enabled = include.page.math -%}
40
+ {%- elsif math_load == 'auto' -%}
41
+ {%- assign math_enabled = math_has_expressions -%}
42
+ {%- if content contains 'math/tex' or content contains 'math-expression--source' -%}
43
+ {%- assign math_enabled = true -%}
44
+ {%- endif -%}
45
+ {%- else -%}
46
+ {%- assign math_enabled = math_load -%}
47
+ {%- endif -%}
@@ -41,8 +41,9 @@ Defaults to TechnicalArticle for posts, WebPage for other pages.
41
41
  "@type": "WebPage",
42
42
  "@id": {{ page_url | jsonify }}
43
43
  },
44
- "headline": {{ schema_page.title | truncate: 110 | jsonify }},
45
- {%- if description -%}
44
+ "headline": {{ schema_page.title | default: site.title | truncate: 110 | jsonify }},
45
+ {%- comment -%} Values the page doesn't have are left out: "description": "" and "dateModified": "" are invalid structured data. {%- endcomment -%}
46
+ {%- if description and description != "" -%}
46
47
  "description": {{ description | jsonify }},
47
48
  {%- endif -%}
48
49
  "url": {{ page_url | jsonify }},
@@ -64,7 +65,9 @@ Defaults to TechnicalArticle for posts, WebPage for other pages.
64
65
  {%- if schema_page.date -%}
65
66
  "datePublished": "{{ schema_page.date | date_to_xmlschema }}",
66
67
  {%- endif -%}
68
+ {%- if modified -%}
67
69
  "dateModified": "{{ modified | date_to_xmlschema }}",
70
+ {%- endif -%}
68
71
  {%- if word_count > 0 -%}
69
72
  "wordCount": {{ word_count }},
70
73
  {%- endif -%}