datalog-theme 0.8.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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +63 -0
  3. data/CITATION.cff +2 -2
  4. data/README.md +4 -2
  5. data/_data/i18n/en.yml +297 -0
  6. data/_data/i18n/es.yml +297 -0
  7. data/_data/i18n/pt.yml +297 -0
  8. data/_data/js_manifest.json +16 -0
  9. data/_includes/components/author-bio.html +21 -11
  10. data/_includes/components/author-list.html +32 -0
  11. data/_includes/components/citation-tools.html +33 -25
  12. data/_includes/components/comments-thread.html +90 -0
  13. data/_includes/components/contact-form.html +120 -0
  14. data/_includes/components/correction-report.html +82 -0
  15. data/_includes/components/enhanced-toc.html +6 -7
  16. data/_includes/components/license-link.html +13 -0
  17. data/_includes/components/license-notice.html +28 -0
  18. data/_includes/components/moderation-inbox.html +139 -0
  19. data/_includes/components/reactions.html +49 -0
  20. data/_includes/components/reading-list.html +31 -0
  21. data/_includes/components/reading-mode-toggle.html +65 -0
  22. data/_includes/components/reading-state-bookmark.html +27 -0
  23. data/_includes/components/reading-state-panel.html +71 -0
  24. data/_includes/components/reproducibility.html +61 -0
  25. data/_includes/components/responsive-image.html +3 -3
  26. data/_includes/components/revision-history.html +39 -0
  27. data/_includes/components/revision-notice.html +35 -0
  28. data/_includes/components/series-nav.html +64 -0
  29. data/_includes/components/subscribe-form.html +88 -0
  30. data/_includes/components/subscription-manage.html +68 -0
  31. data/_includes/components/webmentions.html +48 -0
  32. data/_includes/csp-meta.html +21 -1
  33. data/_includes/footer.html +3 -0
  34. data/_includes/head.html +51 -40
  35. data/_includes/layouts/default/article.html +4 -6
  36. data/_includes/meta/dynamic-services-config.html +14 -0
  37. data/_includes/meta/math-config.html +9 -5
  38. data/_includes/meta/person-json.html +27 -0
  39. data/_includes/meta/publisher.html +45 -0
  40. data/_includes/meta/schema.html +62 -28
  41. data/_includes/meta/scholarly.html +121 -0
  42. data/_includes/meta/scripts-loader.html +2 -0
  43. data/_includes/meta/webmention-discovery.html +14 -0
  44. data/_includes/scripts.html +57 -0
  45. data/_layouts/dataset.html +4 -3
  46. data/_layouts/default.html +1 -1
  47. data/_layouts/package.html +3 -2
  48. data/_layouts/page.html +15 -0
  49. data/_layouts/post.html +82 -18
  50. data/_layouts/project.html +2 -2
  51. data/_layouts/research.html +20 -8
  52. data/_plugins/authors.rb +133 -0
  53. data/_plugins/config_validator.rb +223 -20
  54. data/_plugins/critical_css_check.rb +42 -0
  55. data/_plugins/i18n.rb +6 -4
  56. data/_plugins/image_optimizer.rb +224 -172
  57. data/_plugins/licenses.rb +135 -0
  58. data/_plugins/math_preprocessor.rb +32 -10
  59. data/_plugins/references.rb +238 -0
  60. data/_plugins/reproducibility.rb +150 -0
  61. data/_plugins/revisions.rb +101 -0
  62. data/_plugins/scholarly.rb +50 -0
  63. data/_plugins/series.rb +104 -0
  64. data/_plugins/statements.rb +87 -0
  65. data/_sass/_base.scss +8 -1
  66. data/_sass/_comments-thread.scss +159 -0
  67. data/_sass/_layout.scss +384 -0
  68. data/_sass/_mathematical.scss +27 -0
  69. data/_sass/_moderation.scss +222 -0
  70. data/_sass/_post-components.scss +4 -2
  71. data/_sass/_print.scss +291 -0
  72. data/_sass/_reactions.scss +89 -0
  73. data/_sass/_reading-state.scss +290 -0
  74. data/_sass/_service-forms.scss +204 -0
  75. data/_sass/_subscriptions.scss +140 -0
  76. data/_sass/_syntax-highlighting.scss +2 -2
  77. data/_sass/_theme.scss +11 -0
  78. data/_sass/_typography.scss +116 -0
  79. data/_sass/_utilities.scss +5 -0
  80. data/_sass/_variables.scss +6 -0
  81. data/_sass/_webmentions.scss +125 -0
  82. data/assets/js/dist/academic.js +1 -1
  83. data/assets/js/dist/analytics-dashboard.js +1 -1
  84. data/assets/js/dist/chunks/chunk-2DYDWUFX.js +1 -0
  85. data/assets/js/dist/chunks/chunk-PATLC23F.js +1 -0
  86. data/assets/js/dist/chunks/chunk-V7734B2G.js +1 -0
  87. data/assets/js/dist/comments.js +2 -0
  88. data/assets/js/dist/contact.js +1 -0
  89. data/assets/js/dist/core.js +1 -1
  90. data/assets/js/dist/corrections.js +1 -0
  91. data/assets/js/dist/loader.js +1 -1
  92. data/assets/js/dist/math.js +1 -1
  93. data/assets/js/dist/moderation.js +1 -0
  94. data/assets/js/dist/notebook.js +1 -1
  95. data/assets/js/dist/reactions.js +1 -0
  96. data/assets/js/dist/reading-state.js +1 -0
  97. data/assets/js/dist/search.js +1 -1
  98. data/assets/js/dist/sources.json +52 -0
  99. data/assets/js/dist/subscriptions.js +1 -0
  100. data/assets/js/dist/webmentions.js +1 -0
  101. data/assets/js/loader.js +34 -0
  102. data/datalog-theme.gemspec +1 -2
  103. data/lib/datalog/cli.rb +9 -1
  104. data/lib/datalog/critical_css.rb +168 -0
  105. data/lib/datalog/plugins/comments.rb +33 -3
  106. data/lib/datalog/theme/installed_files.rb +113 -0
  107. data/lib/datalog/theme/repository_checkout.rb +6 -2
  108. data/lib/datalog/theme/version.rb +5 -1
  109. data/lib/datalog-theme.rb +1 -0
  110. metadata +57 -23
  111. data/assets/js/dist/chunks/chunk-225H5YXE.js +0 -1
