datalog-theme 0.7.0 → 0.9.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 (203) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +188 -0
  3. data/CITATION.cff +2 -2
  4. data/README.md +25 -17
  5. data/_data/cdn-integrity.yml +0 -30
  6. data/_data/i18n/en.yml +297 -0
  7. data/_data/i18n/es.yml +297 -0
  8. data/_data/i18n/pt.yml +297 -0
  9. data/_data/js_manifest.json +16 -0
  10. data/_includes/analytics/dashboard.html +3 -1
  11. data/_includes/components/api-function.html +20 -1
  12. data/_includes/components/author-bio.html +21 -11
  13. data/_includes/components/author-list.html +32 -0
  14. data/_includes/components/citation-tools.html +33 -25
  15. data/_includes/components/comments-thread.html +90 -0
  16. data/_includes/components/contact-form.html +120 -0
  17. data/_includes/components/correction-report.html +82 -0
  18. data/_includes/components/enhanced-code-block.html +1 -1
  19. data/_includes/components/enhanced-toc.html +6 -7
  20. data/_includes/components/license-link.html +13 -0
  21. data/_includes/components/license-notice.html +28 -0
  22. data/_includes/components/moderation-inbox.html +139 -0
  23. data/_includes/components/reactions.html +49 -0
  24. data/_includes/components/reading-list.html +31 -0
  25. data/_includes/components/reading-mode-toggle.html +65 -0
  26. data/_includes/components/reading-state-bookmark.html +27 -0
  27. data/_includes/components/reading-state-panel.html +71 -0
  28. data/_includes/components/reproducibility.html +61 -0
  29. data/_includes/components/responsive-image.html +3 -3
  30. data/_includes/components/revision-history.html +39 -0
  31. data/_includes/components/revision-notice.html +35 -0
  32. data/_includes/components/series-nav.html +64 -0
  33. data/_includes/components/subscribe-form.html +88 -0
  34. data/_includes/components/subscription-manage.html +68 -0
  35. data/_includes/components/webmentions.html +48 -0
  36. data/_includes/csp-meta.html +135 -11
  37. data/_includes/footer.html +22 -20
  38. data/_includes/head.html +111 -63
  39. data/_includes/header/navigation.html +12 -15
  40. data/_includes/header.html +28 -19
  41. data/_includes/layouts/default/article.html +9 -7
  42. data/_includes/meta/dynamic-services-config.html +14 -0
  43. data/_includes/meta/math-config.html +18 -10
  44. data/_includes/meta/person-json.html +27 -0
  45. data/_includes/meta/publisher.html +45 -0
  46. data/_includes/meta/schema.html +67 -30
  47. data/_includes/meta/scholarly.html +121 -0
  48. data/_includes/meta/scripts-loader.html +18 -32
  49. data/_includes/meta/webmention-discovery.html +14 -0
  50. data/_includes/post/related-posts.html +4 -7
  51. data/_includes/scripts.html +57 -0
  52. data/_includes/search/index-data.json +9 -34
  53. data/_layouts/dataset.html +5 -3
  54. data/_layouts/default.html +16 -8
  55. data/_layouts/notebook.html +1 -0
  56. data/_layouts/package.html +4 -2
  57. data/_layouts/page.html +15 -0
  58. data/_layouts/portfolio.html +1 -0
  59. data/_layouts/post.html +102 -23
  60. data/_layouts/project.html +4 -3
  61. data/_layouts/research.html +20 -8
  62. data/_plugins/analytics_dashboard.rb +9 -3
  63. data/_plugins/authors.rb +133 -0
  64. data/_plugins/config_validator.rb +231 -23
  65. data/_plugins/critical_css_check.rb +42 -0
  66. data/_plugins/csp_generator.rb +18 -28
  67. data/_plugins/datalog_bibliography.rb +9 -7
  68. data/_plugins/datalog_comments.rb +8 -5
  69. data/_plugins/datalog_slides.rb +9 -8
  70. data/_plugins/i18n.rb +13 -12
  71. data/_plugins/image_optimizer.rb +241 -178
  72. data/_plugins/licenses.rb +135 -0
  73. data/_plugins/math_preprocessor.rb +59 -7
  74. data/_plugins/notebook_converter.rb +23 -4
  75. data/_plugins/plugin_loader.rb +3 -1
  76. data/_plugins/publications_generator.rb +8 -2
  77. data/_plugins/references.rb +238 -0
  78. data/_plugins/reproducibility.rb +150 -0
  79. data/_plugins/revisions.rb +101 -0
  80. data/_plugins/rouge_highlight_filter.rb +42 -0
  81. data/_plugins/scholarly.rb +50 -0
  82. data/_plugins/search_code_blocks.rb +30 -0
  83. data/_plugins/search_normalizer.rb +15 -53
  84. data/_plugins/search_pages.rb +3 -4
  85. data/_plugins/series.rb +104 -0
  86. data/_plugins/statements.rb +87 -0
  87. data/_sass/_academic-dashboard.scss +262 -0
  88. data/_sass/_base.scss +20 -1
  89. data/_sass/_comments-thread.scss +159 -0
  90. data/_sass/_components.scss +64 -1153
  91. data/_sass/_features.scss +17 -0
  92. data/_sass/_layout.scss +385 -1
  93. data/_sass/_mathematical.scss +27 -0
  94. data/_sass/_moderation.scss +222 -0
  95. data/_sass/_notebooks.scss +322 -0
  96. data/_sass/_open-science-badges.scss +56 -0
  97. data/_sass/{_phase1-enhancements.scss → _post-components.scss} +5 -3
  98. data/_sass/_print.scss +291 -0
  99. data/_sass/_reactions.scss +89 -0
  100. data/_sass/_reading-state.scss +290 -0
  101. data/_sass/_search-page.scss +530 -0
  102. data/_sass/_search.scss +46 -0
  103. data/_sass/_service-forms.scss +204 -0
  104. data/_sass/_subscriptions.scss +140 -0
  105. data/_sass/_syntax-highlighting.scss +212 -97
  106. data/_sass/_theme.scss +40 -19
  107. data/_sass/_typography.scss +130 -0
  108. data/_sass/_utilities.scss +5 -0
  109. data/_sass/_variables.scss +6 -0
  110. data/_sass/_webmentions.scss +125 -0
  111. data/assets/css/main.scss +14 -0
  112. data/assets/js/dist/academic.js +1 -1
  113. data/assets/js/dist/analytics-dashboard.js +1 -1
  114. data/assets/js/dist/chunks/chunk-2DYDWUFX.js +1 -0
  115. data/assets/js/dist/chunks/chunk-PATLC23F.js +1 -0
  116. data/assets/js/dist/chunks/chunk-V7734B2G.js +1 -0
  117. data/assets/js/dist/comments.js +2 -0
  118. data/assets/js/dist/contact.js +1 -0
  119. data/assets/js/dist/core.js +1 -1
  120. data/assets/js/dist/corrections.js +1 -0
  121. data/assets/js/dist/loader.js +1 -1
  122. data/assets/js/dist/math.js +1 -1
  123. data/assets/js/dist/moderation.js +1 -0
  124. data/assets/js/dist/notebook.js +1 -1
  125. data/assets/js/dist/reactions.js +1 -0
  126. data/assets/js/dist/reading-state.js +1 -0
  127. data/assets/js/dist/search.js +1 -1
  128. data/assets/js/dist/sources.json +52 -0
  129. data/assets/js/dist/subscriptions.js +1 -0
  130. data/assets/js/dist/visualizations.js +11 -2
  131. data/assets/js/dist/webmentions.js +1 -0
  132. data/assets/js/loader.js +37 -1
  133. data/datalog-theme.gemspec +35 -23
  134. data/lib/datalog/cli.rb +43 -15
  135. data/lib/datalog/critical_css.rb +168 -0
  136. data/lib/datalog/plugin_system/dependency_resolver.rb +0 -2
  137. data/lib/datalog/plugins/comments.rb +33 -3
  138. data/lib/datalog/theme/installed_files.rb +113 -0
  139. data/lib/datalog/theme/package.rb +57 -0
  140. data/lib/datalog/theme/repository_checkout.rb +94 -0
  141. data/lib/datalog/theme/version.rb +5 -1
  142. data/lib/datalog/warning_filter.rb +5 -11
  143. data/lib/datalog-theme.rb +6 -0
  144. metadata +96 -147
  145. data/_data/academic.yml +0 -217
  146. data/_data/config/author.yml +0 -121
  147. data/_data/datasets.yml +0 -28
  148. data/_data/js_meta.json +0 -371
  149. data/_data/navigation.yml +0 -145
  150. data/_data/projects.yml +0 -41
  151. data/_data/publications.yml +0 -28
  152. data/_data/social.yml +0 -73
  153. data/_data/visualizations.yml +0 -51
  154. data/_includes/components/advanced-search.html +0 -682
  155. data/_includes/components/bookmark-system.html +0 -96
  156. data/_includes/components/comments.html +0 -244
  157. data/_includes/components/content-recommendations.html +0 -228
  158. data/_includes/components/email-preferences.html +0 -200
  159. data/_includes/components/enhanced-metadata.html +0 -228
  160. data/_includes/components/language-switcher.html +0 -396
  161. data/_includes/components/navigation-enhancements.html +0 -454
  162. data/_includes/components/newsletter-signup.html +0 -178
  163. data/_includes/components/popular-posts.html +0 -233
  164. data/_includes/components/reading-progress.html +0 -133
  165. data/_includes/components/reading-time.html +0 -121
  166. data/_includes/components/series-navigation.html +0 -124
  167. data/_includes/components/social-proof.html +0 -34
  168. data/_includes/components/user-preferences.html +0 -566
  169. data/_includes/meta/syntax-config.html +0 -19
  170. data/_layouts/archive.html +0 -282
  171. data/_layouts/post-sidebar.html +0 -183
  172. data/_sass/_phase3-enhancements.scss +0 -874
  173. data/_sass/_phase4-enhancements.scss +0 -1214
  174. data/_sass/_phase5-enhancements.scss +0 -414
  175. data/assets/js/academic.js +0 -262
  176. data/assets/js/analytics-dashboard.js +0 -382
  177. data/assets/js/core/dark-mode.js +0 -79
  178. data/assets/js/core/github-cards.js +0 -123
  179. data/assets/js/core/language-filter.js +0 -69
  180. data/assets/js/core/navigation.js +0 -184
  181. data/assets/js/core/scroll-progress.js +0 -45
  182. data/assets/js/core/search-hotkeys.js +0 -62
  183. data/assets/js/core/skip-links.js +0 -62
  184. data/assets/js/dist/manifest.json +0 -22
  185. data/assets/js/dist/meta.json +0 -371
  186. data/assets/js/main.js +0 -23
  187. data/assets/js/math.js +0 -818
  188. data/assets/js/notebook.js +0 -158
  189. data/assets/js/search/analytics.js +0 -91
  190. data/assets/js/search/app.js +0 -271
  191. data/assets/js/search/autocomplete.js +0 -120
  192. data/assets/js/search/engine.js +0 -260
  193. data/assets/js/search/filters.js +0 -45
  194. data/assets/js/search/render.js +0 -217
  195. data/assets/js/search/utils.js +0 -99
  196. data/assets/js/search.js +0 -354
  197. data/assets/js/visualizations.js +0 -816
  198. data/assets/publications/datalog-publications.bib +0 -8
  199. data/assets/publications/datalog-publications.ris +0 -9
  200. data/assets/publications/publications.bib +0 -30
  201. data/assets/templates/diogo-ribeiro-cv.md +0 -31
  202. data/assets/templates/diogo-ribeiro-cv.tex +0 -32
  203. data/lib/datalog/theme/theme.rb +0 -18
