datalog-theme 0.10.1 → 0.11.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +127 -0
- data/CITATION.cff +2 -2
- data/README.md +4 -2
- data/_data/i18n/en.yml +55 -3
- data/_data/i18n/es.yml +55 -3
- data/_data/i18n/pt.yml +55 -3
- data/_includes/analytics/dashboard.html +24 -9
- data/_includes/components/academic-dashboard.html +168 -133
- data/_includes/components/author-bio.html +7 -5
- data/_includes/components/author-list.html +6 -5
- data/_includes/components/citation-tools.html +2 -1
- data/_includes/components/content-provenance.html +19 -1
- data/_includes/components/correction-report.html +15 -8
- data/_includes/components/hero.html +22 -15
- data/_includes/components/package-index.html +121 -0
- data/_includes/components/package-install.html +27 -50
- data/_includes/components/post-list.html +14 -0
- data/_includes/components/responsive-image.html +15 -21
- data/_includes/footer.html +1 -1
- data/_includes/head.html +70 -11
- data/_includes/layouts/default/article.html +76 -46
- data/_includes/meta/dataset-json.html +171 -0
- data/_includes/meta/math-config.html +15 -1
- data/_includes/meta/package-json.html +69 -0
- data/_includes/meta/person-json.html +36 -13
- data/_includes/meta/publisher.html +15 -0
- data/_includes/meta/schema.html +108 -23
- data/_includes/meta/scholarly.html +10 -5
- data/_includes/post/related-posts.html +17 -43
- data/_includes/scripts.html +2 -2
- data/_includes/search/index-data.json +10 -1
- data/_includes/search/page.html +11 -6
- data/_layouts/archive.html +67 -0
- data/_layouts/dataset.html +2 -1
- data/_layouts/default.html +13 -11
- data/_layouts/docs.html +53 -0
- data/_layouts/home.html +30 -1
- data/_layouts/notebook.html +5 -1
- data/_layouts/package.html +38 -21
- data/_layouts/portfolio.html +1 -0
- data/_layouts/post.html +21 -62
- data/_layouts/project.html +2 -1
- data/_layouts/research.html +14 -2
- data/_plugins/analytics_dashboard.rb +4 -1
- data/_plugins/archive.rb +114 -0
- data/_plugins/authors.rb +50 -1
- data/_plugins/citation_exports.rb +12 -1
- data/_plugins/config_validator.rb +77 -2
- data/_plugins/correction_fallback.rb +110 -0
- data/_plugins/image_optimizer.rb +129 -22
- data/_plugins/math_preprocessor.rb +7 -48
- data/_plugins/notebook_converter.rb +44 -5
- data/_plugins/packages.rb +84 -0
- data/_plugins/page_dates.rb +37 -0
- data/_plugins/references.rb +16 -2
- data/_plugins/related_posts.rb +149 -0
- data/_plugins/search_sections.rb +110 -0
- data/_plugins/site_identity.rb +42 -0
- data/_plugins/social_cards.rb +17 -0
- data/_sass/_academic-dashboard.scss +18 -30
- data/_sass/_base.scss +2 -0
- data/_sass/_components.scss +21 -23
- data/_sass/_docs.scss +143 -0
- data/_sass/_features.scss +6 -0
- data/_sass/_layout.scss +65 -6
- data/_sass/_mathematical.scss +61 -19
- data/_sass/_notebooks.scss +1 -32
- data/_sass/_package-docs.scss +6 -9
- data/_sass/_post-components.scss +23 -23
- data/_sass/_print.scss +1 -1
- data/_sass/_search-page.scss +75 -0
- data/_sass/_search.scss +17 -26
- data/_sass/_syntax-highlighting.scss +23 -1
- data/_sass/_theme.scss +1 -0
- data/_sass/_utilities.scss +87 -40
- data/_sass/_variables.scss +31 -29
- data/assets/css/main.scss +2 -1
- data/assets/js/dist/academic.js +1 -1
- data/assets/js/dist/analytics-dashboard.js +1 -1
- data/assets/js/dist/chunks/chunk-TNVD6UAM.js +1 -0
- data/assets/js/dist/comments.js +1 -1
- data/assets/js/dist/contact.js +1 -1
- data/assets/js/dist/core.js +1 -1
- data/assets/js/dist/corrections.js +5 -1
- data/assets/js/dist/loader.js +1 -1
- data/assets/js/dist/math.js +2 -1
- data/assets/js/dist/moderation.js +1 -1
- data/assets/js/dist/reactions.js +1 -1
- data/assets/js/dist/search.js +1 -1
- data/assets/js/dist/sources.json +20 -16
- data/assets/js/dist/subscriptions.js +1 -1
- data/assets/js/loader.js +6 -2
- data/lib/datalog/audit/checks.rb +269 -0
- data/lib/datalog/audit/known_keys.rb +61 -0
- data/lib/datalog/audit/site_reader.rb +118 -0
- data/lib/datalog/audit/source_file.rb +85 -0
- data/lib/datalog/audit.rb +145 -0
- data/lib/datalog/citations/bibtex.rb +200 -0
- data/lib/datalog/citations/entry.rb +333 -0
- data/lib/datalog/citations/markup.rb +79 -0
- data/lib/datalog/cli.rb +164 -89
- data/lib/datalog/critical_css.rb +126 -21
- data/lib/datalog/latex_speech/words.json +88 -0
- data/lib/datalog/latex_speech.rb +355 -0
- data/lib/datalog/packages/command.rb +51 -0
- data/lib/datalog/packages/refresh.rb +187 -0
- data/lib/datalog/packages.rb +177 -0
- data/lib/datalog/plugin_system.rb +5 -0
- data/lib/datalog/plugins/citations.rb +325 -109
- data/lib/datalog/site_config.rb +26 -0
- data/lib/datalog/social_cards/font.rb +286 -0
- data/lib/datalog/social_cards/fonts/IBMPlexSans-Regular.ttf +0 -0
- data/lib/datalog/social_cards/fonts/IBMPlexSerif-SemiBold.ttf +0 -0
- data/lib/datalog/social_cards/fonts/OFL.txt +92 -0
- data/lib/datalog/social_cards/geometry.rb +51 -0
- data/lib/datalog/social_cards/outline.rb +54 -0
- data/lib/datalog/social_cards/plain_text.rb +258 -0
- data/lib/datalog/social_cards/template.rb +322 -0
- data/lib/datalog/social_cards/template.svg +17 -0
- data/lib/datalog/social_cards/typesetter.rb +178 -0
- data/lib/datalog/social_cards.rb +369 -0
- data/lib/datalog/theme/updater.rb +307 -0
- data/lib/datalog/theme/version.rb +1 -1
- metadata +44 -7
- data/_includes/components/enhanced-code-block.html +0 -212
- data/_includes/components/performance-monitor.html +0 -170
- data/_includes/components/viz-table-fallback.html +0 -19
- data/_plugins/datalog_bibliography.rb +0 -21
- data/assets/js/dist/chunks/chunk-WIRUK3OZ.js +0 -1
data/_layouts/notebook.html
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
layout: default
|
|
3
3
|
show_title: false # this layout renders the page's <h1>
|
|
4
|
+
article_meta: false # the default heading adds no date or byline above this layout's own header
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
{%- comment -%}
|
|
@@ -24,10 +25,13 @@ show_title: false # this layout renders the page's <h1>
|
|
|
24
25
|
<p class="notebook-article__summary">{{ page.summary | default: page.excerpt }}</p>
|
|
25
26
|
|
|
26
27
|
<ul class="notebook-article__meta">
|
|
28
|
+
{%- assign notebook_published = page | published_date %}
|
|
29
|
+
{%- if notebook_published %}
|
|
27
30
|
<li>
|
|
28
31
|
<span>{% t 'notebook.meta.published' %}</span>
|
|
29
|
-
<time datetime="{{
|
|
32
|
+
<time datetime="{{ notebook_published | date_to_xmlschema }}" itemprop="datePublished">{{ notebook_published | localize_date: date_format }}</time>
|
|
30
33
|
</li>
|
|
34
|
+
{%- endif %}
|
|
31
35
|
|
|
32
36
|
{% if page.last_modified_at %}
|
|
33
37
|
<li>
|
data/_layouts/package.html
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
layout: default
|
|
3
3
|
show_title: false # this layout renders the page's <h1>
|
|
4
|
+
article_meta: false # the default heading adds no date or byline above this layout's own header
|
|
4
5
|
---
|
|
5
6
|
|
|
7
|
+
{%- comment -%}
|
|
8
|
+
The latest release: from _data/package_releases.yml, which
|
|
9
|
+
`datalog packages refresh` writes from the package's registries, else from
|
|
10
|
+
the front matter. The registries' pages and docs.rs come from `registry:`.
|
|
11
|
+
{%- endcomment -%}
|
|
12
|
+
{%- assign release = page | package_release -%}
|
|
13
|
+
{%- assign registry_links = page | package_links -%}
|
|
6
14
|
<div class="package-docs">
|
|
7
15
|
{%- comment -%} Package Header {%- endcomment -%}
|
|
8
16
|
<div class="package-docs__header">
|
|
@@ -20,33 +28,42 @@ show_title: false # this layout renders the page's <h1>
|
|
|
20
28
|
</div>
|
|
21
29
|
|
|
22
30
|
<div class="package-docs__meta">
|
|
23
|
-
{% if
|
|
24
|
-
<div class="package-docs__version">
|
|
25
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
31
|
+
{% if release.version %}
|
|
32
|
+
<div class="package-docs__version" data-package-version>
|
|
33
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
26
34
|
<circle cx="12" cy="12" r="10"></circle>
|
|
27
35
|
<polyline points="12 6 12 12 16 14"></polyline>
|
|
28
36
|
</svg>
|
|
29
|
-
<span>v{{
|
|
37
|
+
<span>v{{ release.version | escape }}{% if release.prerelease %} · {% t 'packages.prerelease' %}{% endif %}{% if release.yanked %} · {% t 'packages.yanked' %}{% endif %}</span>
|
|
38
|
+
{%- if release.released %}
|
|
39
|
+
<span>· <time datetime="{{ release.released | escape }}">{{ release.released | localize_date: 'long' }}</time></span>
|
|
40
|
+
{%- endif %}
|
|
30
41
|
</div>
|
|
31
42
|
{% endif %}
|
|
32
43
|
|
|
33
|
-
{% if page.language %}
|
|
44
|
+
{% if release.requirement or page.language %}
|
|
34
45
|
<div class="package-docs__language">
|
|
35
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
46
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
36
47
|
<polyline points="16 18 22 12 16 6"></polyline>
|
|
37
48
|
<polyline points="8 6 2 12 8 18"></polyline>
|
|
38
49
|
</svg>
|
|
39
|
-
<span>{{ page.language }}</span>
|
|
50
|
+
<span>{{ release.requirement | default: page.language | escape }}</span>
|
|
40
51
|
</div>
|
|
41
52
|
{% endif %}
|
|
42
53
|
|
|
43
|
-
{% if
|
|
54
|
+
{% if release.license %}
|
|
44
55
|
<div class="package-docs__license">
|
|
45
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
56
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
46
57
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
|
47
58
|
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
|
48
59
|
</svg>
|
|
49
|
-
<span>{{
|
|
60
|
+
<span>{{ release.license | escape }}</span>
|
|
61
|
+
</div>
|
|
62
|
+
{% endif %}
|
|
63
|
+
|
|
64
|
+
{% if release.status %}
|
|
65
|
+
<div class="package-docs__status">
|
|
66
|
+
<span>{{ release.status | capitalize | escape }}</span>
|
|
50
67
|
</div>
|
|
51
68
|
{% endif %}
|
|
52
69
|
</div>
|
|
@@ -61,22 +78,22 @@ show_title: false # this layout renders the page's <h1>
|
|
|
61
78
|
</a>
|
|
62
79
|
{% endif %}
|
|
63
80
|
|
|
64
|
-
{
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
{%- comment -%} PyPI, crates.io and docs.rs, RubyGems, CRAN, conda-forge, npm, JuliaHub {%- endcomment -%}
|
|
82
|
+
{% for link in registry_links %}
|
|
83
|
+
<a href="{{ link.url | escape }}" class="package-docs__action-btn" target="_blank" rel="noopener" data-registry="{{ link.registry }}">
|
|
84
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
67
85
|
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
|
68
86
|
</svg>
|
|
69
|
-
<span>
|
|
87
|
+
<span>{{ link.label }}</span>
|
|
70
88
|
</a>
|
|
71
|
-
{%
|
|
89
|
+
{% endfor %}
|
|
72
90
|
|
|
73
|
-
{% if page.
|
|
74
|
-
<a href="{{ page.
|
|
75
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
76
|
-
<
|
|
77
|
-
<text x="12" y="16" font-size="12" text-anchor="middle" fill="currentColor">R</text>
|
|
91
|
+
{% if page.docs_url %}
|
|
92
|
+
<a href="{{ page.docs_url | escape }}" class="package-docs__action-btn" target="_blank" rel="noopener" data-registry="docs">
|
|
93
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
94
|
+
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V3H6.5A2.5 2.5 0 0 0 4 5.5z"></path>
|
|
78
95
|
</svg>
|
|
79
|
-
<span>
|
|
96
|
+
<span>{% t 'packages.documentation' %}</span>
|
|
80
97
|
</a>
|
|
81
98
|
{% endif %}
|
|
82
99
|
</div>
|
data/_layouts/portfolio.html
CHANGED
data/_layouts/post.html
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
layout: default
|
|
3
3
|
schema_type: TechArticle
|
|
4
|
+
article_meta: false # the default heading adds no date or byline above this layout's own header
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
{% assign localization_config = site.theme_options.localization %}
|
|
@@ -48,6 +49,15 @@ schema_type: TechArticle
|
|
|
48
49
|
{% assign has_categories = false %}
|
|
49
50
|
{% if page.categories and page.categories.size > 0 %}{% assign has_categories = true %}{% endif %}
|
|
50
51
|
|
|
52
|
+
{%- comment -%}
|
|
53
|
+
Where the author and editorial note goes: after the article by default
|
|
54
|
+
(theme_options.provenance.position: end), or before it with `start`. A page
|
|
55
|
+
chooses for itself with provenance_position in its front matter.
|
|
56
|
+
{%- endcomment -%}
|
|
57
|
+
{% assign provenance_position = page.provenance_position | default: site.theme_options.provenance.position | default: 'end' %}
|
|
58
|
+
{% assign provenance_first = false %}
|
|
59
|
+
{% if provenance_position == 'start' %}{% assign provenance_first = true %}{% endif %}
|
|
60
|
+
|
|
51
61
|
{%- comment -%} ISO 8601 duration for reading time {%- endcomment -%}
|
|
52
62
|
{% assign time_required = 'PT' | append: reading_minutes | append: 'M' %}
|
|
53
63
|
|
|
@@ -55,6 +65,7 @@ schema_type: TechArticle
|
|
|
55
65
|
{% include components/breadcrumbs.html %}
|
|
56
66
|
|
|
57
67
|
<div class="post-wrapper" itemscope itemtype="https://schema.org/TechArticle">
|
|
68
|
+
<meta itemprop="headline" content="{{ page.title | escape }}" />
|
|
58
69
|
<meta itemprop="url" content="{{ canonical_url }}" />
|
|
59
70
|
<meta itemprop="datePublished" content="{{ page.date | date_to_xmlschema }}" />
|
|
60
71
|
{% if page.last_modified_at or page.updated %}
|
|
@@ -123,6 +134,8 @@ schema_type: TechArticle
|
|
|
123
134
|
</dd>
|
|
124
135
|
</div>
|
|
125
136
|
{% endif %}
|
|
137
|
+
{%- comment -%} With the note after the article, the metadata says there is one, and when the article was reviewed {%- endcomment -%}
|
|
138
|
+
{% unless provenance_first %}{% include components/content-provenance.html page=page date_format=date_format pointer=true %}{% endunless %}
|
|
126
139
|
</dl>
|
|
127
140
|
</section>
|
|
128
141
|
|
|
@@ -157,8 +170,8 @@ schema_type: TechArticle
|
|
|
157
170
|
</section>
|
|
158
171
|
{% endif %}
|
|
159
172
|
|
|
160
|
-
{%- comment -%} Editorial provenance (why_this_exists, evidence, methodology, reviewed_at) {%- endcomment -%}
|
|
161
|
-
{% include components/content-provenance.html page=page date_format=date_format %}
|
|
173
|
+
{%- comment -%} Editorial provenance (why_this_exists, evidence, methodology, reviewed_at), here with provenance position `start` {%- endcomment -%}
|
|
174
|
+
{% if provenance_first %}{% include components/content-provenance.html page=page date_format=date_format %}{% endif %}
|
|
162
175
|
|
|
163
176
|
{%- comment -%} The article's series: every part in order, and the previous and next ones {%- endcomment -%}
|
|
164
177
|
{% include components/series-nav.html page=page %}
|
|
@@ -189,6 +202,9 @@ schema_type: TechArticle
|
|
|
189
202
|
{%- comment -%} The code, data and environment behind the analysis {%- endcomment -%}
|
|
190
203
|
{% include components/reproducibility.html page=page %}
|
|
191
204
|
|
|
205
|
+
{%- comment -%} How the article was made: after it, by default, as the apparatus that follows the text {%- endcomment -%}
|
|
206
|
+
{% unless provenance_first %}{% include components/content-provenance.html page=page date_format=date_format %}{% endunless %}
|
|
207
|
+
|
|
192
208
|
{%- comment -%} Report an error: structured feedback to the author, on a site with a backend {%- endcomment -%}
|
|
193
209
|
{% include components/correction-report.html %}
|
|
194
210
|
|
|
@@ -281,8 +297,9 @@ schema_type: TechArticle
|
|
|
281
297
|
{% assign subscribe_placement = site.subscriptions.placement | default: 'footer' %}
|
|
282
298
|
{% if subscribe_placement contains 'post' %}{% include components/subscribe-form.html where='post' %}{% endif %}
|
|
283
299
|
|
|
284
|
-
{%-
|
|
285
|
-
|
|
300
|
+
{%- unless page.related_posts == false -%}
|
|
301
|
+
{% include post/related-posts.html page=page date_format=date_format %}
|
|
302
|
+
{%- endunless -%}
|
|
286
303
|
|
|
287
304
|
<nav class="post-pagination" aria-label="Post navigation">
|
|
288
305
|
{% if page.previous %}
|
|
@@ -326,65 +343,7 @@ schema_type: TechArticle
|
|
|
326
343
|
</div>
|
|
327
344
|
|
|
328
345
|
<script nonce="{{ page.csp_nonce }}">
|
|
329
|
-
// Enhance code blocks with a copy button and language badge.
|
|
330
346
|
document.addEventListener('DOMContentLoaded', function () {
|
|
331
|
-
const codeBlocks = document.querySelectorAll('.post-content pre > code');
|
|
332
|
-
codeBlocks.forEach(function (codeBlock) {
|
|
333
|
-
const pre = codeBlock.parentElement;
|
|
334
|
-
if (!pre || pre.querySelector('.code-copy')) return;
|
|
335
|
-
|
|
336
|
-
const wrapper = document.createElement('div');
|
|
337
|
-
wrapper.className = 'code-block-wrapper';
|
|
338
|
-
pre.parentNode.insertBefore(wrapper, pre);
|
|
339
|
-
wrapper.appendChild(pre);
|
|
340
|
-
|
|
341
|
-
const metaBar = document.createElement('div');
|
|
342
|
-
metaBar.className = 'code-block-meta';
|
|
343
|
-
|
|
344
|
-
// Rouge puts the language on the block's wrapper rather than on the code.
|
|
345
|
-
// With line numbers on, the code is a table whose first column holds the
|
|
346
|
-
// numbers, and the copy leaves them out.
|
|
347
|
-
const langHolder = codeBlock.matches('[class*="language-"]') ? codeBlock : codeBlock.closest('.highlighter-rouge');
|
|
348
|
-
const lang = langHolder && langHolder.getAttribute('class');
|
|
349
|
-
const source = codeBlock.querySelector('.rouge-code') || codeBlock;
|
|
350
|
-
if (lang) {
|
|
351
|
-
const m = lang.match(/language-([a-z0-9+#]+)/i);
|
|
352
|
-
if (m) {
|
|
353
|
-
const langBadge = document.createElement('span');
|
|
354
|
-
langBadge.className = 'code-language';
|
|
355
|
-
langBadge.textContent = m[1].toUpperCase();
|
|
356
|
-
metaBar.appendChild(langBadge);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const button = document.createElement('button');
|
|
361
|
-
button.type = 'button';
|
|
362
|
-
button.className = 'code-copy';
|
|
363
|
-
button.setAttribute('aria-label', 'Copy code to clipboard');
|
|
364
|
-
button.textContent = 'Copy';
|
|
365
|
-
|
|
366
|
-
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
367
|
-
button.addEventListener('click', function () {
|
|
368
|
-
navigator.clipboard.writeText(source.textContent).then(function () {
|
|
369
|
-
button.textContent = 'Copied!';
|
|
370
|
-
button.classList.add('is-copied');
|
|
371
|
-
setTimeout(function () {
|
|
372
|
-
button.textContent = 'Copy';
|
|
373
|
-
button.classList.remove('is-copied');
|
|
374
|
-
}, 2000);
|
|
375
|
-
}).catch(function () {
|
|
376
|
-
button.textContent = 'Error';
|
|
377
|
-
});
|
|
378
|
-
});
|
|
379
|
-
} else {
|
|
380
|
-
button.disabled = true;
|
|
381
|
-
button.title = 'Clipboard copying is not supported in this browser';
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
metaBar.appendChild(button);
|
|
385
|
-
wrapper.insertBefore(metaBar, pre);
|
|
386
|
-
});
|
|
387
|
-
|
|
388
347
|
// Make equation references focus their targets for accessibility. This runs
|
|
389
348
|
// on DOMContentLoaded, before MathJax has loaded, when window.MathJax is
|
|
390
349
|
// still the configuration object and has no startup.promise; reading it
|
data/_layouts/project.html
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
layout: default
|
|
3
3
|
show_title: false # this layout renders the page's <h1>
|
|
4
|
+
article_meta: false # the default heading adds no date or byline above this layout's own header
|
|
4
5
|
---
|
|
5
6
|
{% assign localization_config = site.theme_options.localization %}
|
|
6
7
|
{% assign date_format = localization_config.date_format | default: 'long' %}
|
|
@@ -210,7 +211,7 @@ show_title: false # this layout renders the page's <h1>
|
|
|
210
211
|
|
|
211
212
|
{% assign citation_key = page.citation_key | default: page.slug | default: page.title | slugify %}
|
|
212
213
|
{% assign citation_authors = page | page_authors %}
|
|
213
|
-
{% assign citation_date = page
|
|
214
|
+
{% assign citation_date = page | published_date | default: page.last_updated %}
|
|
214
215
|
{% assign citation_url = page.url | absolute_url %}
|
|
215
216
|
{% 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 %}
|
|
216
217
|
|
data/_layouts/research.html
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
layout: default
|
|
3
3
|
schema_type: ScholarlyArticle
|
|
4
|
+
show_title: false # this layout renders the page's <h1>
|
|
5
|
+
article_meta: false # the default heading adds no date or byline above this layout's own header
|
|
4
6
|
---
|
|
5
7
|
{% assign date_format = site.date_format | default: '%B %-d, %Y' %}
|
|
6
8
|
{% assign authors = page | page_authors %}
|
|
@@ -32,6 +34,7 @@ schema_type: ScholarlyArticle
|
|
|
32
34
|
{% assign supplementary = page.supplementary | default: page.supplementary_materials %}
|
|
33
35
|
{% assign references = page.references %}
|
|
34
36
|
{% assign references_array = references | arrayify %}
|
|
37
|
+
{% capture bibliography_label %}{% t 'post.bibliography.label' %}{% endcapture %}
|
|
35
38
|
{% assign funding = page.funding %}
|
|
36
39
|
{% assign rights = page.rights %}
|
|
37
40
|
{% assign research_license = page | page_license %}
|
|
@@ -57,6 +60,9 @@ schema_type: ScholarlyArticle
|
|
|
57
60
|
|
|
58
61
|
<header class="research-header" aria-labelledby="research-title">
|
|
59
62
|
<h1 id="research-title" class="research-title">{{ page.title | escape }}</h1>
|
|
63
|
+
{%- if page.summary %}
|
|
64
|
+
<p class="article-summary">{{ page.summary | strip_html | escape }}</p>
|
|
65
|
+
{%- endif %}
|
|
60
66
|
|
|
61
67
|
<div class="research-authors" aria-label="Authors">
|
|
62
68
|
<h2 class="visually-hidden">Authors</h2>
|
|
@@ -73,7 +79,7 @@ schema_type: ScholarlyArticle
|
|
|
73
79
|
{% endif %}
|
|
74
80
|
{% if author_orcid %}
|
|
75
81
|
<span class="research-author-orcid">
|
|
76
|
-
<a href="{{ author_orcid }}" target="_blank" rel="
|
|
82
|
+
<a href="{{ author_orcid }}" target="_blank" rel="{{ author | author_profile_rel | escape }}" itemprop="identifier" aria-label="ORCID profile for {{ author_name }}">
|
|
77
83
|
ORCID
|
|
78
84
|
</a>
|
|
79
85
|
</span>
|
|
@@ -302,7 +308,13 @@ schema_type: ScholarlyArticle
|
|
|
302
308
|
</section>
|
|
303
309
|
{% endif %}
|
|
304
310
|
|
|
305
|
-
{
|
|
311
|
+
{%- comment -%} The works the article cites with {% raw %}{% cite %}{% endraw %}; a hand-written `references:` list otherwise {%- endcomment -%}
|
|
312
|
+
{% if page.datalog_bibliography and page.datalog_bibliography.size > 0 %}
|
|
313
|
+
<section class="research-references" aria-labelledby="references-heading">
|
|
314
|
+
<h2 id="references-heading">{{ page.datalog_bibliography_title | default: bibliography_label }}</h2>
|
|
315
|
+
{% datalog_bibliography %}
|
|
316
|
+
</section>
|
|
317
|
+
{% elsif references_array and references_array.size > 0 %}
|
|
306
318
|
<section class="research-references" aria-labelledby="references-heading">
|
|
307
319
|
<h2 id="references-heading">References</h2>
|
|
308
320
|
{% assign references_markup = references_array | join: '' %}
|
|
@@ -25,12 +25,15 @@ module Datalog
|
|
|
25
25
|
module_function
|
|
26
26
|
|
|
27
27
|
def fetch(site)
|
|
28
|
+
cached = nil
|
|
28
29
|
cache_path = cache_path_for(site)
|
|
29
30
|
cached = read_cache(cache_path)
|
|
30
31
|
return cached if fresh?(cached)
|
|
31
32
|
|
|
32
33
|
data = query_analytics(site)
|
|
33
34
|
data["fetched_at"] = Time.now.utc.iso8601
|
|
35
|
+
return cached.merge("stale" => true) if data["status"] == "error" && cached&.fetch("status", nil) == "ok"
|
|
36
|
+
|
|
34
37
|
# Only a successful report is cached. A missing-configuration or error
|
|
35
38
|
# payload used to be kept for CACHE_TTL as well, so a site that had just
|
|
36
39
|
# set GA4_PROPERTY_ID went on reporting the old problem for a day.
|
|
@@ -38,7 +41,7 @@ module Datalog
|
|
|
38
41
|
data
|
|
39
42
|
rescue StandardError => e
|
|
40
43
|
Jekyll.logger.warn("Analytics", "Falling back to cached analytics data: #{e.message}")
|
|
41
|
-
cached
|
|
44
|
+
cached&.fetch("status", nil) == "ok" ? cached.merge("stale" => true) : fallback_payload("error", e.message)
|
|
42
45
|
end
|
|
43
46
|
|
|
44
47
|
def cache_path_for(site)
|
data/_plugins/archive.rb
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datalog
|
|
4
|
+
module Archive
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def build(site, page)
|
|
8
|
+
mode = page["archive"].to_s
|
|
9
|
+
limit = (page["archive_limit"] || site.config["archive_limit"]).to_i
|
|
10
|
+
limit = 20 if limit <= 0
|
|
11
|
+
posts = site.posts.docs.sort_by { |post| [-post.date.to_i, post.url] }
|
|
12
|
+
|
|
13
|
+
case mode
|
|
14
|
+
when "year" then years(posts, page, limit)
|
|
15
|
+
when "tag" then terms(posts, "tags", limit)
|
|
16
|
+
when "category" then terms(posts, "categories", limit)
|
|
17
|
+
when "facet" then facet(posts, page, limit)
|
|
18
|
+
when "topic" then topic(posts, page, limit)
|
|
19
|
+
else
|
|
20
|
+
[]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def years(posts, page, limit)
|
|
25
|
+
groups = grouped(posts, limit) { |post| [[post.date.year.to_s, post.date.year.to_s]] }
|
|
26
|
+
groups.sort_by! { |group| -group["name"].to_i }
|
|
27
|
+
return groups unless page["archive_months"]
|
|
28
|
+
|
|
29
|
+
groups.each do |year|
|
|
30
|
+
year_posts = posts.select { |post| post.date.year.to_s == year["name"] }
|
|
31
|
+
months = grouped(year_posts, limit) do |post|
|
|
32
|
+
[[post.date.strftime("%Y-%m"), post.date.strftime("%Y-%m")]]
|
|
33
|
+
end
|
|
34
|
+
year["months"] = months.sort_by { |month| month["name"] }.reverse
|
|
35
|
+
end
|
|
36
|
+
groups
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def terms(posts, field, limit)
|
|
40
|
+
groups = grouped(posts, limit) do |post|
|
|
41
|
+
Array(post.data[field]).map { |term| [term.to_s, term.to_s] }
|
|
42
|
+
end
|
|
43
|
+
groups.sort_by { |group| group["name"].downcase }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def facet(posts, page, limit)
|
|
47
|
+
field = page["archive_field"].to_s
|
|
48
|
+
wanted = Array(page["archive_terms"]).map(&:to_s)
|
|
49
|
+
groups = grouped(posts, limit) do |post|
|
|
50
|
+
Array(post.data[field]).map { |term| [term.to_s, term.to_s] }.select do |name, _|
|
|
51
|
+
wanted.any? { |given| given.casecmp?(name) }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
wanted.filter_map { |term| groups.find { |group| group["name"].casecmp?(term) } }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def grouped(posts, limit)
|
|
58
|
+
groups = {}
|
|
59
|
+
posts.each do |post|
|
|
60
|
+
yield(post).each do |name, label|
|
|
61
|
+
slug = Jekyll::Utils.slugify(name)
|
|
62
|
+
next if slug.empty?
|
|
63
|
+
|
|
64
|
+
group = (groups[slug] ||= { "name" => label, "slug" => slug, "posts" => [] })
|
|
65
|
+
group["posts"] << post unless group["posts"].include?(post)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
groups.each_value do |group|
|
|
69
|
+
group["count"] = group["posts"].size
|
|
70
|
+
group["remaining"] = group["posts"].drop(limit)
|
|
71
|
+
group["posts"] = group["posts"].first(limit)
|
|
72
|
+
end
|
|
73
|
+
groups.values
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def topic(posts, page, limit)
|
|
77
|
+
config = page["topic"] || {}
|
|
78
|
+
tags = Array(config["tags"]).map { |value| value.to_s.downcase }
|
|
79
|
+
categories = Array(config["categories"]).map { |value| value.to_s.downcase }
|
|
80
|
+
featured = Array(config["featured"]).map(&:to_s)
|
|
81
|
+
matches = posts.select { |post| topic_match?(post, tags, categories) }
|
|
82
|
+
series, loose = matches.partition do |post|
|
|
83
|
+
post.data["series"].is_a?(Hash) && post.data["series"]["id"]
|
|
84
|
+
end
|
|
85
|
+
series = topic_series(series, featured)
|
|
86
|
+
loose.sort_by! { |post| [featured.include?(post.url) ? 0 : 1, -post.date.to_i, post.url] }
|
|
87
|
+
{ "series" => series, "posts" => loose.first(limit), "remaining" => loose.drop(limit),
|
|
88
|
+
"count" => matches.size }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def topic_match?(post, tags, categories)
|
|
92
|
+
Array(post.data["tags"]).map { |value| value.to_s.downcase }.intersect?(tags) ||
|
|
93
|
+
Array(post.data["categories"]).map { |value| value.to_s.downcase }.intersect?(categories)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def topic_series(posts, featured)
|
|
97
|
+
groups = posts.group_by { |post| post.data["series"]["id"] }
|
|
98
|
+
series = groups.map do |_id, parts|
|
|
99
|
+
data = parts.first.data["series"]
|
|
100
|
+
{ "title" => data["title"], "url" => parts.first.url,
|
|
101
|
+
"count" => parts.size, "featured" => parts.any? { |post| featured.include?(post.url) } }
|
|
102
|
+
end
|
|
103
|
+
series.sort_by { |item| [item["featured"] ? 0 : 1, item["title"].to_s.downcase] }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
module ArchiveFilters
|
|
108
|
+
def archive_data(page)
|
|
109
|
+
Archive.build(@context.registers[:site], page)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
Liquid::Template.register_filter(Datalog::ArchiveFilters)
|
data/_plugins/authors.rb
CHANGED
|
@@ -22,6 +22,8 @@ module Datalog
|
|
|
22
22
|
}.freeze
|
|
23
23
|
PROFILE_LINKS = %w[researchgate google_scholar].freeze
|
|
24
24
|
ORCID_ID = /\A\d{4}-\d{4}-\d{4}-\d{3}[\dX]\z/
|
|
25
|
+
# Profile links open in a new tab; no `profile_rel` setting removes these.
|
|
26
|
+
SAFE_REL = %w[noopener noreferrer].freeze
|
|
25
27
|
|
|
26
28
|
# `authors:`, else `author:`, else the site's author.
|
|
27
29
|
def authors(page, site)
|
|
@@ -94,6 +96,10 @@ module Datalog
|
|
|
94
96
|
record["affiliation"] ||= record["institution"]
|
|
95
97
|
record["bio"] ||= record["biography"]
|
|
96
98
|
record["image"] ||= record["avatar"] || record["photo"]
|
|
99
|
+
# What the JSON-LD Person says about them besides the name, each a list.
|
|
100
|
+
record["alternate_names"] = list(record, "alternate_names", "alternate_name")
|
|
101
|
+
record["job_titles"] = list(record, "job_title", "roles")
|
|
102
|
+
record["knows_about"] = list(record, "knows_about", "expertise", "research_areas")
|
|
97
103
|
record["orcid"] = "https://orcid.org/#{record['orcid']}" if record["orcid"].to_s.match?(ORCID_ID)
|
|
98
104
|
record["site_author"] = record["name"] == site_author_profile(site)&.fetch("name")
|
|
99
105
|
PROFILE_URLS.each do |key, template|
|
|
@@ -107,11 +113,44 @@ module Datalog
|
|
|
107
113
|
record
|
|
108
114
|
end
|
|
109
115
|
|
|
116
|
+
# The `rel` of a person's profile links. The site's own author gets `me`,
|
|
117
|
+
# which says the profile belongs to whoever owns the site: the relationship
|
|
118
|
+
# the JSON-LD `sameAs` states, in the form IndieAuth and Mastodon's
|
|
119
|
+
# verified links read. Anyone else gets it only when their record sets
|
|
120
|
+
# `profile_rel`, so a guest is never claimed as the site's owner. A record's
|
|
121
|
+
# `profile_rel` replaces `me`, and `author.profile_rel` in _config.yml does
|
|
122
|
+
# so for the site author; neither can drop noopener or noreferrer.
|
|
123
|
+
def profile_rel(person, site)
|
|
124
|
+
record = person.is_a?(String) ? { "name" => person } : present(person)
|
|
125
|
+
owner = site_author?(record, site)
|
|
126
|
+
configured = record["profile_rel"]
|
|
127
|
+
configured = site_author_profile(site)&.fetch("profile_rel", nil) if configured.nil? && owner
|
|
128
|
+
tokens = configured.to_s.downcase.split
|
|
129
|
+
tokens = ["me"] if configured.nil? && owner
|
|
130
|
+
(tokens + SAFE_REL).uniq.join(" ")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# A record from `page_authors` knows; a raw data record is compared by name.
|
|
134
|
+
def site_author?(record, site)
|
|
135
|
+
return record["site_author"] == true if record.key?("site_author")
|
|
136
|
+
|
|
137
|
+
name = site_author_profile(site)&.fetch("name")
|
|
138
|
+
!name.nil? && record["name"] == name
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# The profiles the record names, then any other address it lists in `same_as`.
|
|
110
142
|
def profiles(record)
|
|
111
143
|
urls = [record["orcid"]]
|
|
112
144
|
urls += PROFILE_URLS.map { |key, template| format(template, record[key]) if record[key] }
|
|
113
145
|
urls += PROFILE_LINKS.map { |key| record[key] }
|
|
114
|
-
urls
|
|
146
|
+
urls += Array(record["same_as"])
|
|
147
|
+
urls.compact.map { |url| url.to_s.strip }.reject(&:empty?).uniq
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# The first of `keys` the record sets, as a list of its non-blank values.
|
|
151
|
+
def list(record, *keys)
|
|
152
|
+
found = keys.map { |key| record[key] }.compact.first
|
|
153
|
+
Array(found).map { |item| item.to_s.strip }.reject(&:empty?)
|
|
115
154
|
end
|
|
116
155
|
|
|
117
156
|
# `author_affiliation` in front matter names the affiliation of a page's
|
|
@@ -145,6 +184,16 @@ module Datalog
|
|
|
145
184
|
def page_contributors(page)
|
|
146
185
|
Authors.contributors(page, @context["site"])
|
|
147
186
|
end
|
|
187
|
+
|
|
188
|
+
def author_profile_rel(person)
|
|
189
|
+
Authors.profile_rel(person, @context["site"])
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# The site's own author as one full record, whatever the input:
|
|
193
|
+
# `{% assign owner = site | site_author_record %}`.
|
|
194
|
+
def site_author_record(_input = nil)
|
|
195
|
+
Authors.site_author(@context["site"])
|
|
196
|
+
end
|
|
148
197
|
end
|
|
149
198
|
end
|
|
150
199
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "page_dates"
|
|
4
|
+
|
|
3
5
|
module Datalog
|
|
4
6
|
module CitationExports
|
|
5
7
|
module_function
|
|
@@ -79,7 +81,7 @@ module Datalog
|
|
|
79
81
|
key = slugify(get.call("citation_key") || Authors.value(page, "slug") || title)
|
|
80
82
|
kind = get.call("citation_type") || Authors.value(options, "type") || "article"
|
|
81
83
|
record = {
|
|
82
|
-
"title" => title, "year" => (
|
|
84
|
+
"title" => title, "year" => citation_year(page, options),
|
|
83
85
|
"publisher" => publisher, "journal" => get.call("journal") || get.call("publication"),
|
|
84
86
|
"doi" => get.call("doi"), "url" => absolute_url(get.call("url") || "/"),
|
|
85
87
|
"volume" => get.call("volume"), "number" => get.call("issue") || get.call("number"),
|
|
@@ -87,6 +89,15 @@ module Datalog
|
|
|
87
89
|
}
|
|
88
90
|
CitationExports.render(record, authors, kind, key)
|
|
89
91
|
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
# The year of the date given, else of the page's own. An undated page's
|
|
96
|
+
# date is the build's, and gives no year (#411).
|
|
97
|
+
def citation_year(page, options)
|
|
98
|
+
published = Authors.value(options, "date") || PageDates.published(page, @context.registers[:site]&.time)
|
|
99
|
+
date(published, "%Y") if published
|
|
100
|
+
end
|
|
90
101
|
end
|
|
91
102
|
end
|
|
92
103
|
|