@@ -0,0 +1,88 @@
1
+ {%- comment -%}
2
+ ---
3
+ optional:
4
+ - where
5
+ ---
6
+ The newsletter subscribe form (assets/js/subscriptions/form.js): an email
7
+ address and, when `subscriptions.topics` lists any, what to receive, sent
8
+ to the site's dynamic services (docs/dynamic-services.md). Renders only
9
+ when the site has a backend with the subscriptions feature on and
10
+ `subscriptions.enabled` is not false; the footer and the post layout
11
+ include it where `subscriptions.placement` says. `where` ("footer",
12
+ "post" or "page", the default) keeps the ids apart when a page carries
13
+ more than one, and picks the heading level.
14
+
15
+ The footer is an include_cached, rendered once per language and reused on
16
+ every page, so the footer form carries nothing of the page: the script
17
+ reads the page's address from its canonical link and its language from
18
+ <html lang>. For the same reason the block has no accessible name of its
19
+ own, which would make two same-named landmarks on a post.
20
+ {%- endcomment -%}
21
+ {%- assign services = site.dynamic_services -%}
22
+ {%- assign subscriptions = site.subscriptions -%}
23
+ {%- if services.base_url and services.base_url != '' and services.features.subscriptions != false and subscriptions.enabled != false -%}
24
+ {%- assign where = include.where | default: 'page' -%}
25
+ {%- assign topics = subscriptions.topics -%}
26
+ {%- assign privacy_url = subscriptions.privacy_url | default: nil -%}
27
+ {%- capture labels_json -%}
28
+ {
29
+ "sending": {{ 'subscriptions.sending' | t | jsonify }},
30
+ "pending": {{ 'subscriptions.pending' | t | jsonify }},
31
+ "confirmed": {{ 'subscriptions.confirmed' | t | jsonify }},
32
+ "duplicate": {{ 'subscriptions.duplicate' | t | jsonify }},
33
+ "email_invalid": {{ 'subscriptions.email_invalid' | t | jsonify }},
34
+ "topics_required": {{ 'subscriptions.topics_required' | t | jsonify }},
35
+ "errors": {
36
+ {%- assign kinds = "disabled,unsupported,version,timeout,network,malformed,unauthorized,forbidden,not_found,conflict,invalid,rate_limited,server,http,aborted,reference" | split: "," -%}
37
+ {%- for kind in kinds -%}
38
+ {%- assign kind_key = "dynamic_services.errors." | append: kind -%}
39
+ {{ kind | jsonify }}: {{ kind_key | t | jsonify }}{% unless forloop.last %},{% endunless %}
40
+ {%- endfor -%}
41
+ }
42
+ }
43
+ {%- endcapture -%}
44
+ <section class="subscribe subscribe--{{ where }}" data-subscribe{% unless where == 'footer' %} data-subscribe-source="{{ page.url | absolute_url }}" data-subscribe-locale="{{ page.lang | default: site.lang | default: 'en' }}"{% endunless %} data-subscribe-double-opt-in="{% if subscriptions.double_opt_in == false %}false{% else %}true{% endif %}">
45
+ <script type="application/json" data-subscribe-labels>{{ labels_json | strip }}</script>
46
+ {%- if where == 'footer' %}
47
+ <h3 class="subscribe__heading" id="subscribe-heading-{{ where }}">{% t 'footer.subscribe_title' %}</h3>
48
+ {%- else %}
49
+ <h2 class="subscribe__heading" id="subscribe-heading-{{ where }}">{% t 'footer.subscribe_title' %}</h2>
50
+ {%- endif %}
51
+ <p class="subscribe__description">{% t 'footer.subscribe_description' %}</p>
52
+ <form class="service-form subscribe__form" id="subscribe-form-{{ where }}" data-state="idle" aria-busy="false" novalidate>
53
+ <fieldset class="service-form__fields">
54
+ <legend class="visually-hidden">{% t 'footer.subscribe_title' %}</legend>
55
+ <div class="service-form__field">
56
+ <label for="subscribe-email-{{ where }}">{% t 'footer.newsletter_label' %}</label>
57
+ <input id="subscribe-email-{{ where }}" name="email" type="email" required autocomplete="email">
58
+ <p class="service-form__error" data-error-for="email" hidden></p>
59
+ </div>
60
+ {%- if topics and topics.size > 0 %}
61
+ <fieldset class="subscribe__topics">
62
+ <legend>{% t 'subscriptions.topics_legend' %}</legend>
63
+ {%- for topic in topics %}
64
+ {%- assign topic_key = "subscriptions.topics." | append: topic %}
65
+ <label class="subscribe__topic" for="subscribe-topic-{{ where }}-{{ topic | slugify }}">
66
+ <input id="subscribe-topic-{{ where }}-{{ topic | slugify }}" type="checkbox" name="topics" value="{{ topic | escape }}" checked>
67
+ {{ topic_key | t }}
68
+ </label>
69
+ {%- endfor %}
70
+ <p class="service-form__error" data-error-for="topics" hidden></p>
71
+ </fieldset>
72
+ {%- endif %}
73
+ <div class="service-form__trap" aria-hidden="true">
74
+ <label for="subscribe-website-{{ where }}">Website</label>
75
+ <input id="subscribe-website-{{ where }}" name="website" type="text" tabindex="-1" autocomplete="off">
76
+ </div>
77
+ </fieldset>
78
+ <p class="service-form__privacy">
79
+ {%- if subscriptions.double_opt_in == false %}{% t 'subscriptions.consent' %}{% else %}{% t 'subscriptions.consent_double' %}{% endif %}
80
+ {%- if privacy_url %} <a href="{{ privacy_url | relative_url }}">{% t 'subscriptions.privacy_link' %}</a>{% endif -%}
81
+ </p>
82
+ <div class="service-form__actions">
83
+ <button type="submit" class="post-tool">{% t 'footer.newsletter_button' %}</button>
84
+ </div>
85
+ <p class="service-form__status" data-form-status role="status" aria-live="polite" hidden></p>
86
+ </form>
87
+ </section>
88
+ {%- endif -%}
@@ -0,0 +1,68 @@
1
+ {%- comment -%}
2
+ ---
3
+ optional: []
4
+ ---
5
+ The page the newsletter's emails link to (assets/js/subscriptions/manage.js):
6
+ `?confirm=<token>` confirms a subscription, `?unsubscribe=<token>` ends one
7
+ after a press of the button, `?manage=<token>` changes its topics. Rendered
8
+ by the page layout when the page says `subscription_manage: true`. Without
9
+ a backend offering the feature it says so, since a link in an old email
10
+ may still lead here.
11
+ {%- endcomment -%}
12
+ {%- assign services = site.dynamic_services -%}
13
+ {%- assign subscriptions = site.subscriptions -%}
14
+ {%- if services.base_url and services.base_url != '' and services.features.subscriptions != false and subscriptions.enabled != false -%}
15
+ {%- assign topics = subscriptions.topics -%}
16
+ {%- capture labels_json -%}
17
+ {
18
+ "confirming": {{ 'subscriptions.manage.confirming' | t | jsonify }},
19
+ "confirmed": {{ 'subscriptions.manage.confirmed' | t | jsonify }},
20
+ "invalid_link": {{ 'subscriptions.manage.invalid_link' | t | jsonify }},
21
+ "unsubscribing": {{ 'subscriptions.manage.unsubscribing' | t | jsonify }},
22
+ "unsubscribed": {{ 'subscriptions.manage.unsubscribed' | t | jsonify }},
23
+ "loading": {{ 'subscriptions.manage.loading' | t | jsonify }},
24
+ "saving": {{ 'subscriptions.manage.saving' | t | jsonify }},
25
+ "saved": {{ 'subscriptions.manage.saved' | t | jsonify }},
26
+ "errors": {
27
+ {%- assign kinds = "disabled,unsupported,version,timeout,network,malformed,unauthorized,forbidden,not_found,conflict,invalid,rate_limited,server,http,aborted,reference" | split: "," -%}
28
+ {%- for kind in kinds -%}
29
+ {%- assign kind_key = "dynamic_services.errors." | append: kind -%}
30
+ {{ kind | jsonify }}: {{ kind_key | t | jsonify }}{% unless forloop.last %},{% endunless %}
31
+ {%- endfor -%}
32
+ }
33
+ }
34
+ {%- endcapture -%}
35
+ <section class="subscription-manage" id="subscription-manage" data-subscription-manage data-state="idle" aria-busy="false">
36
+ <script type="application/json" nonce="{{ page.csp_nonce }}" data-manage-labels>{{ labels_json | strip }}</script>
37
+ <p class="subscription-manage__status" data-manage-status role="status" aria-live="polite" hidden></p>
38
+ <p class="subscription-manage__idle" data-manage-idle>{% t 'subscriptions.manage.idle' %}</p>
39
+ <div class="subscription-manage__panel" data-manage-unsubscribe-panel hidden>
40
+ <p>{% t 'subscriptions.manage.unsubscribe_question' %}</p>
41
+ <button type="button" class="post-tool" data-manage-unsubscribe>{% t 'subscriptions.manage.unsubscribe' %}</button>
42
+ </div>
43
+ <form class="service-form subscription-manage__panel" id="subscription-preferences" data-manage-preferences hidden novalidate>
44
+ {%- if topics and topics.size > 0 %}
45
+ <fieldset class="subscribe__topics">
46
+ <legend>{% t 'subscriptions.manage.preferences' %}</legend>
47
+ {%- for topic in topics %}
48
+ {%- assign topic_key = "subscriptions.topics." | append: topic %}
49
+ <label class="subscribe__topic" for="manage-topic-{{ topic | slugify }}">
50
+ <input id="manage-topic-{{ topic | slugify }}" type="checkbox" name="topics" value="{{ topic | escape }}">
51
+ {{ topic_key | t }}
52
+ </label>
53
+ {%- endfor %}
54
+ </fieldset>
55
+ {%- endif %}
56
+ <div class="service-form__actions">
57
+ {%- if topics and topics.size > 0 %}
58
+ <button type="submit" class="post-tool">{% t 'subscriptions.manage.save' %}</button>
59
+ {%- endif %}
60
+ <button type="button" class="post-tool" data-manage-unsubscribe>{% t 'subscriptions.manage.unsubscribe' %}</button>
61
+ </div>
62
+ </form>
63
+ </section>
64
+ {%- else -%}
65
+ <section class="subscription-manage" id="subscription-manage">
66
+ <p class="subscription-manage__idle">{% t 'subscriptions.manage.unavailable' %}</p>
67
+ </section>
68
+ {%- endif -%}
@@ -0,0 +1,48 @@
1
+ {%- comment -%}
2
+ ---
3
+ optional: []
4
+ ---
5
+ "Mentioned elsewhere" (assets/js/webmentions/list.js): the links, replies
6
+ and reposts other websites sent about this article, read through the
7
+ site's dynamic services (docs/dynamic-services.md) from what the
8
+ Webmention receiver verified. Apart from the comments, which are hosted
9
+ here. Renders only when the site has a backend with the webmentions
10
+ feature on, `webmentions.enabled` is not false, and the page does not say
11
+ `webmentions: false`. The kinds shown come from `webmentions.types`.
12
+ The receiver itself is advertised by meta/webmention-discovery.html.
13
+ {%- endcomment -%}
14
+ {%- assign services = site.dynamic_services -%}
15
+ {%- assign webmentions = site.webmentions -%}
16
+ {%- if services.base_url and services.base_url != '' and services.features.webmentions != false and webmentions.enabled != false and page.webmentions != false -%}
17
+ {%- assign types = webmentions.types -%}
18
+ {%- if types == nil or types.size == 0 -%}
19
+ {%- assign types = "mention,reply" | split: "," -%}
20
+ {%- endif -%}
21
+ {%- capture labels_json -%}
22
+ {
23
+ "loading": {{ 'webmentions.loading' | t | jsonify }},
24
+ "empty": {{ 'webmentions.empty' | t | jsonify }},
25
+ "retry": {{ 'webmentions.retry' | t | jsonify }},
26
+ "types": {
27
+ "mention": {{ 'webmentions.types.mention' | t | jsonify }},
28
+ "reply": {{ 'webmentions.types.reply' | t | jsonify }},
29
+ "repost": {{ 'webmentions.types.repost' | t | jsonify }},
30
+ "like": {{ 'webmentions.types.like' | t | jsonify }}
31
+ },
32
+ "errors": {
33
+ {%- assign kinds = "disabled,unsupported,version,timeout,network,malformed,unauthorized,forbidden,not_found,conflict,invalid,rate_limited,server,http,aborted,reference" | split: "," -%}
34
+ {%- for kind in kinds -%}
35
+ {%- assign kind_key = "dynamic_services.errors." | append: kind -%}
36
+ {{ kind | jsonify }}: {{ kind_key | t | jsonify }}{% unless forloop.last %},{% endunless %}
37
+ {%- endfor -%}
38
+ }
39
+ }
40
+ {%- endcapture -%}
41
+ <section class="webmentions" id="webmentions" data-webmentions data-webmentions-target="{{ page.url | absolute_url }}" data-webmentions-types="{{ types | join: ',' }}" data-state="idle" aria-labelledby="webmentions-heading">
42
+ <script type="application/json" nonce="{{ page.csp_nonce }}" data-webmentions-labels>{{ labels_json | strip }}</script>
43
+ <h2 class="webmentions__heading" id="webmentions-heading">{% t 'webmentions.heading' %}</h2>
44
+ <p class="webmentions__intro">{% t 'webmentions.intro' %}</p>
45
+ <p class="webmentions__status" data-webmentions-status role="status" aria-live="polite">{% t 'webmentions.loading' %}</p>
46
+ <ul class="webmentions__list" data-webmentions-list hidden></ul>
47
+ </section>
48
+ {%- endif -%}
@@ -10,6 +10,26 @@
10
10
  {% assign extra_style_src = site.csp.style_src | join: ' ' %}