@@ -31,6 +31,55 @@
31
31
  {% assign academic_flag = 'false' %}
32
32
  {% endif %}
33
33
 
34
+ {% if site.theme_options.reading_state.enabled == false %}
35
+ {% assign reading_state_flag = 'false' %}
36
+ {% else %}
37
+ {% assign reading_state_flag = 'auto' %}
38
+ {% endif %}
39
+
40
+ {%- comment -%} The correction-report form needs a backend with the feature on (components/correction-report.html). {%- endcomment -%}
41
+ {% assign corrections_flag = 'false' %}
42
+ {% if site.dynamic_services.base_url and site.dynamic_services.base_url != '' and site.dynamic_services.features.corrections != false and site.corrections.enabled != false %}
43
+ {% assign corrections_flag = 'auto' %}
44
+ {% endif %}
45
+
46
+ {%- comment -%} The contact form likewise (components/contact-form.html). {%- endcomment -%}
47
+ {% assign contact_flag = 'false' %}
48
+ {% if site.dynamic_services.base_url and site.dynamic_services.base_url != '' and site.dynamic_services.features.contact != false and site.contact.enabled != false %}
49
+ {% assign contact_flag = 'auto' %}
50
+ {% endif %}
51
+
52
+ {%- comment -%} The comments thread of the api provider (components/comments-thread.html), site-wide or for this page. {%- endcomment -%}
53
+ {% assign comments_flag = 'false' %}
54
+ {% assign comments_options = site.datalog_plugins.options['datalog-comments'] %}
55
+ {% if comments_options.provider == 'api' or page.datalog_comments.provider == 'api' %}
56
+ {% assign comments_flag = 'auto' %}
57
+ {% endif %}
58
+
59
+ {%- comment -%} "Was this useful?" needs a backend with the feature on (components/reactions.html). {%- endcomment -%}
60
+ {% assign reactions_flag = 'false' %}
61
+ {% if site.dynamic_services.base_url and site.dynamic_services.base_url != '' and site.dynamic_services.features.reactions != false and site.reactions.enabled != false %}
62
+ {% assign reactions_flag = 'auto' %}
63
+ {% endif %}
64
+
65
+ {%- comment -%} "Mentioned elsewhere" needs a backend with the feature on (components/webmentions.html). {%- endcomment -%}
66
+ {% assign webmentions_flag = 'false' %}
67
+ {% if site.dynamic_services.base_url and site.dynamic_services.base_url != '' and site.dynamic_services.features.webmentions != false and site.webmentions.enabled != false %}
68
+ {% assign webmentions_flag = 'auto' %}
69
+ {% endif %}
70
+
71
+ {%- comment -%} The newsletter form and its manage page need a backend with the feature on (components/subscribe-form.html). {%- endcomment -%}
72
+ {% assign subscriptions_flag = 'false' %}
73
+ {% if site.dynamic_services.base_url and site.dynamic_services.base_url != '' and site.dynamic_services.features.subscriptions != false and site.subscriptions.enabled != false %}
74
+ {% assign subscriptions_flag = 'auto' %}
75
+ {% endif %}
76
+
77
+ {%- comment -%} The moderation inbox is off unless the site turns it on (components/moderation-inbox.html). {%- endcomment -%}
78
+ {% assign moderation_flag = 'false' %}
79
+ {% if site.moderation.enabled == true %}
80
+ {% assign moderation_flag = 'auto' %}
81
+ {% endif %}
82
+
34
83
  <script nonce="{{ page.csp_nonce }}">
