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,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datalog
4
+ # Article series: multi-part writing a reader follows in order, whatever
5
+ # was published in between.
6
+ #
7
+ # series: # front matter, as a map
8
+ # id: missing-data
9
+ # title: Missing Data and Statistical Inference
10
+ # order: 3
11
+ #
12
+ # series: missing-data # or flat
13
+ # series_title: Missing Data and Statistical Inference
14
+ # series_order: 3
15
+ #
16
+ # missing-data: # _data/series.yml, so the title lives once
17
+ # title: Missing Data and Statistical Inference
18
+ # description: Four parts, from the missing-data mechanisms to sensitivity analysis.
19
+ #
20
+ # Before the site renders, every part is checked (an order that is missing,
21
+ # not a whole number or taken by another part stops the build), the parts
22
+ # are put in order, and each page's `series` becomes one shape: id, title,
23
+ # description, order, position, count, parts (title, url, order, position,
24
+ # current), previous and next. _includes/components/series-nav.html reads it.
25
+ module Series
26
+ module_function
27
+
28
+ def normalize!(site)
29
+ documents = site.documents + site.pages
30
+ registry = site.data["series"].is_a?(Hash) ? site.data["series"] : {}
31
+ documents.filter_map { |document| part(document) }.group_by { |part| part[:id] }.each do |id, parts|
32
+ check_orders!(id, parts)
33
+ meta = registry[id].is_a?(Hash) ? registry[id] : {}
34
+ resolve!(id, parts.sort_by { |part| part[:order] }, meta)
35
+ end
36
+ end
37
+
38
+ # The document's part of a series, as {doc, id, order, title}, or nil.
39
+ def part(document)
40
+ data = document.data
41
+ value = data["series"]
42
+ return if value.nil? || value == false || (value.is_a?(String) && value.strip.empty?)
43
+
44
+ given = case value
45
+ when Hash then value.transform_keys(&:to_s)
46
+ when String, Symbol then { "id" => value.to_s }
47
+ else stop(document, "has a series that is neither a name nor a map with an id and an order")
48
+ end
49
+ id = given["id"].to_s.strip
50
+ stop(document, "has a series without an id, such as series: missing-data") if id.empty?
51
+
52
+ order = given["order"] || data["series_order"]
53
+ unless whole_number?(order)
54
+ stop(document, "is part of the series \"#{id}\" without an order; give the part a whole number from 1, " \
55
+ "such as order: 2")
56
+ end
57
+
58
+ { doc: document, id: id, order: order.to_i, title: given["title"] || data["series_title"] }
59
+ end
60
+
61
+ def whole_number?(value)
62
+ (value.is_a?(Integer) && value >= 1) || (value.is_a?(String) && value.match?(/\A[1-9]\d*\z/))
63
+ end
64
+
65
+ def check_orders!(id, parts)
66
+ parts.group_by { |part| part[:order] }.each_value do |same|
67
+ next if same.size == 1
68
+
69
+ paths = same.map { |part| part[:doc].relative_path }.sort.join(" and ")
70
+ raise Jekyll::Errors::FatalException,
71
+ "#{paths} are both part #{same.first[:order]} of the series \"#{id}\"; each part needs its own order"
72
+ end
73
+ end
74
+
75
+ # Writes each part's `series` from the ordered parts and the data file's entry.
76
+ def resolve!(id, ordered, meta)
77
+ title = meta["title"] || ordered.filter_map { |part| part[:title] }.first || titleize(id)
78
+ summaries = ordered.each_with_index.map do |part, index|
79
+ { "title" => part[:doc].data["title"], "url" => part[:doc].url, "order" => part[:order],
80
+ "position" => index + 1 }
81
+ end
82
+ ordered.each_with_index do |part, index|
83
+ part[:doc].data["series"] = {
84
+ "id" => id, "title" => title, "description" => meta["description"],
85
+ "order" => part[:order], "position" => index + 1, "count" => ordered.size,
86
+ "parts" => summaries.map { |summary| summary.merge("current" => summary["url"] == part[:doc].url) },
87
+ "previous" => (summaries[index - 1] if index.positive?), "next" => summaries[index + 1]
88
+ }.compact
89
+ end
90
+ end
91
+
92
+ def titleize(id)
93
+ id.tr("-_", " ").split.map(&:capitalize).join(" ")
94
+ end
95
+
96
+ def stop(document, problem)
97
+ raise Jekyll::Errors::FatalException, "#{document.relative_path} #{problem}"
98
+ end
99
+ end
100
+ end
101
+
102
+ Jekyll::Hooks.register :site, :post_read do |site|
103
+ Datalog::Series.normalize!(site)
104
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+
5
+ module Datalog
6
+ # Theorems, lemmas and the other statements of mathematical writing, and
7
+ # proofs. Articles marked them with a blockquote and a bold "Theorem 1.",
8
+ # typed by hand:
9
+ #
10
+ # {% theorem id="thm-consistency" title="Consistency" %}
11
+ # Let $\hat\theta_n$ be ...
12
+ # {% endtheorem %}
13
+ #
14
+ # {% proof for="thm-consistency" %}
15
+ # ...
16
+ # {% endproof %}
17
+ #
18
+ # Statements are numbered with figures and tables (_plugins/references.rb):
19
+ # each kind counts on its own, {% ref thm-consistency %} reads "Theorem 1",
20
+ # and a duplicate id stops the build. `label="A"` names a statement
21
+ # "Theorem A" instead of numbering it. A proof is not numbered; with `for`
22
+ # its heading reads "Proof of Theorem 1".
23
+ class StatementTag < Liquid::Block
24
+ def initialize(tag_name, markup, options)
25
+ super
26
+ @kind = tag_name
27
+ @markup = markup
28
+ end
29
+
30
+ def render(context)
31
+ attributes = References.attributes(@markup, context)
32
+ id = References.validate_id(attributes["id"], @kind)
33
+ custom = attributes["label"]
34
+ References.validate_label(custom, @kind)
35
+ body = References.markdown(context, super)
36
+
37
+ number = custom ? %( data-ref-number="#{CGI.escapeHTML(custom)}") : ""
38
+ title = attributes["title"].to_s.strip
39
+ title = title.empty? ? "" : %( <span class="datalog-statement__title">(#{CGI.escapeHTML(title)})</span>)
40
+ %(<div class="datalog-statement datalog-statement--#{@kind}" role="group" aria-labelledby="#{id}-heading" ) +
41
+ %(id="#{id}" data-ref-target="#{id}" data-ref-kind="#{@kind}"#{number}>) +
42
+ %(<p class="datalog-statement__heading" id="#{id}-heading">) +
43
+ %(<span class="datalog-ref-label" data-ref-for="#{id}" data-ref-end=""></span>#{title}.</p>) +
44
+ %(<div class="datalog-statement__body">#{body.strip}</div></div>\n)
45
+ end
46
+ end
47
+
48
+ class ProofTag < Liquid::Block
49
+ def initialize(tag_name, markup, options)
50
+ super
51
+ @markup = markup
52
+ end
53
+
54
+ def render(context)
55
+ attributes = References.attributes(@markup, context)
56
+ target = attributes["for"]
57
+ body = References.markdown(context, super)
58
+ heading_id = "proof-#{target ? References.validate_id(target, 'proof') : Statements.next_proof(context)}"
59
+ heading = if target
60
+ link = %(<a class="datalog-ref" href="##{target}" data-ref="#{target}">#{target}</a>)
61
+ I18n.translate(context, "references.proof_of", "target" => link)
62
+ else
63
+ CGI.escapeHTML(I18n.translate(context, "references.proof"))
64
+ end
65
+ end_mark = %(<p class="datalog-proof__end"><span aria-hidden="true">∎</span></p>)
66
+ end_mark = "" if attributes["qed"] == "false"
67
+
68
+ %(<div class="datalog-proof" role="group" aria-labelledby="#{heading_id}">) +
69
+ %(<p class="datalog-proof__heading" id="#{heading_id}">#{heading}.</p>) +
70
+ %(<div class="datalog-proof__body">#{body.strip}</div>#{end_mark}</div>\n)
71
+ end
72
+ end
73
+
74
+ module Statements
75
+ module_function
76
+
77
+ KINDS = %w[theorem lemma proposition corollary definition assumption example remark].freeze
78
+
79
+ # Proofs without a statement are numbered in the page for their heading ids only.
80
+ def next_proof(context)
81
+ context.registers[:datalog_proofs] = context.registers[:datalog_proofs].to_i + 1
82
+ end
83
+ end
84
+ end
85
+
86
+ Datalog::Statements::KINDS.each { |kind| Liquid::Template.register_tag(kind, Datalog::StatementTag) }
87
+ Liquid::Template.register_tag("proof", Datalog::ProofTag)
data/_sass/_base.scss CHANGED
@@ -12,7 +12,14 @@
12
12
 
13
13
  html {
14
14
  font-size: $font-size-root;
15
- scroll-behavior: smooth;
15
+ }
16
+
17
+ // Anchor links, and the theme's scripts, which pass no scroll behavior, follow
18
+ // this rule, so a reader who asks for reduced motion jumps to the target.
19
+ @media (prefers-reduced-motion: no-preference) {
20
+ html {
21
+ scroll-behavior: smooth;
22
+ }
16
23
  }
17
24
 
18
25
  body {
@@ -0,0 +1,159 @@
1
+ @use 'sass:map';
2
+
3
+ @use 'variables' as *;
4
+
5
+ // The comments thread of the `api` provider (components/comments-thread.html,
6
+ // assets/js/comments/thread.js). The form itself is a .service-form
7
+ // (_service-forms.scss).
8
+
9
+ .comments-thread {
10
+ max-width: $max-width-reading;
11
+ }
12
+
13
+ .comments-thread__status {
14
+ margin: 0 0 map.get($spacing, 3);
15
+ color: $color-ink-500;
16
+ font-size: map.get($font-size-scale, sm);
17
+
18
+ .comments-thread[data-state='error'] & {
19
+ color: $color-danger-700;
20
+ }
21
+ }
22
+
23
+ .comments-thread__noscript {
24
+ margin: 0 0 map.get($spacing, 3);
25
+ color: $color-ink-500;
26
+ }
27
+
28
+ .comments-thread__list,
29
+ .comments-thread__replies {
30
+ list-style: none;
31
+ margin: 0;
32
+ padding: 0;
33
+ }
34
+
35
+ .comments-thread__list {
36
+ display: grid;
37
+ gap: map.get($spacing, 3);
38
+ margin-bottom: map.get($spacing, 5);
39
+ }
40
+
41
+ // display: grid and display: flex would override the hidden attribute.
42
+ .comments-thread__list[hidden],
43
+ .comments-form__replying[hidden] {
44
+ display: none;
45
+ }
46
+
47
+ .comments-thread__replies {
48
+ display: grid;
49
+ gap: map.get($spacing, 3);
50
+ margin-top: map.get($spacing, 3);
51
+ padding-left: map.get($spacing, 4);
52
+ border-left: 2px solid rgba($color-neutral-500, 0.25);
53
+ }
54
+
55
+ .comment__card {
56
+ padding: map.get($spacing, 3) map.get($spacing, 4);
57
+ border-radius: map.get($radius, md);
58
+ background: rgba($color-neutral-500, 0.06);
59
+ }
60
+
61
+ .comment--pending > .comment__card {
62
+ border: 1px dashed rgba($color-neutral-500, 0.5);
63
+ background: transparent;
64
+ }
65
+
66
+ .comment__meta {
67
+ display: flex;
68
+ flex-wrap: wrap;
69
+ align-items: baseline;
70
+ gap: map.get($spacing, 2);
71
+ margin-bottom: map.get($spacing, 2);
72
+ font-size: map.get($font-size-scale, sm);
73
+ }
74
+
75
+ .comment__author {
76
+ font-weight: 600;
77
+ color: $color-ink-900;
78
+ }
79
+
80
+ .comment__time {
81
+ color: $color-ink-500;
82
+ }
83
+
84
+ .comment__pending {
85
+ padding: 0 map.get($spacing, 2);
86
+ border-radius: map.get($radius, sm);
87
+ background: rgba($color-neutral-500, 0.15);
88
+ color: $color-ink-700;
89
+ font-size: map.get($font-size-scale, xs);
90
+ }
91
+
92
+ .comment__body {
93
+ overflow-wrap: anywhere;
94
+
95
+ p {
96
+ margin: 0 0 map.get($spacing, 2);
97
+ }
98
+
99
+ p:last-child {
100
+ margin-bottom: 0;
101
+ }
102
+ }
103
+
104
+ .comment__reply {
105
+ margin-top: map.get($spacing, 2);
106
+ }
107
+
108
+ .comments-form__heading {
109
+ margin: 0 0 map.get($spacing, 2);
110
+ font-size: map.get($font-size-scale, md);
111
+ }
112
+
113
+ .comments-form__replying {
114
+ display: flex;
115
+ flex-wrap: wrap;
116
+ align-items: center;
117
+ gap: map.get($spacing, 2);
118
+ margin: 0 0 map.get($spacing, 3);
119
+ color: $color-ink-700;
120
+ font-size: map.get($font-size-scale, sm);
121
+ }
122
+
123
+ body.dark-mode {
124
+ .comments-thread__status,
125
+ .comments-thread__noscript,
126
+ .comment__time {
127
+ color: $dark-ink-500;
128
+ }
129
+
130
+ .comments-thread[data-state='error'] .comments-thread__status {
131
+ color: $color-danger-300;
132
+ }
133
+
134
+ .comment__card {
135
+ background: rgba($dark-ink-500, 0.12);
136
+ }
137
+
138
+ .comment--pending > .comment__card {
139
+ border-color: rgba($dark-ink-500, 0.5);
140
+ background: transparent;
141
+ }
142
+
143
+ .comment__author {
144
+ color: $dark-ink-900;
145
+ }
146
+
147
+ .comment__pending,
148
+ .comments-form__replying {
149
+ color: $dark-ink-700;
150
+ }
151
+
152
+ .comment__pending {
153
+ background: rgba($dark-ink-500, 0.2);
154
+ }
155
+
156
+ .comments-thread__replies {
157
+ border-left-color: rgba($dark-ink-500, 0.35);
158
+ }
159
+ }