11
11
  {% assign extra_font_src = site.csp.font_src | join: ' ' %}
12
12
  {% assign extra_connect_src = site.csp.connect_src | join: ' ' %}
13
+ {%- comment -%} The dynamic services' origin, so the browser may call the site's backend (docs/dynamic-services.md). {%- endcomment -%}
14
+ {% assign services_origin = '' %}
15
+ {% if site.dynamic_services.base_url and site.dynamic_services.base_url != '' %}
16
+ {% assign services_parts = site.dynamic_services.base_url | split: '/' %}
17
+ {% assign services_origin = services_parts[0] | append: '//' | append: services_parts[2] %}
18
+ {% endif %}
19
+ {%- comment -%} The comments endpoint, when the api provider keeps comments apart from the services (lib/datalog/plugins/comments.rb). {%- endcomment -%}
20
+ {% assign comments_origin = '' %}
21
+ {% assign comments_options = site.datalog_plugins.options['datalog-comments'] %}
22
+ {% assign comments_endpoint = page.datalog_comments.endpoint | default: comments_options.endpoint %}
23
+ {% if comments_endpoint and comments_endpoint != '' %}
24
+ {% assign comments_parts = comments_endpoint | split: '/' %}
25
+ {% assign comments_origin = comments_parts[0] | append: '//' | append: comments_parts[2] %}
26
+ {% endif %}
27
+ {%- comment -%} The moderation API, when it lives apart from the services, and only on the inbox page (docs/moderation.md). {%- endcomment -%}
28
+ {% assign moderation_origin = '' %}
29
+ {% if page.moderation_inbox and site.moderation.enabled == true and site.moderation.endpoint and site.moderation.endpoint != '' %}
30
+ {% assign moderation_parts = site.moderation.endpoint | split: '/' %}
31
+ {% assign moderation_origin = moderation_parts[0] | append: '//' | append: moderation_parts[2] %}
32
+ {% endif %}
13
33
  {%- comment -%}