35
84
  (function () {
36
85
  const body = document.body;
@@ -42,6 +91,14 @@
42
91
  body.dataset.featureMath = "{{ math_flag }}";
43
92
  body.dataset.featureNotebook = "{{ notebook_flag }}";
44
93
  body.dataset.featureAcademic = "{{ academic_flag }}";
94
+ body.dataset.featureReadingState = "{{ reading_state_flag }}";
95
+ body.dataset.featureCorrections = "{{ corrections_flag }}";
96
+ body.dataset.featureContact = "{{ contact_flag }}";
97
+ body.dataset.featureComments = "{{ comments_flag }}";
98
+ body.dataset.featureReactions = "{{ reactions_flag }}";
99
+ body.dataset.featureWebmentions = "{{ webmentions_flag }}";
100
+ body.dataset.featureSubscriptions = "{{ subscriptions_flag }}";
101
+ body.dataset.featureModeration = "{{ moderation_flag }}";
45
102
  })();
46
103
  </script>
47
104
 
@@ -2,6 +2,7 @@
2
2
  "generated_at": "{{ 'now' | date_to_xmlschema }}",
3
3
  "documents": [
4
4
  {% assign known_langs = 'python,r,sql,julia,javascript,scala,go,java,typescript,html,css' | split: ',' %}
5
+ {% assign no_code = '' | split: '' %}
5
6
  {% assign searchable = '' | split: '' %}
6
7
  {% for post in site.posts %}
7
8
  {% unless post.exclude_from_search %}
@@ -76,37 +77,19 @@
76
77
  {% capture doc_type %}
77
78
  {% if doc.collection == 'posts' %}post{% elsif doc.collection == 'portfolio' %}project{% elsif doc.collection == 'datasets' %}dataset{% elsif doc.layout == 'research' %}research{% elsif doc.layout == 'project' %}project{% elsif doc.layout == 'post' %}post{% else %}{{ doc.collection | default: doc.layout | default: 'page' }}{% endif %}
78
79
  {% endcapture %}
79
- {% assign normalized_title = doc.title | default: doc.name | normalize_search %}
80
- {% assign normalized_summary = summary | normalize_search %}
81
- {% assign normalized_content = content_text | normalize_search %}
82
- {% assign normalized_tags = tags | normalize_search_array %}
83
- {% assign normalized_languages = languages | normalize_search_array %}
84
- {% assign normalized_difficulty = doc.difficulty | default: doc.level | normalize_search | default: '' %}
80
+ {%- comment -%}
81
+ Each text field is written once. The search engine normalizes it in the browser with the
82
+ function it applies to queries; a precomputed normalized copy doubled the size of the file.
83
+ The code blocks come from the page's source, collected before rendering by
84
+ _plugins/search_code_blocks.rb: by the time this renders, a post's content is already HTML.
85
+ {%- endcomment %}
85
86
  {
86
87
  "title": {{ doc.title | default: doc.name | jsonify }},
87
88
  "url": {{ doc.url | relative_url | jsonify }},
88
89
  "type": {{ doc_type | strip | jsonify }},
89
90
  "summary": {{ summary | strip | jsonify }},
90
91
  "content": {{ content_text | jsonify }},
91
- "code": [
92
- {% assign raw_blocks = raw_content | split: '```' %}
93
- {% assign code_counter = 0 %}
94
- {% for block in raw_blocks %}
95
- {% assign block_index = forloop.index0 | modulo: 2 %}
96
- {% if block_index == 1 %}
97
- {% assign lines = block | split: '\n' %}
98
- {% assign first_line = lines[0] | default: '' %}
99
- {% assign language_hint = first_line | strip | downcase | default: 'text' %}
100
- {% assign code_body = block | remove_first: first_line | strip %}
101
- {% if code_counter > 0 %},{% endif %}
102
- {
103
- "language": {{ language_hint | jsonify }},
104
- "code": {{ code_body | jsonify }}
105
- }
106
- {% assign code_counter = code_counter | plus: 1 %}
107
- {% endif %}
108
- {% endfor %}
109
- ],
92
+ "code": {{ doc.search_code | default: no_code | jsonify }},
110
93
  "math": [
111
94
  {% assign math_counter = 0 %}
112
95
  {% for piece in inline_math_segments %}
@@ -137,15 +120,7 @@
137
120
  "difficulty": {{ doc.difficulty | default: doc.level | default: '' | jsonify }},
138
121
  "topics": {{ doc.topics | default: tags | uniq | jsonify }},
139
122
  "date": {% if doc.date %}{{ doc.date | date_to_xmlschema | jsonify }}{% else %}null{% endif %},
140
- "reading_time": {{ doc.reading_time | default: doc.time | default: '' | jsonify }},
141
- "normalized": {
142
- "title": {{ normalized_title | jsonify }},
143
- "summary": {{ normalized_summary | jsonify }},
144
- "content": {{ normalized_content | jsonify }},
145
- "tags": {{ normalized_tags | uniq | jsonify }},
146
- "languages": {{ normalized_languages | uniq | jsonify }},
147
- "difficulty": {{ normalized_difficulty | jsonify }}
148
- }
123
+ "reading_time": {{ doc.reading_time | default: doc.time | default: '' | jsonify }}
149
124
  {% assign search_extensions = doc.datalog_search_extensions %}
150
125
  {% if search_extensions and search_extensions != empty %}
151
126
  ,"extensions": {{ search_extensions | jsonify }}
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  layout: default
3
+ show_title: false # this layout renders the page's <h1>
3
4
  ---
4
5
  {% assign localization_config = site.theme_options.localization %}
5
6
  {% assign date_format = page.date_format | default: localization_config.date_format | default: 'long' %}
@@ -7,7 +8,8 @@ layout: default
7
8
  <header class="dataset-header">
8
9
  <h1>{{ page.title }}</h1>
9
10
  {% if page.updated %}<p class="dataset-meta"><strong>{% t 'dataset.last_updated' %}</strong> {{ page.updated | localize_date: date_format }}</p>{% endif %}
10
- {% if page.license %}<p class="dataset-meta"><strong>{% t 'dataset.license' %}</strong> {{ page.license }}</p>{% endif %}
11
+ {% assign dataset_license = page | page_license %}
12
+ {% if dataset_license %}<p class="dataset-meta"><strong>{% t 'dataset.license' %}</strong> {% include components/license-link.html license=dataset_license %}</p>{% endif %}
11
13
  </header>
12
14
  <div class="dataset-summary">
13
15
  {{ page.summary | default: page.excerpt }}
@@ -44,9 +46,9 @@ layout: default
44
46
  </section>
45
47
  {% endif %}
46
48
  {% assign citation_key = page.citation_key | default: page.slug | default: page.title | slugify %}
47
- {% assign citation_authors = page.authors | default: page.author | default: site.author.name %}
49
+ {% assign citation_authors = page | page_authors %}
48
50
  {% assign citation_date = page.date | default: page.updated %}
49
51
  {% assign citation_url = page.url | absolute_url %}
50
- {% include components/citation-tools.html title=page.title authors=citation_authors date=citation_date url=citation_url citation_key=citation_key publisher=site.author.affiliation citation_type='dataset' doi=page.doi %}
52
+ {% include components/citation-tools.html title=page.title authors=citation_authors date=citation_date url=citation_url citation_key=citation_key citation_type='dataset' doi=page.doi %}
51
53
  {% include components/open-science-badges.html %}
52
54
  </article>
@@ -8,25 +8,33 @@
8
8
  <head>
9
9
  {% include head.html %}
10
10
  <meta name="twitter:creator" content="{{ site.author.twitter | default: site.social.twitter }}" />
11
- {% if page.tags and page.tags.size > 0 %}
12
- <meta name="keywords" content="{% include helpers/array-to-sentence.html items=page.tags conjunction='and' %}" />
13
- {% endif %}
14
- <meta name="format-detection" content="telephone=no" />
15
- <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
16
11
  {% include meta/schema.html page=page %}
17
- {% include meta/scripts-loader.html location='head' %}
18
12
  </head>
19
13
  {% capture body_classes %}
20
14
  site-body{% if page.layout %} layout-{{ page.layout | replace: ' ', '-' }}{% endif %}{% if page.page_class %} page-{{ page.page_class | replace: ' ', '-' | downcase }}{% endif %}{% if page.classes %} {{ page.classes | join: ' ' }}{% endif %}{% if site.features.dark_mode_toggle %} has-dark-toggle{% endif %}{% if html_dir == 'rtl' %} rtl{% endif %}
21
15
  {% endcapture %}
22
16
  <body class="{{ body_classes | strip }}" data-theme="{{ page.theme | default: 'system' }}">
23
- {% include skip-link.html %}
17
+ {%- comment -%}
18
+ Applies the saved or system color scheme before anything is drawn. core/dark-mode.js
19
+ applies the same rule, but only once the script bundles have loaded, so readers who
20
+ chose dark saw the light theme first; keep the two in step.
21
+ {%- endcomment %}
22
+ <script nonce="{{ page.csp_nonce }}">
23
+ try {
24
+ var mode = localStorage.getItem("datalog-color-mode");
25
+ if (mode === "dark" || (mode !== "light" && window.matchMedia("(prefers-color-scheme: dark)").matches)) {
26
+ document.body.classList.add("dark-mode");
27
+ }
28
+ } catch (error) {}
29
+ </script>
30
+ {%- comment -%} The skip link and the footer take nothing from the page, so each is rendered once per build (the skip link once per language). {%- endcomment -%}
31
+ {% include_cached skip-link.html lang=page.lang %}
24
32
  {% include header.html %}
25
33
  <main id="main-content" class="site-main container" role="main" tabindex="-1" aria-describedby="{{ math_status_id }}">
26
34
  {% include components/math-fallback.html expressions=page.math_expressions %}
27
35
  {% include layouts/default/article.html page=page content=content date_format=date_format %}
28
36
  </main>
29
- {% include footer.html %}
37
+ {% include_cached footer.html lang=page.lang %}
30
38
  <div class="visually-hidden" aria-live="polite" id="{{ math_status_id }}">{% t 'global.math_loading' %}</div>
31
39
  {% include meta/scripts-loader.html location='body' %}
32
40
  </body>
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  layout: default
3
+ show_title: false # this layout renders the page's <h1>
3
4
  ---
4
5
 
5
6
  {%- comment -%}
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  layout: default
3
+ show_title: false # this layout renders the page's <h1>
3
4
  ---
4
5
 
5
6
  <div class="package-docs">
@@ -223,13 +224,14 @@ layout: default
223
224
  observer.observe(heading);
224
225
  });
225
226
 
226
- // Smooth scroll for anchor links
227
+ // Scroll to anchor targets. The stylesheet's scroll-behavior decides whether
228
+ // the scroll is smooth, so reduced-motion preferences are respected.
227
229
  document.querySelectorAll('.package-nav__link[href^="#"]').forEach(link => {
228
230
  link.addEventListener('click', (e) => {
229
231
  e.preventDefault();
230
232
  const target = document.querySelector(link.getAttribute('href'));
231
233
  if (target) {
232
- target.scrollIntoView({ behavior: 'smooth', block: 'start' });
234
+ target.scrollIntoView({ block: 'start' });
233
235
  history.pushState(null, null, link.getAttribute('href'));
234
236
  }
235
237
  });
data/_layouts/page.html CHANGED
@@ -11,6 +11,21 @@ layout: default
11
11
  {% endif %}
12
12
  <div class="page-content">
13
13
  {{ content }}
14
+ {%- if page.contact_form %}
15
+ {%- comment -%} The contact and collaboration form, or the email address without a backend {%- endcomment %}
16
+ {% include components/contact-form.html %}
17
+ {%- endif %}
18
+ {%- if page.subscription_manage %}
19
+ {%- comment -%} Where the newsletter's emails lead: confirm, change topics, unsubscribe {%- endcomment %}
20
+ {% include components/subscription-manage.html %}
21
+ {%- endif %}
22
+ {%- if page.subscribe_form %}
23
+ {% include components/subscribe-form.html where='page' %}
24
+ {%- endif %}
25
+ {%- if page.moderation_inbox %}
26
+ {%- comment -%} The moderation inbox: a shell; the service authenticates every request {%- endcomment %}
27
+ {% include components/moderation-inbox.html %}
28
+ {%- endif %}
14
29
  </div>
15
30
  </div>
16
31
  </section>
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  layout: default
3
+ show_title: false # this layout renders the page's <h1>
3
4
  ---
4
5
  <article class="portfolio-item">
5
6
  <header class="portfolio-header">
data/_layouts/post.html CHANGED
@@ -34,8 +34,8 @@ schema_type: TechnicalArticle
34
34
 
35
35
  {%- comment -%} Canonical URL & author info {%- endcomment -%}
36
36
  {% assign canonical_url = page.url | absolute_url %}
37
- {% assign author_name = page.author | default: site.author.name %}
38
- {% assign author_affiliation = page.author_affiliation | default: site.author.affiliation %}
37
+ {% assign authors = page | page_authors %}
38
+ {% assign contributors = page | page_contributors %}
39
39
 
40
40
  {%- comment -%} Tag/Category flags {%- endcomment -%}
41
41
  {% assign post_tags = page.tags %}
@@ -56,8 +56,8 @@ schema_type: TechnicalArticle
56
56
  {% if page.last_modified_at or page.updated %}
57
57
  <meta itemprop="dateModified" content="{{ page.last_modified_at | default: page.updated | date_to_xmlschema }}" />
58
58
  {% endif %}
59
- <meta itemprop="author" content="{{ author_name }}" />
60
- <meta itemprop="publisher" content="{{ site.author.affiliation | default: site.title }}" />
59
+ {% include meta/publisher.html %}
60
+ <meta itemprop="publisher" content="{{ publisher_name | escape }}" />
61
61
  <meta itemprop="timeRequired" content="{{ time_required }}" />
62
62
  {% if difficulty %}<meta itemprop="learningResourceType" content="{{ difficulty }}" />{% endif %}
63
63
  {% if has_tags %}<meta itemprop="keywords" content="{{ post_tags | join: ', ' }}" />{% endif %}
@@ -69,17 +69,28 @@ schema_type: TechnicalArticle
69
69
  <dt class="post-meta-term">{% t 'post.meta.published' %}</dt>
70
70
  <dd class="post-meta-definition">{{ page.date | localize_date: date_format }}</dd>
71
71
  </div>
72
+ {%- comment -%} The last change, when it fell on another day than the publication {%- endcomment -%}
73
+ {% assign modified = page.last_modified_at | default: page.updated %}
74
+ {% if modified %}
75
+ {% assign modified_day = modified | date: '%F' %}
76
+ {% assign published_day = page.date | date: '%F' %}
77
+ {% if modified_day != published_day %}
78
+ <div>
79
+ <dt class="post-meta-term">{% t 'post.meta.updated' %}</dt>
80
+ <dd class="post-meta-definition">{{ modified | localize_date: date_format }}</dd>
81
+ </div>
82
+ {% endif %}
83
+ {% endif %}
72
84
  <div>
73
- <dt class="post-meta-term">{% t 'post.meta.author' %}</dt>
74
- <dd class="post-meta-definition">
75
- <span itemprop="author" itemscope itemtype="https://schema.org/Person">
76
- <span itemprop="name">{{ author_name }}</span>
77
- {% if author_affiliation %}
78
- <span class="post-author-affiliation" itemprop="affiliation">{{ author_affiliation }}</span>
79
- {% endif %}
80
- </span>
81
- </dd>
85
+ <dt class="post-meta-term">{% if authors.size > 1 %}{% t 'post.meta.authors' %}{% else %}{% t 'post.meta.author' %}{% endif %}</dt>
86
+ <dd class="post-meta-definition">{% include components/author-list.html people=authors %}</dd>
82
87
  </div>
88
+ {% if contributors.size > 0 %}
89
+ <div>
90
+ <dt class="post-meta-term">{% t 'post.meta.contributors' %}</dt>
91
+ <dd class="post-meta-definition">{% include components/author-list.html people=contributors property='contributor' %}</dd>
92
+ </div>
93
+ {% endif %}
83
94
  <div>
84
95
  <dt class="post-meta-term">{% t 'post.meta.reading_time' %}</dt>
85
96
  <dd class="post-meta-definition"><span data-reading-time>{{ reading_label }}</span></dd>
@@ -111,6 +122,16 @@ schema_type: TechnicalArticle
111
122
  </dl>
112
123
  </section>
113
124
 
125
+ {%- comment -%} The reader's tools: save for later and reading mode; and the article's address on paper {%- endcomment -%}
126
+ <div class="post-tools">
127
+ {% include components/reading-state-bookmark.html %}
128
+ {% include components/reading-mode-toggle.html %}
129
+ </div>
130
+ <p class="post-print-source print-only">{% t 'post.print.source' %} {{ canonical_url }}{% if page.doi %} · DOI {{ page.doi | escape }}{% endif %}</p>
131
+
132
+ {%- comment -%} The newest correction or update, with a link to the revision history {%- endcomment -%}
133
+ {% include components/revision-notice.html page=page date_format=date_format %}
134
+
114
135
  {% if has_tags or has_categories %}
115
136
  <section class="post-taxonomy" aria-labelledby="post-taxonomy-heading">
116
137
  <h2 id="post-taxonomy-heading" class="visually-hidden">{% t 'post.topics_heading' %}</h2>
@@ -134,6 +155,9 @@ schema_type: TechnicalArticle
134
155
  {%- comment -%} Editorial provenance (why_this_exists, evidence, methodology, reviewed_at) {%- endcomment -%}
135
156
  {% include components/content-provenance.html page=page date_format=date_format %}
136
157
 
158
+ {%- comment -%} The article's series: every part in order, and the previous and next ones {%- endcomment -%}
159
+ {% include components/series-nav.html page=page %}
160
+
137
161
  {%- comment -%} Enhanced social sharing {%- endcomment -%}
138
162
  {% include components/social-share.html url=canonical_url title=page.title description=page.excerpt %}
139
163
 
@@ -148,6 +172,24 @@ schema_type: TechnicalArticle
148
172
  </div>
149
173
  </div>
150
174
 
175
+ {%- comment -%} The previous and next parts of the series, for a reader who has finished this one {%- endcomment -%}
176
+ {% include components/series-nav.html page=page compact=true %}
177
+
178
+ {%- comment -%} Was this useful? A reaction without a comment, on a site with a backend {%- endcomment -%}
179
+ {% include components/reactions.html %}
180
+
181
+ {%- comment -%} The reader's own: resume prompt, highlights and notes, kept in the browser {%- endcomment -%}
182
+ {% include components/reading-state-panel.html %}
183
+
184
+ {%- comment -%} The code, data and environment behind the analysis {%- endcomment -%}
185
+ {% include components/reproducibility.html page=page %}
186
+
187
+ {%- comment -%} Report an error: structured feedback to the author, on a site with a backend {%- endcomment -%}
188
+ {% include components/correction-report.html %}
189
+
190
+ {%- comment -%} What changed since publication (revisions in front matter) {%- endcomment -%}
191
+ {% include components/revision-history.html page=page date_format=date_format %}
192
+
151
193
  <section class="post-interactive" aria-label="{% t 'post.interactive.label' %}">
152
194
  {% if page.interactive_components and page.interactive_components.size > 0 %}
153
195
  <ul class="post-interactive-list">
@@ -195,22 +237,44 @@ schema_type: TechnicalArticle
195
237
  </section>
196
238
  {% endif %}
197
239
 
240
+ {%- comment -%} What other websites said about this article, apart from the comments hosted here {%- endcomment -%}
241
+ {% include components/webmentions.html %}
242
+
243
+ {%- comment -%} Reuse terms: the licence of the text and figures, and of the code samples {%- endcomment -%}
244
+ {% include components/license-notice.html page=page %}
245
+
198
246
  {%- comment -%} ===== Citation tools & badges ===== {%- endcomment -%}
199
247
  {% assign citation_key = page.citation_key | default: page.slug | default: page.title | slugify %}
200
- {% assign citation_authors = page.authors | default: page.author | default: author_name %}
201
248
  {% include components/citation-tools.html
202
249
  title=page.title
203
- authors=citation_authors
250
+ authors=authors
204
251
  date=page.date
205
252
  url=canonical_url
206
253
  citation_key=citation_key
207
- publisher=site.author.affiliation
208
254
  citation_type='article'
209
255
  doi=page.doi %}
210
256
  {% include components/open-science-badges.html %}
211
257
 
212
- {%- comment -%} Author bio card {%- endcomment -%}
213
- {% include components/author-bio.html author=author_name %}
258
+ {%- comment -%}
259
+ Author cards: a full card for a single author, or for each author with
260
+ `author_cards: detailed`; a compact card each for several authors;
261
+ none with `author_cards: false`.
262
+ {%- endcomment -%}
263
+ {% unless page.author_cards == false %}
264
+ {% assign detailed_cards = false %}
265
+ {% if authors.size == 1 or page.author_cards == 'detailed' %}{% assign detailed_cards = true %}{% endif %}
266
+ {% for author in authors %}
267
+ {% if detailed_cards %}
268
+ {% include components/author-bio.html person=author %}
269
+ {% else %}
270
+ {% include components/author-bio.html person=author compact=true %}
271
+ {% endif %}
272
+ {% endfor %}
273
+ {% endunless %}
274
+
275
+ {%- comment -%} The newsletter form at the end of the article, when subscriptions.placement says so {%- endcomment -%}
276
+ {% assign subscribe_placement = site.subscriptions.placement | default: 'footer' %}
277
+ {% if subscribe_placement contains 'post' %}{% include components/subscribe-form.html where='post' %}{% endif %}
214
278
 
215
279
  {%- comment -%} ===== Robust related posts (no where_exp, no nil sort) ===== {%- endcomment -%}
216
280
  {% include post/related-posts.html page=page date_format=date_format %}
@@ -272,7 +336,12 @@ schema_type: TechnicalArticle
272
336
  const metaBar = document.createElement('div');
273
337
  metaBar.className = 'code-block-meta';
274
338
 
275
- const lang = codeBlock.getAttribute('class');
339
+ // Rouge puts the language on the block's wrapper rather than on the code.
340
+ // With line numbers on, the code is a table whose first column holds the
341
+ // numbers, and the copy leaves them out.
342
+ const langHolder = codeBlock.matches('[class*="language-"]') ? codeBlock : codeBlock.closest('.highlighter-rouge');
343
+ const lang = langHolder && langHolder.getAttribute('class');
344
+ const source = codeBlock.querySelector('.rouge-code') || codeBlock;
276
345
  if (lang) {
277
346
  const m = lang.match(/language-([a-z0-9+#]+)/i);
278
347
  if (m) {
@@ -291,7 +360,7 @@ schema_type: TechnicalArticle
291
360
 
292
361
  if (navigator.clipboard && navigator.clipboard.writeText) {
293
362
  button.addEventListener('click', function () {
294
- navigator.clipboard.writeText(codeBlock.textContent).then(function () {
363
+ navigator.clipboard.writeText(source.textContent).then(function () {
295
364
  button.textContent = 'Copied!';
296
365
  button.classList.add('is-copied');
297
366
  setTimeout(function () {
@@ -311,9 +380,19 @@ schema_type: TechnicalArticle
311
380
  wrapper.insertBefore(metaBar, pre);
312
381
  });
313
382
 
314
- // Make equation references focus their targets for accessibility.
315
- if (window.MathJax && window.MathJax.startup) {
316
- window.MathJax.startup.promise.then(function () {
383
+ // Make equation references focus their targets for accessibility. This runs
384
+ // on DOMContentLoaded, before MathJax has loaded, when window.MathJax is
385
+ // still the configuration object and has no startup.promise; reading it
386
+ // threw. The head's MathJax configuration announces typeset math instead.
387
+ const onMathReady = function (callback) {
388
+ if (document.body.classList.contains('math-ready') && document.querySelector('mjx-container')) {
389
+ callback();
390
+ } else {
391
+ document.addEventListener('datalog:math-ready', callback, { once: true });
392
+ }
393
+ };
394
+ if (window.MathJax) {
395
+ onMathReady(function () {
317
396
  const equations = document.querySelectorAll('.post-content mjx-container[display="true"]');
318
397
  let eqIndex = 1;
319
398
  equations.forEach(function (container) {
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  layout: default
3
+ show_title: false # this layout renders the page's <h1>
3
4
  ---
4
5
  {% assign localization_config = site.theme_options.localization %}
5
6
  {% assign date_format = localization_config.date_format | default: 'long' %}
@@ -30,7 +31,7 @@ layout: default
30
31
  {% if page.github %}
31
32
  <section class="project-case__github" aria-labelledby="github-heading">
32
33
  <h2 id="github-heading">{% t 'project.header.github_repository' %}</h2>
33
- <div class="project-case__github-card" data-github-repo="{{ page.github.repo }}" data-github-owner="{{ page.github.owner }}">
34
+ <div class="project-case__github-card" data-github-repo="{{ page.github.repo }}" data-github-owner="{{ page.github.owner }}" data-github-unavailable="{% t 'project.github.unavailable' %}">
34
35
  <div class="project-case__github-meta">
35
36
  <p class="project-case__github-name">
36
37
  <a href="https://github.com/{{ page.github.owner }}/{{ page.github.repo }}" itemprop="url">{{ page.github.owner }}/{{ page.github.repo }}</a>
@@ -208,10 +209,10 @@ layout: default
208
209
  {% endif %}
209
210
 
210
211
  {% assign citation_key = page.citation_key | default: page.slug | default: page.title | slugify %}
211
- {% assign citation_authors = page.authors | default: page.author | default: site.author.name %}
212
+ {% assign citation_authors = page | page_authors %}
212
213
  {% assign citation_date = page.date | default: page.last_updated %}
213
214
  {% assign citation_url = page.url | absolute_url %}
214
- {% include components/citation-tools.html title=page.title authors=citation_authors date=citation_date url=citation_url citation_key=citation_key publisher=site.author.affiliation citation_type='software' doi=page.doi %}
215
+ {% include components/citation-tools.html title=page.title authors=citation_authors date=citation_date url=citation_url citation_key=citation_key citation_type='software' doi=page.doi %}
215
216
 
216
217
  {% include components/open-science-badges.html %}
217
218
 
@@ -3,8 +3,7 @@ layout: default
3
3
  schema_type: ScholarlyArticle
4
4
  ---
5
5
  {% assign date_format = site.date_format | default: '%B %-d, %Y' %}
6
- {% assign authors_source = page.authors | default: page.author | default: site.author %}
7
- {% assign authors = authors_source | arrayify %}
6
+ {% assign authors = page | page_authors %}
8
7
  {% assign publication_name = page.publication | default: page.journal | default: page.conference %}
9
8
  {% assign publication_status = page.publication_status | default: page.status %}
10
9
  {% assign doi = page.doi %}
@@ -20,7 +19,13 @@ schema_type: ScholarlyArticle
20
19
  {% assign methodology_content = page.methodology %}
21
20
  {% assign results_content = page.results %}
22
21
  {% assign data_availability = page.data_availability %}
23
- {% assign reproducibility = page.reproducibility | default: page.reproducibility_notes %}
22
+ {%- comment -%} `reproducibility` is free text here, or a map of artifacts for the panel (components/reproducibility.html). {%- endcomment -%}
23
+ {% capture reproducibility_shape %}{{ page.reproducibility | jsonify | slice: 0, 1 }}{% endcapture %}
24
+ {% if reproducibility_shape == '{' %}
25
+ {% assign reproducibility = page.reproducibility_notes %}
26
+ {% else %}
27
+ {% assign reproducibility = page.reproducibility | default: page.reproducibility_notes %}
28
+ {% endif %}
24
29
  {% assign peer_review = page.peer_review | default: page.peer_review_notes %}
25
30
  {% assign version_history = page.version_history %}
26
31
  {% assign appendices = page.appendices %}
@@ -28,8 +33,8 @@ schema_type: ScholarlyArticle
28
33
  {% assign references = page.references %}
29
34
  {% assign references_array = references | arrayify %}
30
35
  {% assign funding = page.funding %}
31
- {% assign rights = page.rights | default: page.license %}
32
- {% assign affiliation_default = site.author.affiliation %}
36
+ {% assign rights = page.rights %}
37
+ {% assign research_license = page | page_license %}
33
38
  {% assign show_publications = page.show_publications | default: false %}
34
39
 
35
40
  <div class="research-wrapper" itemscope itemtype="https://schema.org/ScholarlyArticle">
@@ -57,9 +62,9 @@ schema_type: ScholarlyArticle
57
62
  <h2 class="visually-hidden">Authors</h2>
58
63
  <ul class="research-author-list">
59
64
  {% for author in authors %}
60
- {% assign author_name = author.name | default: author %}
61
- {% assign author_affiliation = author.affiliation | default: author.institution | default: affiliation_default %}
62
- {% assign author_orcid = author.orcid | default: site.author.orcid %}
65
+ {% assign author_name = author.name | escape %}
66
+ {% assign author_affiliation = author.affiliation | escape %}
67
+ {% assign author_orcid = author.orcid %}
63
68
  {% assign author_email = author.email | default: author.contact %}
64
69
  <li class="research-author" itemprop="author" itemscope itemtype="https://schema.org/Person">
65
70
  <span class="research-author-name" itemprop="name">{{ author_name }}</span>
@@ -125,6 +130,11 @@ schema_type: ScholarlyArticle
125
130
  <dt>Rights</dt>
126
131
  <dd>{{ rights }}</dd>
127
132
  </div>
133
+ {% elsif research_license %}
134
+ <div>
135
+ <dt>Rights</dt>
136
+ <dd>{% include components/license-link.html license=research_license %}</dd>
137
+ </div>
128
138
  {% endif %}
129
139
  </dl>
130
140
 
@@ -321,6 +331,8 @@ ER -
321
331
  </section>
322
332
  {% endif %}
323
333
 
334
+ {% include components/reproducibility.html page=page %}
335
+
324
336
  {% if supplementary or appendices %}
325
337
  <section class="research-appendices" aria-labelledby="appendix-heading">
326
338
  <h2 id="appendix-heading">Appendix and supplementary materials</h2>
@@ -31,7 +31,10 @@ module Datalog
31
31
 
32
32
  data = query_analytics(site)
33
33
  data["fetched_at"] = Time.now.utc.iso8601
34
- write_cache(cache_path, data)
34
+ # Only a successful report is cached. A missing-configuration or error
35
+ # payload used to be kept for CACHE_TTL as well, so a site that had just
36
+ # set GA4_PROPERTY_ID went on reporting the old problem for a day.
37
+ write_cache(cache_path, data) if data["status"] == "ok"
35
38
  data
36
39
  rescue StandardError => e
37
40
  Jekyll.logger.warn("Analytics", "Falling back to cached analytics data: #{e.message}")
@@ -43,7 +46,7 @@ module Datalog
43
46
  end
44
47
 
45
48
  def fresh?(payload)
46
- return false unless payload.is_a?(Hash)
49
+ return false unless payload.is_a?(Hash) && payload["status"] == "ok"
47
50
 
48
51
  fetched_at = payload["fetched_at"]
49
52
  return false if fetched_at.to_s.empty?
@@ -95,7 +98,10 @@ module Datalog
95
98
  results["status"] = "ok"
96
99
  results
97
100
  rescue LoadError => e
98
- fallback_payload("missing_dependency", "Install googleauth to enable GA4 integration: #{e.message}")
101
+ # The theme does not depend on googleauth: only a site with a GA4 property
102
+ # configured needs it, and adds it to its own Gemfile.
103
+ fallback_payload("missing_dependency",
104
+ "Add gem \"googleauth\" to the site's Gemfile to enable the GA4 integration: #{e.message}")
99
105
  rescue StandardError => e
100
106
  fallback_payload("error", e.message)
101
107
  end