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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +63 -0
- data/CITATION.cff +2 -2
- data/README.md +4 -2
- data/_data/i18n/en.yml +297 -0
- data/_data/i18n/es.yml +297 -0
- data/_data/i18n/pt.yml +297 -0
- data/_data/js_manifest.json +16 -0
- data/_includes/components/author-bio.html +21 -11
- data/_includes/components/author-list.html +32 -0
- data/_includes/components/citation-tools.html +33 -25
- data/_includes/components/comments-thread.html +90 -0
- data/_includes/components/contact-form.html +120 -0
- data/_includes/components/correction-report.html +82 -0
- data/_includes/components/enhanced-toc.html +6 -7
- data/_includes/components/license-link.html +13 -0
- data/_includes/components/license-notice.html +28 -0
- data/_includes/components/moderation-inbox.html +139 -0
- data/_includes/components/reactions.html +49 -0
- data/_includes/components/reading-list.html +31 -0
- data/_includes/components/reading-mode-toggle.html +65 -0
- data/_includes/components/reading-state-bookmark.html +27 -0
- data/_includes/components/reading-state-panel.html +71 -0
- data/_includes/components/reproducibility.html +61 -0
- data/_includes/components/responsive-image.html +3 -3
- data/_includes/components/revision-history.html +39 -0
- data/_includes/components/revision-notice.html +35 -0
- data/_includes/components/series-nav.html +64 -0
- data/_includes/components/subscribe-form.html +88 -0
- data/_includes/components/subscription-manage.html +68 -0
- data/_includes/components/webmentions.html +48 -0
- data/_includes/csp-meta.html +21 -1
- data/_includes/footer.html +3 -0
- data/_includes/head.html +51 -40
- data/_includes/layouts/default/article.html +4 -6
- data/_includes/meta/dynamic-services-config.html +14 -0
- data/_includes/meta/math-config.html +9 -5
- data/_includes/meta/person-json.html +27 -0
- data/_includes/meta/publisher.html +45 -0
- data/_includes/meta/schema.html +62 -28
- data/_includes/meta/scholarly.html +121 -0
- data/_includes/meta/scripts-loader.html +2 -0
- data/_includes/meta/webmention-discovery.html +14 -0
- data/_includes/scripts.html +57 -0
- data/_layouts/dataset.html +4 -3
- data/_layouts/default.html +1 -1
- data/_layouts/package.html +3 -2
- data/_layouts/page.html +15 -0
- data/_layouts/post.html +82 -18
- data/_layouts/project.html +2 -2
- data/_layouts/research.html +20 -8
- data/_plugins/authors.rb +133 -0
- data/_plugins/config_validator.rb +223 -20
- data/_plugins/critical_css_check.rb +42 -0
- data/_plugins/i18n.rb +6 -4
- data/_plugins/image_optimizer.rb +224 -172
- data/_plugins/licenses.rb +135 -0
- data/_plugins/math_preprocessor.rb +32 -10
- data/_plugins/references.rb +238 -0
- data/_plugins/reproducibility.rb +150 -0
- data/_plugins/revisions.rb +101 -0
- data/_plugins/scholarly.rb +50 -0
- data/_plugins/series.rb +104 -0
- data/_plugins/statements.rb +87 -0
- data/_sass/_base.scss +8 -1
- data/_sass/_comments-thread.scss +159 -0
- data/_sass/_layout.scss +384 -0
- data/_sass/_mathematical.scss +27 -0
- data/_sass/_moderation.scss +222 -0
- data/_sass/_post-components.scss +4 -2
- data/_sass/_print.scss +291 -0
- data/_sass/_reactions.scss +89 -0
- data/_sass/_reading-state.scss +290 -0
- data/_sass/_service-forms.scss +204 -0
- data/_sass/_subscriptions.scss +140 -0
- data/_sass/_syntax-highlighting.scss +2 -2
- data/_sass/_theme.scss +11 -0
- data/_sass/_typography.scss +116 -0
- data/_sass/_utilities.scss +5 -0
- data/_sass/_variables.scss +6 -0
- data/_sass/_webmentions.scss +125 -0
- data/assets/js/dist/academic.js +1 -1
- data/assets/js/dist/analytics-dashboard.js +1 -1
- data/assets/js/dist/chunks/chunk-2DYDWUFX.js +1 -0
- data/assets/js/dist/chunks/chunk-PATLC23F.js +1 -0
- data/assets/js/dist/chunks/chunk-V7734B2G.js +1 -0
- data/assets/js/dist/comments.js +2 -0
- data/assets/js/dist/contact.js +1 -0
- data/assets/js/dist/core.js +1 -1
- data/assets/js/dist/corrections.js +1 -0
- data/assets/js/dist/loader.js +1 -1
- data/assets/js/dist/math.js +1 -1
- data/assets/js/dist/moderation.js +1 -0
- data/assets/js/dist/notebook.js +1 -1
- data/assets/js/dist/reactions.js +1 -0
- data/assets/js/dist/reading-state.js +1 -0
- data/assets/js/dist/search.js +1 -1
- data/assets/js/dist/sources.json +52 -0
- data/assets/js/dist/subscriptions.js +1 -0
- data/assets/js/dist/webmentions.js +1 -0
- data/assets/js/loader.js +34 -0
- data/datalog-theme.gemspec +1 -2
- data/lib/datalog/cli.rb +9 -1
- data/lib/datalog/critical_css.rb +168 -0
- data/lib/datalog/plugins/comments.rb +33 -3
- data/lib/datalog/theme/installed_files.rb +113 -0
- data/lib/datalog/theme/repository_checkout.rb +6 -2
- data/lib/datalog/theme/version.rb +5 -1
- data/lib/datalog-theme.rb +1 -0
- metadata +57 -23
- data/assets/js/dist/chunks/chunk-225H5YXE.js +0 -1
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
{%- comment -%}
|
|
2
2
|
Author bio card with social links and credentials
|
|
3
3
|
Parameters:
|
|
4
|
-
-
|
|
4
|
+
- person: An author record from `page | page_authors` (default: the page's first author)
|
|
5
|
+
- author: An author's name or _data/authors.yml key, when no person is given
|
|
5
6
|
- show_avatar: Show author avatar (default: true)
|
|
6
7
|
- show_social: Show social links (default: true)
|
|
7
|
-
- compact:
|
|
8
|
+
- compact: Avatar, name, affiliation and links only (default: false)
|
|
8
9
|
{%- endcomment -%}
|
|
9
10
|
|
|
10
|
-
{%- assign author_name = include.author | default: page.author | default: site.author.name -%}
|
|
11
11
|
{%- assign show_avatar = include.show_avatar | default: true -%}
|
|
12
12
|
{%- assign show_social = include.show_social | default: true -%}
|
|
13
13
|
{%- assign compact = include.compact | default: false -%}
|
|
14
14
|
|
|
15
|
-
{%-
|
|
16
|
-
{%-
|
|
17
|
-
|
|
15
|
+
{%- if include.person -%}
|
|
16
|
+
{%- assign author_data = include.person -%}
|
|
17
|
+
{%- elsif include.author -%}
|
|
18
|
+
{%- if site.data.authors and site.data.authors[include.author] -%}
|
|
19
|
+
{%- assign author_data = site.data.authors[include.author] -%}
|
|
20
|
+
{%- else -%}
|
|
21
|
+
{%- assign author_data = site.author -%}
|
|
22
|
+
{%- endif -%}
|
|
18
23
|
{%- else -%}
|
|
19
|
-
{%- assign
|
|
24
|
+
{%- assign bio_authors = page | page_authors -%}
|
|
25
|
+
{%- assign author_data = bio_authors | first -%}
|
|
20
26
|
{%- endif -%}
|
|
27
|
+
{%- assign author_name = author_data.name | default: include.author | default: site.author.name | escape -%}
|
|
21
28
|
|
|
22
29
|
<aside class="author-bio{% if compact %} author-bio--compact{% endif %}" itemscope itemtype="https://schema.org/Person">
|
|
23
30
|
<div class="author-bio__inner">
|
|
@@ -33,7 +40,10 @@ Parameters:
|
|
|
33
40
|
height="80">
|
|
34
41
|
{%- else -%}
|
|
35
42
|
{%- comment -%} Fallback avatar with initials {%- endcomment -%}
|
|
36
|
-
{%-
|
|
43
|
+
{%- comment -%} `map: 'first'` on a list of words returned nothing, so the placeholder showed no initials. {%- endcomment -%}
|
|
44
|
+
{%- assign name_words = author_data.name | default: author_name | split: ' ' -%}
|
|
45
|
+
{%- capture initials -%}{%- for word in name_words limit: 2 -%}{{ word | slice: 0 }}{%- endfor -%}{%- endcapture -%}
|
|
46
|
+
{%- assign initials = initials | upcase | escape -%}
|
|
37
47
|
<div class="author-bio__avatar-placeholder" role="img" aria-label="{{ author_name }}">
|
|
38
48
|
<span aria-hidden="true">{{ initials }}</span>
|
|
39
49
|
</div>
|
|
@@ -46,17 +56,17 @@ Parameters:
|
|
|
46
56
|
|
|
47
57
|
{%- if author_data.affiliation -%}
|
|
48
58
|
<p class="author-bio__affiliation" itemprop="affiliation">
|
|
49
|
-
{{ author_data.affiliation }}
|
|
59
|
+
{{ author_data.affiliation | escape }}
|
|
50
60
|
</p>
|
|
51
61
|
{%- endif -%}
|
|
52
62
|
|
|
53
|
-
{%- if author_data.biography or author_data.bio -%}
|
|
63
|
+
{%- if compact != true and author_data.biography or compact != true and author_data.bio -%}
|
|
54
64
|
<p class="author-bio__description" itemprop="description">
|
|
55
65
|
{{ author_data.biography | default: author_data.bio }}
|
|
56
66
|
</p>
|
|
57
67
|
{%- endif -%}
|
|
58
68
|
|
|
59
|
-
{%- if author_data.research_areas and author_data.research_areas.size > 0 -%}
|
|
69
|
+
{%- if compact != true and author_data.research_areas and author_data.research_areas.size > 0 -%}
|
|
60
70
|
<div class="author-bio__research">
|
|
61
71
|
<strong class="author-bio__research-label">Research interests:</strong>
|
|
62
72
|
<span class="author-bio__research-list">{{ author_data.research_areas | join: ', ' }}</span>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
---
|
|
3
|
+
required:
|
|
4
|
+
- people
|
|
5
|
+
optional:
|
|
6
|
+
- property
|
|
7
|
+
- show_affiliation
|
|
8
|
+
---
|
|
9
|
+
Lists the people who wrote or contributed to a page as schema.org Person
|
|
10
|
+
items: each name, linked to the person's url when there is one, with their
|
|
11
|
+
role and affiliation. `people` comes from `page | page_authors` or
|
|
12
|
+
`page | page_contributors`; `property` is the microdata property, author
|
|
13
|
+
(the default) or contributor.
|
|
14
|
+
{%- endcomment -%}
|
|
15
|
+
{%- assign list_property = include.property | default: 'author' -%}
|
|
16
|
+
<ul class="author-list">
|
|
17
|
+
{%- for person in include.people -%}
|
|
18
|
+
<li class="author-list__person" itemprop="{{ list_property }}" itemscope itemtype="https://schema.org/Person">
|
|
19
|
+
{%- if person.url -%}
|
|
20
|
+
{%- assign person_href = person.url -%}
|
|
21
|
+
{%- unless person_href contains '://' -%}{%- assign person_href = person_href | relative_url -%}{%- endunless -%}
|
|
22
|
+
<a href="{{ person_href | escape }}" itemprop="url"><span itemprop="name">{{ person.name | escape }}</span></a>
|
|
23
|
+
{%- else -%}
|
|
24
|
+
<span itemprop="name">{{ person.name | escape }}</span>
|
|
25
|
+
{%- endif -%}
|
|
26
|
+
{%- if person.role %} <span class="author-list__role">({{ person.role | escape }})</span>{%- endif -%}
|
|
27
|
+
{%- if person.affiliation and include.show_affiliation != false -%}
|
|
28
|
+
<span class="post-author-affiliation" itemprop="affiliation">{{ person.affiliation | escape }}</span>
|
|
29
|
+
{%- endif -%}
|
|
30
|
+
</li>
|
|
31
|
+
{%- endfor -%}
|
|
32
|
+
</ul>
|
|
@@ -2,27 +2,36 @@
|
|
|
2
2
|
{% assign citation_url = include.url | default: page.url | default: '/' | absolute_url %}
|
|
3
3
|
{% assign citation_date = include.date | default: page.date %}
|
|
4
4
|
{% assign citation_year = citation_date | date: '%Y' %}
|
|
5
|
-
{
|
|
6
|
-
{%
|
|
7
|
-
{% assign
|
|
5
|
+
{%- comment -%} `authors`: records from `page | page_authors`, or names. Without it, the page's authors. {%- endcomment -%}
|
|
6
|
+
{% if include.authors %}
|
|
7
|
+
{% assign authors_array = include.authors %}
|
|
8
|
+
{% else %}
|
|
9
|
+
{% assign authors_array = page | page_authors %}
|
|
10
|
+
{% endif %}
|
|
11
|
+
{%- comment -%} A configured publisher, else the site's name. The author's affiliation is not the publisher. {%- endcomment -%}
|
|
12
|
+
{% assign publisher = include.publisher | default: site.publisher.name | default: site.title %}
|
|
8
13
|
{% assign doi = include.doi | default: page.doi %}
|
|
9
14
|
{% assign citation_type = include.citation_type | default: include.type | default: 'article' %}
|
|
10
15
|
{% assign citation_key = include.citation_key | default: page.citation_key | default: page.slug | default: citation_title | slugify %}
|
|
11
|
-
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
{%
|
|
18
|
-
{
|
|
19
|
-
{
|
|
20
|
-
{
|
|
21
|
-
{
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
{
|
|
16
|
+
{%- comment -%}
|
|
17
|
+
The author loops trim their whitespace: with several authors, the indentation
|
|
18
|
+
ended up inside the BibTeX author field and between the RIS author lines.
|
|
19
|
+
{%- endcomment -%}
|
|
20
|
+
{%- capture author_line -%}
|
|
21
|
+
{%- for author in authors_array -%}
|
|
22
|
+
{{- author.name | default: author | strip | escape -}}{%- unless forloop.last %} and {% endunless -%}
|
|
23
|
+
{%- endfor -%}
|
|
24
|
+
{%- endcapture -%}
|
|
25
|
+
{%- capture ris_authors -%}
|
|
26
|
+
{%- for author in authors_array %}
|
|
27
|
+
AU - {{ author.name | default: author | strip | escape }}
|
|
28
|
+
{%- endfor -%}
|
|
29
|
+
{%- endcapture -%}
|
|
30
|
+
{%- capture endnote_authors -%}
|
|
31
|
+
{%- for author in authors_array %}
|
|
32
|
+
%A {{ author.name | default: author | strip | escape }}
|
|
33
|
+
{%- endfor -%}
|
|
34
|
+
{%- endcapture -%}
|
|
26
35
|
{% capture bibtex_entry %}@{{ citation_type }}{ {{ citation_key }},
|
|
27
36
|
title = { {{ citation_title | strip }} },
|
|
28
37
|
author = { {{ author_line | strip }} },
|
|
@@ -44,7 +53,7 @@ ER -
|
|
|
44
53
|
{% endcapture %}
|
|
45
54
|
{% capture endnote_entry %}%0 {{ citation_type | capitalize }}
|
|
46
55
|
%T {{ citation_title | strip }}
|
|
47
|
-
{{
|
|
56
|
+
{{ endnote_authors | strip }}
|
|
48
57
|
{% if citation_year %}%D {{ citation_year }}
|
|
49
58
|
{% endif %}
|
|
50
59
|
{% if publisher %}%I {{ publisher | strip }}
|
|
@@ -53,12 +62,11 @@ ER -
|
|
|
53
62
|
{% endif %}
|
|
54
63
|
%U {{ citation_url }}
|
|
55
64
|
{% endcapture %}
|
|
56
|
-
{
|
|
57
|
-
{
|
|
58
|
-
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
{% endcapture %}
|
|
65
|
+
{%- capture authors_plain -%}
|
|
66
|
+
{%- for author in authors_array -%}
|
|
67
|
+
{{- author.name | default: author | strip | escape -}}{%- unless forloop.last -%}, {% endunless -%}
|
|
68
|
+
{%- endfor -%}
|
|
69
|
+
{%- endcapture -%}
|
|
62
70
|
{% capture citation_text %}
|
|
63
71
|
{{ authors_plain | strip }}{% if citation_year %} ({{ citation_year }}){% endif %}. {{ citation_title | strip }}. {{ publisher | strip }}. {{ citation_url }}.
|
|
64
72
|
{% endcapture %}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
---
|
|
3
|
+
optional: []
|
|
4
|
+
---
|
|
5
|
+
The comments thread of the `api` provider (lib/datalog/plugins/comments.rb
|
|
6
|
+
renders it through the datalog_comments tag; assets/js/comments/thread.js
|
|
7
|
+
fills it): the discussion under an article, read from and written to the
|
|
8
|
+
site's own backend through the dynamic services (docs/dynamic-services.md).
|
|
9
|
+
Reads `page.datalog_comments` (the plugin's resolved settings: `replies`,
|
|
10
|
+
`moderation`, `endpoint`) and `page.url`, which keys the thread.
|
|
11
|
+
{%- endcomment -%}
|
|
12
|
+
{%- assign settings = page.datalog_comments -%}
|
|
13
|
+
{%- capture labels_json -%}
|
|
14
|
+
{
|
|
15
|
+
"loading": {{ 'comments.loading' | t | jsonify }},
|
|
16
|
+
"empty": {{ 'comments.empty' | t | jsonify }},
|
|
17
|
+
"retry": {{ 'comments.retry' | t | jsonify }},
|
|
18
|
+
"reply": {{ 'comments.reply' | t | jsonify }},
|
|
19
|
+
"replying_to": {{ 'comments.replying_to' | t | jsonify }},
|
|
20
|
+
"anonymous": {{ 'comments.anonymous' | t | jsonify }},
|
|
21
|
+
"pending": {{ 'comments.pending' | t | jsonify }},
|
|
22
|
+
"sending": {{ 'comments.sending' | t | jsonify }},
|
|
23
|
+
"posted": {{ 'comments.posted' | t | jsonify }},
|
|
24
|
+
"posted_pending": {{ 'comments.posted_pending' | t | jsonify }},
|
|
25
|
+
"name_required": {{ 'comments.name_required' | t | jsonify }},
|
|
26
|
+
"email_invalid": {{ 'comments.email_invalid' | t | jsonify }},
|
|
27
|
+
"url_invalid": {{ 'comments.url_invalid' | t | jsonify }},
|
|
28
|
+
"body_short": {{ 'comments.body_short' | t | jsonify }},
|
|
29
|
+
"errors": {
|
|
30
|
+
{%- assign kinds = "disabled,unsupported,version,timeout,network,malformed,unauthorized,forbidden,not_found,conflict,invalid,rate_limited,server,http,aborted,reference" | split: "," -%}
|
|
31
|
+
{%- for kind in kinds -%}
|
|
32
|
+
{%- assign kind_key = "dynamic_services.errors." | append: kind -%}
|
|
33
|
+
{{ kind | jsonify }}: {{ kind_key | t | jsonify }}{% unless forloop.last %},{% endunless %}
|
|
34
|
+
{%- endfor -%}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
{%- endcapture -%}
|
|
38
|
+
<div class="comments-thread" id="comments" data-comments-thread data-comments-path="{{ page.url }}" data-comments-replies="{% if settings.replies == false %}false{% else %}true{% endif %}"{% if settings.endpoint and settings.endpoint != '' %} data-comments-endpoint="{{ settings.endpoint }}"{% endif %} data-state="idle">
|
|
39
|
+
<script type="application/json" nonce="{{ page.csp_nonce }}" data-comments-labels>{{ labels_json | strip }}</script>
|
|
40
|
+
<p class="comments-thread__status" data-comments-status role="status" aria-live="polite">{% t 'comments.loading' %}</p>
|
|
41
|
+
<noscript><p class="comments-thread__noscript">{% t 'comments.noscript' %}</p></noscript>
|
|
42
|
+
<ol class="comments-thread__list" data-comments-list hidden></ol>
|
|
43
|
+
<form class="service-form comments-form" id="comments-form" data-comments-form data-state="idle" aria-busy="false" aria-labelledby="comments-form-heading" novalidate>
|
|
44
|
+
<h3 class="comments-form__heading" id="comments-form-heading">{% t 'comments.form_heading' %}</h3>
|
|
45
|
+
<p class="comments-form__replying" data-comments-replying hidden>
|
|
46
|
+
<span data-comments-replying-to></span>
|
|
47
|
+
<button type="button" class="post-tool" data-comments-cancel-reply>{% t 'comments.cancel_reply' %}</button>
|
|
48
|
+
</p>
|
|
49
|
+
<fieldset class="service-form__fields">
|
|
50
|
+
<legend class="visually-hidden">{% t 'comments.form_heading' %}</legend>
|
|
51
|
+
<div class="service-form__row">
|
|
52
|
+
<div class="service-form__field">
|
|
53
|
+
<label for="comments-name">{% t 'comments.name' %}</label>
|
|
54
|
+
<input id="comments-name" name="name" type="text" required autocomplete="name">
|
|
55
|
+
<p class="service-form__error" data-error-for="name" hidden></p>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="service-form__field">
|
|
58
|
+
<label for="comments-email">{% t 'comments.email' %}</label>
|
|
59
|
+
<input id="comments-email" name="email" type="email" autocomplete="email" aria-describedby="comments-email-hint">
|
|
60
|
+
<p class="service-form__hint" id="comments-email-hint">{% t 'comments.email_hint' %}</p>
|
|
61
|
+
<p class="service-form__error" data-error-for="email" hidden></p>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="service-form__field">
|
|
65
|
+
<label for="comments-url">{% t 'comments.url' %}</label>
|
|
66
|
+
<input id="comments-url" name="url" type="url" autocomplete="url">
|
|
67
|
+
<p class="service-form__error" data-error-for="url" hidden></p>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="service-form__field">
|
|
70
|
+
<label for="comments-body">{% t 'comments.body' %}</label>
|
|
71
|
+
<textarea id="comments-body" name="body" rows="5" required></textarea>
|
|
72
|
+
<p class="service-form__error" data-error-for="body" hidden></p>
|
|
73
|
+
</div>
|
|
74
|
+
<input type="hidden" name="parent_id" value="">
|
|
75
|
+
<div class="service-form__trap" aria-hidden="true">
|
|
76
|
+
<label for="comments-website">Website</label>
|
|
77
|
+
<input id="comments-website" name="website" type="text" tabindex="-1" autocomplete="off">
|
|
78
|
+
</div>
|
|
79
|
+
</fieldset>
|
|
80
|
+
{%- if settings.moderation != false %}
|
|
81
|
+
<p class="service-form__privacy">{% t 'comments.moderated' %}</p>
|
|
82
|
+
{%- else %}
|
|
83
|
+
<p class="service-form__privacy">{% t 'comments.privacy' %}</p>
|
|
84
|
+
{%- endif %}
|
|
85
|
+
<div class="service-form__actions">
|
|
86
|
+
<button type="submit" class="post-tool">{% t 'comments.send' %}</button>
|
|
87
|
+
</div>
|
|
88
|
+
<p class="service-form__status" data-form-status role="status" aria-live="polite" hidden></p>
|
|
89
|
+
</form>
|
|
90
|
+
</div>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
---
|
|
3
|
+
optional: []
|
|
4
|
+
---
|
|
5
|
+
The contact and collaboration form (assets/js/contact/form.js): a structured
|
|
6
|
+
message to the author, research collaboration, consulting, speaking,
|
|
7
|
+
mentoring, a reproducibility question, a media request or anything else,
|
|
8
|
+
sent to the site's dynamic services (docs/dynamic-services.md). Rendered by
|
|
9
|
+
the page layout when the page says `contact_form: true`, or included
|
|
10
|
+
anywhere. Without a backend offering the feature it falls back to the
|
|
11
|
+
site's email address, so the page is never empty. Categories come from
|
|
12
|
+
`contact.categories`, their labels and prompts from `contact.categories.*`
|
|
13
|
+
and `contact.prompts.*` in _data/i18n; `contact.prompts` in _config.yml
|
|
14
|
+
overrides a prompt.
|
|
15
|
+
{%- endcomment -%}
|
|
16
|
+
{%- assign services = site.dynamic_services -%}
|
|
17
|
+
{%- assign contact = site.contact -%}
|
|
18
|
+
{%- if contact.enabled != false -%}
|
|
19
|
+
{%- assign mail = site.contact_email | default: site.author.email -%}
|
|
20
|
+
{%- if services.base_url and services.base_url != '' and services.features.contact != false -%}
|
|
21
|
+
{%- assign categories = contact.categories -%}
|
|
22
|
+
{%- if categories == nil or categories.size == 0 -%}
|
|
23
|
+
{%- assign categories = "research-collaboration,consulting,speaking,mentoring,reproducibility,media,other" | split: "," -%}
|
|
24
|
+
{%- endif -%}
|
|
25
|
+
{%- capture labels_json -%}
|
|
26
|
+
{
|
|
27
|
+
"pending": {{ 'contact.pending' | t | jsonify }},
|
|
28
|
+
"success": {{ 'contact.success' | t | jsonify }},
|
|
29
|
+
"name_required": {{ 'contact.name_required' | t | jsonify }},
|
|
30
|
+
"email_invalid": {{ 'contact.email_invalid' | t | jsonify }},
|
|
31
|
+
"subject_required": {{ 'contact.subject_required' | t | jsonify }},
|
|
32
|
+
"message_short": {{ 'contact.message_short' | t | jsonify }},
|
|
33
|
+
"errors": {
|
|
34
|
+
{%- assign kinds = "disabled,unsupported,version,timeout,network,malformed,unauthorized,forbidden,not_found,conflict,invalid,rate_limited,server,http,aborted,reference" | split: "," -%}
|
|
35
|
+
{%- for kind in kinds -%}
|
|
36
|
+
{%- assign kind_key = "dynamic_services.errors." | append: kind -%}
|
|
37
|
+
{{ kind | jsonify }}: {{ kind_key | t | jsonify }}{% unless forloop.last %},{% endunless %}
|
|
38
|
+
{%- endfor -%}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
{%- endcapture -%}
|
|
42
|
+
{%- capture prompts_json -%}
|
|
43
|
+
{
|
|
44
|
+
{%- for category in categories -%}
|
|
45
|
+
{%- assign prompt = nil -%}
|
|
46
|
+
{%- if contact.prompts -%}{%- assign prompt = contact.prompts[category] -%}{%- endif -%}
|
|
47
|
+
{%- unless prompt -%}
|
|
48
|
+
{%- assign prompt_key = "contact.prompts." | append: category -%}
|
|
49
|
+
{%- assign prompt = prompt_key | t -%}
|
|
50
|
+
{%- if prompt == prompt_key -%}{%- assign prompt = nil -%}{%- endif -%}
|
|
51
|
+
{%- endunless -%}
|
|
52
|
+
{%- if prompt -%}{{ category | jsonify }}: {{ prompt | jsonify }},{%- endif -%}
|
|
53
|
+
{%- endfor -%}
|
|
54
|
+
"": ""
|
|
55
|
+
}
|
|
56
|
+
{%- endcapture -%}
|
|
57
|
+
{%- assign privacy = contact.privacy_notice | default: nil -%}
|
|
58
|
+
{%- unless privacy -%}{%- assign privacy = 'contact.privacy' | t -%}{%- endunless -%}
|
|
59
|
+
<section class="contact-form" id="contact" data-contact-form data-source-url="{{ page.url | absolute_url }}">
|
|
60
|
+
<script type="application/json" nonce="{{ page.csp_nonce }}" data-contact-labels>{{ labels_json | strip }}</script>
|
|
61
|
+
<script type="application/json" nonce="{{ page.csp_nonce }}" data-contact-prompts>{{ prompts_json | strip }}</script>
|
|
62
|
+
<form class="service-form contact-form__form" id="contact-form" data-state="idle" aria-busy="false" novalidate>
|
|
63
|
+
<p class="service-form__intro">{% t 'contact.intro' %}</p>
|
|
64
|
+
<fieldset class="service-form__fields">
|
|
65
|
+
<legend class="visually-hidden">{% t 'contact.legend' %}</legend>
|
|
66
|
+
<div class="service-form__field">
|
|
67
|
+
<label for="contact-category">{% t 'contact.category' %}</label>
|
|
68
|
+
<select id="contact-category" name="category">
|
|
69
|
+
{%- for category in categories %}
|
|
70
|
+
{%- assign category_key = "contact.categories." | append: category %}
|
|
71
|
+
<option value="{{ category | escape }}">{{ category_key | t }}</option>
|
|
72
|
+
{%- endfor %}
|
|
73
|
+
</select>
|
|
74
|
+
</div>
|
|
75
|
+
<div class="service-form__row">
|
|
76
|
+
<div class="service-form__field">
|
|
77
|
+
<label for="contact-name">{% t 'contact.name' %}</label>
|
|
78
|
+
<input id="contact-name" name="name" type="text" required autocomplete="name">
|
|
79
|
+
<p class="service-form__error" data-error-for="name" hidden></p>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="service-form__field">
|
|
82
|
+
<label for="contact-email">{% t 'contact.email' %}</label>
|
|
83
|
+
<input id="contact-email" name="email" type="email" required autocomplete="email">
|
|
84
|
+
<p class="service-form__error" data-error-for="email" hidden></p>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="service-form__field">
|
|
88
|
+
<label for="contact-affiliation">{% t 'contact.affiliation' %}</label>
|
|
89
|
+
<input id="contact-affiliation" name="affiliation" type="text" autocomplete="organization">
|
|
90
|
+
</div>
|
|
91
|
+
<div class="service-form__field">
|
|
92
|
+
<label for="contact-subject">{% t 'contact.subject' %}</label>
|
|
93
|
+
<input id="contact-subject" name="subject" type="text" required>
|
|
94
|
+
<p class="service-form__error" data-error-for="subject" hidden></p>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="service-form__field">
|
|
97
|
+
<label for="contact-message">{% t 'contact.message' %}</label>
|
|
98
|
+
<textarea id="contact-message" name="message" rows="7" required minlength="20" aria-describedby="contact-message-hint"></textarea>
|
|
99
|
+
<p class="service-form__hint" id="contact-message-hint" data-contact-hint>{% t 'contact.message_hint' %}</p>
|
|
100
|
+
<p class="service-form__error" data-error-for="message" hidden></p>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="service-form__trap" aria-hidden="true">
|
|
103
|
+
<label for="contact-website">Website</label>
|
|
104
|
+
<input id="contact-website" name="website" type="text" tabindex="-1" autocomplete="off">
|
|
105
|
+
</div>
|
|
106
|
+
</fieldset>
|
|
107
|
+
<p class="service-form__privacy">{{ privacy }}{% if contact.retention %} {{ contact.retention }}{% endif %}</p>
|
|
108
|
+
<div class="service-form__actions">
|
|
109
|
+
<button type="submit" class="post-tool">{% t 'contact.send' %}</button>
|
|
110
|
+
</div>
|
|
111
|
+
<p class="service-form__status" data-form-status role="status" aria-live="polite" hidden></p>
|
|
112
|
+
</form>
|
|
113
|
+
</section>
|
|
114
|
+
{%- elsif mail -%}
|
|
115
|
+
{%- capture mail_link -%}<a href="mailto:{{ mail }}">{{ mail }}</a>{%- endcapture -%}
|
|
116
|
+
<section class="contact-form contact-form--fallback" id="contact">
|
|
117
|
+
<p class="contact-form__fallback">{% t 'contact.fallback' email: mail_link %}</p>
|
|
118
|
+
</section>
|
|
119
|
+
{%- endif -%}
|
|
120
|
+
{%- endif -%}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
---
|
|
3
|
+
optional: []
|
|
4
|
+
---
|
|
5
|
+
"Report an error or suggest a correction" (assets/js/corrections/form.js):
|
|
6
|
+
structured feedback about an article, apart from comments, sent to the
|
|
7
|
+
site's dynamic services (docs/dynamic-services.md) and never shown on the
|
|
8
|
+
page. Renders only when the site has a backend with the corrections feature
|
|
9
|
+
on, `corrections.enabled` is not false, and the page does not say
|
|
10
|
+
`corrections: false`. The categories come from `corrections.categories`,
|
|
11
|
+
their labels from `corrections.categories.*` in _data/i18n.
|
|
12
|
+
{%- endcomment -%}
|
|
13
|
+
{%- assign services = site.dynamic_services -%}
|
|
14
|
+
{%- assign corrections = site.corrections -%}
|
|
15
|
+
{%- if services.base_url and services.base_url != '' and services.features.corrections != false and corrections.enabled != false and page.corrections != false -%}
|
|
16
|
+
{%- assign categories = corrections.categories -%}
|
|
17
|
+
{%- if categories == nil or categories.size == 0 -%}
|
|
18
|
+
{%- assign categories = "mathematical-error,factual-error,citation,code,reproducibility,typo,accessibility,other" | split: "," -%}
|
|
19
|
+
{%- endif -%}
|
|
20
|
+
{%- capture labels_json -%}
|
|
21
|
+
{
|
|
22
|
+
"pending": {{ 'corrections.pending' | t | jsonify }},
|
|
23
|
+
"success": {{ 'corrections.success' | t | jsonify }},
|
|
24
|
+
"message_short": {{ 'corrections.message_short' | t | jsonify }},
|
|
25
|
+
"email_invalid": {{ 'corrections.email_invalid' | 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
|
+
<details class="correction-report" id="correction-report" data-correction-report data-article-url="{{ page.url | absolute_url }}" data-article-title="{{ page.title | escape }}">
|
|
36
|
+
<summary class="correction-report__summary">{% t 'corrections.summary' %}</summary>
|
|
37
|
+
<script type="application/json" nonce="{{ page.csp_nonce }}" data-correction-labels>{{ labels_json | strip }}</script>
|
|
38
|
+
<form class="service-form correction-report__form" id="correction-report-form" data-state="idle" aria-busy="false" novalidate>
|
|
39
|
+
<p class="service-form__intro">{% t 'corrections.intro' %}</p>
|
|
40
|
+
<p class="service-form__quote" data-correction-quote hidden></p>
|
|
41
|
+
<fieldset class="service-form__fields">
|
|
42
|
+
<legend class="visually-hidden">{% t 'corrections.summary' %}</legend>
|
|
43
|
+
<div class="service-form__field">
|
|
44
|
+
<label for="correction-category">{% t 'corrections.category' %}</label>
|
|
45
|
+
<select id="correction-category" name="category">
|
|
46
|
+
{%- for category in categories %}
|
|
47
|
+
{%- assign category_key = "corrections.categories." | append: category %}
|
|
48
|
+
<option value="{{ category | escape }}">{{ category_key | t }}</option>
|
|
49
|
+
{%- endfor %}
|
|
50
|
+
</select>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="service-form__field">
|
|
53
|
+
<label for="correction-section">{% t 'corrections.section' %}</label>
|
|
54
|
+
<select id="correction-section" name="section">
|
|
55
|
+
<option value="">{% t 'corrections.section_any' %}</option>
|
|
56
|
+
</select>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="service-form__field">
|
|
59
|
+
<label for="correction-message">{% t 'corrections.message' %}</label>
|
|
60
|
+
<textarea id="correction-message" name="message" rows="5" required minlength="20" aria-describedby="correction-message-hint"></textarea>
|
|
61
|
+
<p class="service-form__hint" id="correction-message-hint">{% t 'corrections.message_hint' %}</p>
|
|
62
|
+
<p class="service-form__error" data-error-for="message" hidden></p>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="service-form__field">
|
|
65
|
+
<label for="correction-email">{% t 'corrections.contact_email' %}</label>
|
|
66
|
+
<input id="correction-email" name="contact_email" type="email" autocomplete="email" aria-describedby="correction-email-hint">
|
|
67
|
+
<p class="service-form__hint" id="correction-email-hint">{% t 'corrections.contact_hint' %}</p>
|
|
68
|
+
<p class="service-form__error" data-error-for="contact_email" hidden></p>
|
|
69
|
+
</div>
|
|
70
|
+
<input type="hidden" name="quote" value="">
|
|
71
|
+
<div class="service-form__trap" aria-hidden="true">
|
|
72
|
+
<label for="correction-website">Website</label>
|
|
73
|
+
<input id="correction-website" name="website" type="text" tabindex="-1" autocomplete="off">
|
|
74
|
+
</div>
|
|
75
|
+
</fieldset>
|
|
76
|
+
<div class="service-form__actions">
|
|
77
|
+
<button type="submit" class="post-tool">{% t 'corrections.send' %}</button>
|
|
78
|
+
</div>
|
|
79
|
+
<p class="service-form__status" data-form-status role="status" aria-live="polite" hidden></p>
|
|
80
|
+
</form>
|
|
81
|
+
</details>
|
|
82
|
+
{%- endif -%}
|
|
@@ -27,7 +27,8 @@ Parameters:
|
|
|
27
27
|
{%- endcapture -%}
|
|
28
28
|
|
|
29
29
|
{%- if toc_markup contains '<li' -%}
|
|
30
|
-
<
|
|
30
|
+
{%- comment -%} A <nav>: doc-toc refines the navigation landmark, and is not a role an <aside> may carry. {%- endcomment -%}
|
|
31
|
+
<nav class="enhanced-toc{% if collapsible %} enhanced-toc--collapsible{% endif %}"
|
|
31
32
|
data-toc-enhanced
|
|
32
33
|
aria-label="{{ toc_heading }}"
|
|
33
34
|
role="doc-toc">
|
|
@@ -87,7 +88,7 @@ Parameters:
|
|
|
87
88
|
<span data-progress-percent>0</span>% complete
|
|
88
89
|
</span>
|
|
89
90
|
</div>
|
|
90
|
-
</
|
|
91
|
+
</nav>
|
|
91
92
|
|
|
92
93
|
<script>
|
|
93
94
|
(function() {
|
|
@@ -154,7 +155,8 @@ Parameters:
|
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
//
|
|
158
|
+
// Scroll to TOC targets. The stylesheet's scroll-behavior decides whether
|
|
159
|
+
// the scroll is smooth, so reduced-motion preferences are respected.
|
|
158
160
|
tocLinks.forEach(link => {
|
|
159
161
|
link.addEventListener('click', function(e) {
|
|
160
162
|
const targetId = this.getAttribute('href');
|
|
@@ -163,10 +165,7 @@ Parameters:
|
|
|
163
165
|
const targetElement = document.querySelector(targetId);
|
|
164
166
|
if (targetElement) {
|
|
165
167
|
const offsetTop = targetElement.offsetTop - 80; // Account for sticky header
|
|
166
|
-
window.scrollTo({
|
|
167
|
-
top: offsetTop,
|
|
168
|
-
behavior: 'smooth'
|
|
169
|
-
});
|
|
168
|
+
window.scrollTo({ top: offsetTop });
|
|
170
169
|
// Update URL without jumping
|
|
171
170
|
if (history.pushState) {
|
|
172
171
|
history.pushState(null, null, targetId);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
---
|
|
3
|
+
required:
|
|
4
|
+
- license
|
|
5
|
+
---
|
|
6
|
+
A licence's name (from the page_license or page_code_license filter), linked
|
|
7
|
+
with rel="license" when the licence has a URL.
|
|
8
|
+
{%- endcomment -%}
|
|
9
|
+
{%- if include.license.url -%}
|
|
10
|
+
<a class="license-link" rel="license" href="{{ include.license.url | escape }}">{{ include.license.name | escape }}</a>
|
|
11
|
+
{%- else -%}
|
|
12
|
+
<span class="license-link">{{ include.license.name | escape }}</span>
|
|
13
|
+
{%- endif -%}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
---
|
|
3
|
+
required:
|
|
4
|
+
- page
|
|
5
|
+
---
|
|
6
|
+
The reuse terms of an article: its copyright holder and year, the licence of
|
|
7
|
+
its text and figures and, when it differs, of its code samples
|
|
8
|
+
(_plugins/licenses.rb). Renders nothing for a page without a licence.
|
|
9
|
+
{%- endcomment -%}
|
|
10
|
+
{%- assign content_license = include.page | page_license -%}
|
|
11
|
+
{%- assign code_license = include.page | page_code_license -%}
|
|
12
|
+
{%- if content_license or code_license -%}
|
|
13
|
+
{%- capture content_link -%}{% include components/license-link.html license=content_license %}{%- endcapture -%}
|
|
14
|
+
{%- capture code_link -%}{% include components/license-link.html license=code_license %}{%- endcapture -%}
|
|
15
|
+
{%- assign content_link = content_link | strip -%}
|
|
16
|
+
{%- assign code_link = code_link | strip -%}
|
|
17
|
+
<p class="content-license">
|
|
18
|
+
{%- if content_license and content_license.holder %}
|
|
19
|
+
<span class="content-license__holder">©{% if content_license.year %} {{ content_license.year }}{% endif %} {{ content_license.holder | escape }}.</span>
|
|
20
|
+
{%- endif %}
|
|
21
|
+
{%- if content_license %}
|
|
22
|
+
{% if content_license.reserved %}{% t 'post.license.reserved' %}{% else %}{% t 'post.license.content' license: content_link %}{% endif %}
|
|
23
|
+
{%- endif %}
|
|
24
|
+
{%- if code_license %}
|
|
25
|
+
{% t 'post.license.code' license: code_link %}
|
|
26
|
+
{%- endif %}
|
|
27
|
+
</p>
|
|
28
|
+
{%- endif -%}
|