14
34
  Plotly, the Jupyter widget manager and MathJax can't run under the strict
15
35
  policy. Plotly inserts its rules into a <style> element it creates; the widget
@@ -113,7 +133,7 @@
113
133
  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 %};
114
134
  img-src 'self' data: https:;
115
135
  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 %};
136
+ 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 services_origin != '' %} {{ services_origin }}{% endif %}{% if comments_origin != '' and comments_origin != services_origin %} {{ comments_origin }}{% endif %}{% if moderation_origin != '' and moderation_origin != services_origin %} {{ moderation_origin }}{% endif %}{% if extra_connect_src != '' %} {{ extra_connect_src }}{% endif %};
117
137
  frame-src 'self' https://observablehq.com https://old.observablehq.com{{ cdn_frames }}{% if frame_hosts != '' %} {{ frame_hosts }}{% endif %};
118
138
  object-src 'none';
119
139
  base-uri 'self';
@@ -67,6 +67,9 @@
67
67
  <li><a href="{{ '/feed.xml' | relative_url }}">RSS feed</a></li>
68
68
  {% endif %}
69
69
  </ul>
70
+ {%- comment -%} The newsletter form, on a site with a backend that takes subscriptions {%- endcomment -%}
71
+ {% assign subscribe_placement = site.subscriptions.placement | default: 'footer' %}
72
+ {% if subscribe_placement contains 'footer' %}{% include components/subscribe-form.html where='footer' %}{% endif %}
70
73
  </section>
71
74
  </div>
72
75
  <div class="container footer-bottom">
data/_includes/head.html CHANGED
@@ -24,7 +24,8 @@
24
24
  <meta name="viewport" content="width=device-width, initial-scale=1" />
25
25
  <meta name="theme-color" content="#0b1d3d" />
26
26
  <meta name="description" content="{{ page_description | strip_html | normalize_whitespace }}" />
27
- <meta name="author" content="{{ page.author | default: site.author.name }}" />
27
+ {% assign head_authors = page | page_authors %}
28
+ <meta name="author" content="{{ head_authors | map: 'name' | join: ', ' | escape }}" />
28
29
  {% if keyword_list %}
29
30
  <meta name="keywords" content="{{ keyword_list | join: ', ' | strip }}" />
30
31
  {% endif %}
@@ -60,6 +61,9 @@
60
61
  <meta name="twitter:image" content="{{ site.social.default_image | absolute_url }}" />
61
62
  {% endif %}
62
63
  <link rel="canonical" href="{{ canonical_url }}" />
64
+ {% include meta/webmention-discovery.html %}
65
+ {%- assign head_license = page | page_license %}
66
+ {% if head_license.url %}<link rel="license" href="{{ head_license.url | escape }}" />{% endif %}
63
67
  {% if js_manifest and js_manifest.core %}
64
68
  <link rel="modulepreload" href="{{ js_manifest.core | relative_url }}" crossorigin="anonymous" />
65
69
  {%- comment -%} Only a page that renders the search app uses the search bundle. {%- endcomment -%}
@@ -77,6 +81,50 @@
77
81
  {% if js_manifest.features and js_manifest.features.visualizations and site.features.visualizations and content contains 'data-viz-type' %}
78
82
  <link rel="modulepreload" href="{{ js_manifest.features.visualizations | relative_url }}" crossorigin="anonymous" />
79
83
  {% endif %}
84
+ {%- comment -%} Posts carry the reading-state panel; a page with the saved-articles list carries data-reading-list. {%- endcomment -%}
85
+ {% if js_manifest.features and js_manifest.features['reading-state'] and site.theme_options.reading_state.enabled != false %}
86
+ {% if page.layout == 'post' or content contains 'data-reading-list' %}
87
+ <link rel="modulepreload" href="{{ js_manifest.features['reading-state'] | relative_url }}" crossorigin="anonymous" />
88
+ {% endif %}
89
+ {% endif %}
90
+ {%- comment -%} The correction-report form renders on posts of a site with a backend. {%- endcomment -%}
91
+ {% if js_manifest.features and js_manifest.features.corrections and page.layout == 'post' and page.corrections != false %}
92
+ {% if site.dynamic_services.base_url and site.dynamic_services.base_url != '' and site.dynamic_services.features.corrections != false and site.corrections.enabled != false %}
93
+ <link rel="modulepreload" href="{{ js_manifest.features.corrections | relative_url }}" crossorigin="anonymous" />
94
+ {% endif %}
95
+ {% endif %}
96
+ {%- comment -%} The contact form renders on a page that asks for it, on a site with a backend. {%- endcomment -%}
97
+ {% if js_manifest.features and js_manifest.features.contact and page.contact_form %}
98
+ {% if site.dynamic_services.base_url and site.dynamic_services.base_url != '' and site.dynamic_services.features.contact != false and site.contact.enabled != false %}
99
+ <link rel="modulepreload" href="{{ js_manifest.features.contact | relative_url }}" crossorigin="anonymous" />
100
+ {% endif %}
101
+ {% endif %}
102
+ {%- comment -%} The comments thread of the api provider, on a page that shows comments that way. {%- endcomment -%}
103
+ {% if js_manifest.features and js_manifest.features.comments and page.datalog_comments.provider == 'api' %}
104
+ <link rel="modulepreload" href="{{ js_manifest.features.comments | relative_url }}" crossorigin="anonymous" />
105
+ {% endif %}
106
+ {%- comment -%} "Was this useful?" renders on posts of a site with a backend. {%- endcomment -%}
107
+ {% if js_manifest.features and js_manifest.features.reactions and page.layout == 'post' and page.reactions != false %}
108
+ {% if site.dynamic_services.base_url and site.dynamic_services.base_url != '' and site.dynamic_services.features.reactions != false and site.reactions.enabled != false %}
109
+ <link rel="modulepreload" href="{{ js_manifest.features.reactions | relative_url }}" crossorigin="anonymous" />
110
+ {% endif %}
111
+ {% endif %}
112
+ {%- comment -%} "Mentioned elsewhere" renders on posts of a site with a backend. {%- endcomment -%}
113
+ {% if js_manifest.features and js_manifest.features.webmentions and page.layout == 'post' and page.webmentions != false %}
114
+ {% if site.dynamic_services.base_url and site.dynamic_services.base_url != '' and site.dynamic_services.features.webmentions != false and site.webmentions.enabled != false %}
115
+ <link rel="modulepreload" href="{{ js_manifest.features.webmentions | relative_url }}" crossorigin="anonymous" />
116
+ {% endif %}
117
+ {% endif %}
118
+ {%- comment -%} The page the newsletter's emails link to acts as soon as it loads; the footer form loads on demand. {%- endcomment -%}
119
+ {% if js_manifest.features and js_manifest.features.subscriptions and page.subscription_manage %}
120
+ {% if site.dynamic_services.base_url and site.dynamic_services.base_url != '' and site.dynamic_services.features.subscriptions != false and site.subscriptions.enabled != false %}
121
+ <link rel="modulepreload" href="{{ js_manifest.features.subscriptions | relative_url }}" crossorigin="anonymous" />
122
+ {% endif %}
123
+ {% endif %}
124
+ {%- comment -%} The moderation inbox loads its queue as soon as the page opens. {%- endcomment -%}
125
+ {% if js_manifest.features and js_manifest.features.moderation and page.moderation_inbox and site.moderation.enabled == true %}
126
+ <link rel="modulepreload" href="{{ js_manifest.features.moderation | relative_url }}" crossorigin="anonymous" />
127
+ {% endif %}
80
128
  {% if js_manifest.features and js_manifest.features.notebook and page.notebook_post %}
81
129
  <link rel="modulepreload" href="{{ js_manifest.features.notebook | relative_url }}" crossorigin="anonymous" />
82
130
  {% endif %}
@@ -304,45 +352,8 @@
304
352
  {% endif %}
305
353
  {% endif %}
306
354
 
307
- {% assign citation_source = page.citation_authors | default: page.authors %}
308
- {% if page.title or page.citation_title %}
309
- <meta name="citation_title" content="{{ page.citation_title | default: page_title | strip_html | normalize_whitespace }}" />
310
- {% endif %}
311
- {% if citation_source %}
312
- {% capture citation_source_json %}{{ citation_source | jsonify }}{% endcapture %}
313
- {% assign citation_source_first = citation_source_json | slice: 0, 1 %}
314
- {% if citation_source_first == '[' %}
315
- {% assign citation_author_list = citation_source %}
316
- {% else %}
317
- {% assign citation_author_list = citation_source | replace: ';', ',' | split: ',' %}
318
- {% endif %}
319
- {% for author in citation_author_list %}
320
- <meta name="citation_author" content="{{ author | strip | escape }}" />
321
- {% endfor %}
322
- {% elsif page.author or site.author.name %}
323
- <meta name="citation_author" content="{{ page.author | default: site.author.name | strip | escape }}" />
324
- {% endif %}
325
- {% if page.date %}
326
- <meta name="citation_publication_date" content="{{ page.date | date: '%Y/%m/%d' }}" />
327
- {% endif %}
328
- {% if page.doi %}
329
- <meta name="citation_doi" content="{{ page.doi }}" />
330
- {% endif %}
331
- {% if page.journal %}
332
- <meta name="citation_journal_title" content="{{ page.journal }}" />
333
- {% endif %}
334
- {% if page.conference %}
335
- <meta name="citation_conference_title" content="{{ page.conference }}" />
336
- {% endif %}
337
- {% if page.orcid or site.author.orcid %}
338
- <meta name="citation_orcid" content="{{ page.orcid | default: site.author.orcid }}" />
339
- {% endif %}
340
- {% if page.dataset_doi %}
341
- <meta name="citation_dataset_doi" content="{{ page.dataset_doi }}" />
342
- {% endif %}
343
- {% if page.citation_pdf %}
344
- <link rel="citation_pdf_url" href="{{ page.citation_pdf | absolute_url }}" />
345
- {% endif %}
355
+ {%- comment -%} Highwire and Dublin Core tags, for a page read as a research article; the include decides which. {%- endcomment -%}
356
+ {% include meta/scholarly.html %}
346
357
 
347
358
  <link rel="alternate" type="application/atom+xml" title="{{ site.title }}" href="{{ '/feed.xml' | absolute_url }}" />
348
359
  <meta http-equiv="Permissions-Policy" content="interest-cohort=()" />
@@ -52,14 +52,12 @@ Renders the standard article scaffold used by the default layout.
52
52
  </dd>
53
53
  </div>
54
54
  {%- endif -%}
55
- {%- assign author_name = article_page.author | default: site.author.name -%}
55
+ {%- assign article_authors = article_page | page_authors -%}
56
56
  {%- comment -%} `show_author: false` hides the byline, for pages that are site chrome rather than articles. {%- endcomment -%}
57
- {%- if author_name and article_page.show_author != false -%}
57
+ {%- if article_authors.size > 0 and article_page.show_author != false -%}
58
58
  <div>
59
- <dt class="visually-hidden">{% t 'article.meta.author' %}</dt>
60
- <dd itemprop="author" itemscope itemtype="https://schema.org/Person">
61
- <span itemprop="name">{{ author_name }}</span>
62
- </dd>
59
+ <dt class="visually-hidden">{% if article_authors.size > 1 %}{% t 'article.meta.authors' %}{% else %}{% t 'article.meta.author' %}{% endif %}</dt>
60
+ <dd>{% include components/author-list.html people=article_authors show_affiliation=false %}</dd>
63
61
  </div>
64
62
  {%- endif -%}
65
63
  </dl>
@@ -0,0 +1,14 @@
1
+ {%- comment -%}
2
+ ---
3
+ optional: []
4
+ ---
5
+ The public settings of the site's dynamic services (`dynamic_services` in
6
+ _config.yml), for assets/js/dynamic-services/client.js, on a site that sets
7
+ a base URL. Every reader's browser reads this, so it holds no credential:
8
+ the configuration validator stops the build on a key that names one.
9
+ {%- endcomment -%}
10
+ {%- if site.dynamic_services.base_url and site.dynamic_services.base_url != '' %}
11
+ <script nonce="{{ page.csp_nonce }}">
12
+ window.DatalogDynamicServices = {{ site.dynamic_services | jsonify }};
13
+ </script>
14
+ {%- endif -%}
@@ -8,8 +8,12 @@ optional:
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
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`).
11
+ math_enabled resolves, in order: the page's `math` front matter, then its
12
+ `mathjax` front matter, then `theme_options.math.render_on_load` (falling back
13
+ to `features.mathjax`). `mathjax` is an alias read only when `math` is unset: it
14
+ used to be read first, so a page could not turn off with `math: false` what a
15
+ `mathjax: true` in front matter defaults had turned on.
16
+ _plugins/math_preprocessor.rb resolves the two the same way.
13
17
  With `render_on_load: auto` the engine is only loaded on pages with math: the
14
18
  math preprocessor records each expression it wraps in `math_expressions`, and
15
19
  leaves code alone. Kramdown's `math/tex` scripts and the preprocessor's
@@ -33,10 +37,10 @@ only on pages that opt in.
33
37
  {%- if math_load == nil -%}
34
38
  {%- assign math_load = site.features.mathjax -%}
35
39
  {%- endif -%}
36
- {%- if include.page.mathjax != nil -%}
37
- {%- assign math_enabled = include.page.mathjax -%}
38
- {%- elsif include.page.math != nil -%}
40
+ {%- if include.page.math != nil -%}
39
41
  {%- assign math_enabled = include.page.math -%}
42
+ {%- elsif include.page.mathjax != nil -%}
43
+ {%- assign math_enabled = include.page.mathjax -%}
40
44
  {%- elsif math_load == 'auto' -%}
41
45
  {%- assign math_enabled = math_has_expressions -%}
42
46
  {%- if content contains 'math/tex' or content contains 'math-expression--source' -%}
@@ -0,0 +1,27 @@
1
+ {%- comment -%}
2
+ ---
3
+ required:
4
+ - person
5
+ ---
6
+ One schema.org Person, as JSON, for a record from `page | page_authors` or
7
+ `page | page_contributors`. The site's own author links to the site when the
8
+ record names no url of its own.
9
+ {%- endcomment -%}
10
+ {%- assign person_url = include.person.url -%}
11
+ {%- if person_url == nil and include.person.site_author -%}{%- assign person_url = site.url -%}{%- endif -%}
12
+ {
13
+ "@type": "Person",
14
+ "name": {{ include.person.name | jsonify }}
15
+ {%- if person_url -%},
16
+ "url": {{ person_url | absolute_url | jsonify }}
17
+ {%- endif -%}
18
+ {%- if include.person.affiliation -%},
19
+ "affiliation": {
20
+ "@type": "Organization",
21
+ "name": {{ include.person.affiliation | jsonify }}
22
+ }
23
+ {%- endif -%}
24
+ {%- if include.person.same_as.size > 0 -%},
25
+ "sameAs": {{ include.person.same_as | jsonify }}
26
+ {%- endif -%}
27
+ }
@@ -0,0 +1,45 @@
1
+ {%- comment -%}
2
+ ---
3
+ optional: []
4
+ ---
5
+ Resolves who publishes the site, for structured data. Sets publisher_type
6
+ ("Person" or "Organization"), publisher_name, publisher_url and, for an
7
+ Organization, publisher_logo and publisher_logo_default (true when the logo is
8
+ the theme's 512px icon, whose size is known).
9
+
10
+ `publisher` in _config.yml names the publisher. Without it, the site's author
11
+ publishes as a Person, or the site title as an Organization when there is no
12
+ author name. The author's affiliation is not the publisher: schema.html puts it
13
+ on the author.
14
+ {%- endcomment -%}
15
+ {%- assign site_author_name = site.author.name | default: site.author -%}
16
+ {%- if site.publisher.type == 'Organization' -%}
17
+ {%- assign publisher_type = 'Organization' -%}
18
+ {%- else -%}
19
+ {%- assign publisher_type = 'Person' -%}
20
+ {%- endif -%}
21
+ {%- assign publisher_name = site.publisher.name -%}
22
+ {%- if publisher_type == 'Person' -%}
23
+ {%- assign publisher_name = publisher_name | default: site_author_name -%}
24
+ {%- if publisher_name == nil or publisher_name == '' -%}
25
+ {%- assign publisher_type = 'Organization' -%}
26
+ {%- endif -%}
27
+ {%- endif -%}
28
+ {%- if publisher_type == 'Organization' -%}
29
+ {%- assign publisher_name = publisher_name | default: site.title -%}
30
+ {%- endif -%}
31
+ {%- if site.publisher.url -%}
32
+ {%- assign publisher_url = site.publisher.url | absolute_url -%}
33
+ {%- else -%}
34
+ {%- assign publisher_url = site.url -%}
35
+ {%- endif -%}
36
+ {%- assign publisher_logo = nil -%}
37
+ {%- assign publisher_logo_default = false -%}
38
+ {%- if publisher_type == 'Organization' -%}
39
+ {%- if site.publisher.logo -%}
40
+ {%- assign publisher_logo = site.publisher.logo | absolute_url -%}
41
+ {%- else -%}
42
+ {%- assign publisher_logo = '/assets/img/favicons/android-chrome-512x512.png' | absolute_url -%}
43
+ {%- assign publisher_logo_default = true -%}
44
+ {%- endif -%}
45
+ {%- endif -%}
@@ -10,12 +10,13 @@ Provides rich structured data for:
10
10
  - Article/BlogPosting/TechnicalArticle schema for posts
11
11
  - WebPage schema for standard pages
12
12
  - Author information with social profile links
13
- - Publisher organization with branding
13
+ - Publisher: a Person or an Organization, from `publisher` in _config.yml
14
14
  - Images, keywords, and content metadata
15
15
 
16
16
  Defaults to TechnicalArticle for posts, WebPage for other pages.
17
17
  {%- endcomment -%}
18
18
  {%- assign schema_page = include.page -%}
19
+ {%- include meta/publisher.html -%}
19
20
  {%- assign is_post = false -%}
20
21
  {%- if schema_page.layout == 'post' or schema_page.collection == 'posts' -%}
21
22
  {%- assign is_post = true -%}
@@ -30,7 +31,8 @@ Defaults to TechnicalArticle for posts, WebPage for other pages.
30
31
  {%- assign schema_nonce = schema_page.csp_nonce | default: page.csp_nonce | default: nonce_lookup[schema_page.path] | default: nonce_lookup[page.path] | default: nonce_lookup[page.url] -%}
31
32
  {%- assign description = schema_page.description | default: schema_page.excerpt | strip_html | strip_newlines | truncate: 300 -%}
32
33
  {%- assign page_url = schema_page.url | absolute_url -%}
33
- {%- assign author_name = schema_page.author | default: site.author.name -%}
34
+ {%- assign schema_authors = schema_page | page_authors -%}
35
+ {%- assign schema_contributors = schema_page | page_contributors -%}
34
36
  {%- assign word_count = schema_page.content | strip_html | number_of_words -%}
35
37
  {%- assign modified = schema_page.last_modified_at | default: schema_page.updated | default: schema_page.date -%}
36
38
  <script type="application/ld+json" nonce="{{ schema_nonce }}">
@@ -80,38 +82,35 @@ Defaults to TechnicalArticle for posts, WebPage for other pages.
80
82
  {%- if is_post -%}
81
83
  "inLanguage": "{{ schema_page.lang | default: site.locale | default: 'en' | split: '_' | first }}",
82
84
  {%- endif -%}
83
- "author": {
84
- "@type": "Person",
85
- "name": {{ author_name | jsonify }},
86
- "url": {{ site.url | jsonify }}
87
- {%- if site.author.orcid -%},
88
- "sameAs": [
89
- {{ site.author.orcid | jsonify }}
90
- {%- if site.author.github -%},
91
- "https://github.com/{{ site.author.github }}"
92
- {%- endif -%}
93
- {%- if site.author.twitter -%},
94
- "https://twitter.com/{{ site.author.twitter }}"
95
- {%- endif -%}
96
- {%- if site.author.linkedin -%},
97
- "https://linkedin.com/in/{{ site.author.linkedin }}"
98
- {%- endif -%}
99
- {%- if site.author.researchgate -%},
100
- {{ site.author.researchgate | jsonify }}
101
- {%- endif -%}
102
- ]
103
- {%- endif -%}
104
- },
85
+ {%- comment -%} One author is an object, as it always was; several are a list; a site without an author has none. {%- endcomment -%}
86
+ {%- if schema_authors.size > 0 -%}
87
+ "author": {% if schema_authors.size > 1 %}[{% endif %}
88
+ {%- for author in schema_authors -%}
89
+ {% include meta/person-json.html person=author %}{% unless forloop.last %},{% endunless %}
90
+ {%- endfor -%}
91
+ {% if schema_authors.size > 1 %}]{% endif %},
92
+ {%- endif -%}
93
+ {%- if schema_contributors.size > 0 -%}
94
+ "contributor": [
95
+ {%- for contributor in schema_contributors -%}
96
+ {% include meta/person-json.html person=contributor %}{% unless forloop.last %},{% endunless %}
97
+ {%- endfor -%}
98
+ ],
99
+ {%- endif -%}
105
100
  "publisher": {
106
- "@type": "Organization",
107
- "name": {{ site.author.affiliation | default: site.title | jsonify }},
108
- "url": {{ site.url | jsonify }},
101
+ "@type": {{ publisher_type | jsonify }},
102
+ "name": {{ publisher_name | jsonify }},
103
+ "url": {{ publisher_url | jsonify }}
104
+ {%- if publisher_logo -%},
109
105
  "logo": {
110
106
  "@type": "ImageObject",
111
- "url": {{ '/assets/img/favicons/android-chrome-512x512.png' | absolute_url | jsonify }},
107
+ "url": {{ publisher_logo | jsonify }}
108
+ {%- if publisher_logo_default -%},
112
109
  "width": 512,
113
110
  "height": 512
111
+ {%- endif -%}
114
112
  }
113
+ {%- endif -%}
115
114
  }
116
115
  {%- if schema_page.doi -%},
117
116
  "identifier": {
@@ -123,6 +122,41 @@ Defaults to TechnicalArticle for posts, WebPage for other pages.
123
122
  {%- if schema_page.citation_key -%},
124
123
  "citation": {{ schema_page.citation_key | jsonify }}
125
124
  {%- endif -%}
125
+ {%- comment -%} The licence of the text and figures, and who holds the copyright. {%- endcomment -%}
126
+ {%- assign content_license = schema_page | page_license -%}
127
+ {%- if content_license -%}
128
+ {%- if content_license.url -%},
129
+ "license": {{ content_license.url | jsonify }}
130
+ {%- endif -%}
131
+ {%- if content_license.year -%},
132
+ "copyrightYear": {{ content_license.year | jsonify }}
133
+ {%- endif -%}
134
+ {%- if content_license.holder -%},
135
+ "copyrightHolder": {% if content_license.people %}{% if content_license.holders.size > 1 %}[{% endif %}
136
+ {%- for holder in content_license.holders -%}
137
+ { "@type": "Person", "name": {{ holder | jsonify }} }{% unless forloop.last %},{% endunless %}
138
+ {%- endfor -%}
139
+ {% if content_license.holders.size > 1 %}]{% endif %}{% else %}{ "@type": "Organization", "name": {{ content_license.holder | jsonify }} }{% endif %}
140
+ {%- endif -%}
141
+ {%- endif -%}
142
+ {%- comment -%} Each correction in the revision history, with its date, summary and link. {%- endcomment -%}
143
+ {%- assign corrections = schema_page.revisions | where: 'type', 'correction' -%}
144
+ {%- if corrections and corrections.size > 0 -%},
145
+ "correction": [
146
+ {%- for correction in corrections -%}
147
+ {
148
+ "@type": "CorrectionComment",
149
+ "datePublished": "{{ correction.date | date_to_xmlschema }}",
150
+ "text": {{ correction.summary | jsonify }}
151
+ {%- if correction.details_url -%}
152
+ {%- assign correction_url = correction.details_url -%}
153
+ {%- unless correction_url contains '://' -%}{%- assign correction_url = correction_url | absolute_url -%}{%- endunless -%},
154
+ "url": {{ correction_url | jsonify }}
155
+ {%- endif -%}
156
+ }{% unless forloop.last %},{% endunless %}
157
+ {%- endfor -%}
158
+ ]
159
+ {%- endif -%}
126
160
  }
127
161
  </script>
128
162
  {%- comment -%} Add BreadcrumbList schema for posts {%- endcomment -%}