datalog-theme 0.6.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 (180) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +162 -0
  3. data/CITATION.cff +29 -0
  4. data/LICENSE +21 -0
  5. data/README.md +257 -0
  6. data/_data/academic.yml +217 -0
  7. data/_data/cdn-integrity.yml +51 -0
  8. data/_data/config/author.yml +121 -0
  9. data/_data/config/features.yml +262 -0
  10. data/_data/config/site.yml +42 -0
  11. data/_data/config/theme.yml +181 -0
  12. data/_data/datasets.yml +28 -0
  13. data/_data/i18n/en.yml +438 -0
  14. data/_data/i18n/es.yml +438 -0
  15. data/_data/i18n/pt.yml +438 -0
  16. data/_data/js_manifest.json +22 -0
  17. data/_data/js_meta.json +371 -0
  18. data/_data/navigation.yml +145 -0
  19. data/_data/projects.yml +41 -0
  20. data/_data/publications.yml +28 -0
  21. data/_data/social.yml +73 -0
  22. data/_data/visualizations.yml +51 -0
  23. data/_includes/analytics/dashboard.html +149 -0
  24. data/_includes/analytics.html +9 -0
  25. data/_includes/components/academic-dashboard.html +347 -0
  26. data/_includes/components/advanced-search.html +682 -0
  27. data/_includes/components/api-function.html +94 -0
  28. data/_includes/components/author-bio.html +166 -0
  29. data/_includes/components/bookmark-system.html +96 -0
  30. data/_includes/components/breadcrumbs.html +89 -0
  31. data/_includes/components/citation-tools.html +94 -0
  32. data/_includes/components/comments.html +244 -0
  33. data/_includes/components/content-provenance.html +43 -0
  34. data/_includes/components/content-recommendations.html +228 -0
  35. data/_includes/components/difficulty-badge.html +84 -0
  36. data/_includes/components/email-preferences.html +200 -0
  37. data/_includes/components/enhanced-code-block.html +212 -0
  38. data/_includes/components/enhanced-metadata.html +228 -0
  39. data/_includes/components/enhanced-toc.html +209 -0
  40. data/_includes/components/hero.html +15 -0
  41. data/_includes/components/language-switcher.html +396 -0
  42. data/_includes/components/math-fallback.html +19 -0
  43. data/_includes/components/navigation-enhancements.html +454 -0
  44. data/_includes/components/newsletter-signup.html +178 -0
  45. data/_includes/components/open-science-badges.html +16 -0
  46. data/_includes/components/package-install.html +194 -0
  47. data/_includes/components/performance-monitor.html +170 -0
  48. data/_includes/components/popular-posts.html +233 -0
  49. data/_includes/components/post-hero.html +62 -0
  50. data/_includes/components/reading-progress.html +133 -0
  51. data/_includes/components/reading-time.html +121 -0
  52. data/_includes/components/responsive-image.html +63 -0
  53. data/_includes/components/search-facets.html +307 -0
  54. data/_includes/components/series-navigation.html +124 -0
  55. data/_includes/components/social-proof.html +34 -0
  56. data/_includes/components/social-share.html +119 -0
  57. data/_includes/components/user-preferences.html +566 -0
  58. data/_includes/components/visualization-card.html +24 -0
  59. data/_includes/components/viz-table-fallback.html +19 -0
  60. data/_includes/critical-css/default.html +0 -0
  61. data/_includes/critical-css/home.html +0 -0
  62. data/_includes/critical-css/post.html +0 -0
  63. data/_includes/csp-meta.html +17 -0
  64. data/_includes/footer/nav-column.html +31 -0
  65. data/_includes/footer.html +76 -0
  66. data/_includes/head.html +337 -0
  67. data/_includes/header/navigation.html +48 -0
  68. data/_includes/header.html +70 -0
  69. data/_includes/helpers/array-to-sentence.html +21 -0
  70. data/_includes/helpers/date-format.html +16 -0
  71. data/_includes/helpers/link-with-icon.html +26 -0
  72. data/_includes/layouts/default/article.html +67 -0
  73. data/_includes/meta/language-attributes.html +25 -0
  74. data/_includes/meta/math-config.html +21 -0
  75. data/_includes/meta/schema.html +153 -0
  76. data/_includes/meta/scripts-loader.html +52 -0
  77. data/_includes/post/related-posts.html +63 -0
  78. data/_includes/scripts.html +72 -0
  79. data/_includes/skip-link.html +1 -0
  80. data/_includes/toc.html +2 -0
  81. data/_layouts/archive.html +282 -0
  82. data/_layouts/dataset.html +52 -0
  83. data/_layouts/default.html +33 -0
  84. data/_layouts/home.html +48 -0
  85. data/_layouts/notebook.html +173 -0
  86. data/_layouts/package.html +238 -0
  87. data/_layouts/page.html +16 -0
  88. data/_layouts/portfolio.html +41 -0
  89. data/_layouts/post-sidebar.html +183 -0
  90. data/_layouts/post.html +338 -0
  91. data/_layouts/project.html +252 -0
  92. data/_layouts/research.html +504 -0
  93. data/_plugins/analytics_dashboard.rb +315 -0
  94. data/_plugins/config_validator.rb +397 -0
  95. data/_plugins/csp_generator.rb +115 -0
  96. data/_plugins/datalog_bibliography.rb +19 -0
  97. data/_plugins/datalog_comments.rb +18 -0
  98. data/_plugins/datalog_slides.rb +20 -0
  99. data/_plugins/front_matter_compat.rb +109 -0
  100. data/_plugins/i18n.rb +160 -0
  101. data/_plugins/image_optimizer.rb +445 -0
  102. data/_plugins/math_preprocessor.rb +185 -0
  103. data/_plugins/notebook_converter.rb +1107 -0
  104. data/_plugins/plugin_loader.rb +185 -0
  105. data/_plugins/publications_generator.rb +234 -0
  106. data/_plugins/reading_time.rb +15 -0
  107. data/_plugins/search_normalizer.rb +74 -0
  108. data/_plugins/sri_filter.rb +18 -0
  109. data/_plugins/toc_filter.rb +68 -0
  110. data/_plugins/warning_filter.rb +3 -0
  111. data/_sass/_base.scss +142 -0
  112. data/_sass/_components.scss +2380 -0
  113. data/_sass/_header.scss +470 -0
  114. data/_sass/_interactive.scss +344 -0
  115. data/_sass/_layout.scss +1760 -0
  116. data/_sass/_mathematical.scss +368 -0
  117. data/_sass/_mixins.scss +99 -0
  118. data/_sass/_package-docs.scss +715 -0
  119. data/_sass/_phase1-enhancements.scss +721 -0
  120. data/_sass/_phase3-enhancements.scss +874 -0
  121. data/_sass/_phase4-enhancements.scss +1214 -0
  122. data/_sass/_phase5-enhancements.scss +414 -0
  123. data/_sass/_search.scss +654 -0
  124. data/_sass/_syntax-highlighting.scss +128 -0
  125. data/_sass/_theme.scss +79 -0
  126. data/_sass/_typography.scss +148 -0
  127. data/_sass/_utilities.scss +334 -0
  128. data/_sass/_variables.scss +153 -0
  129. data/_sass/_visualizations.scss +242 -0
  130. data/assets/css/main.scss +4 -0
  131. data/assets/img/20220607123041_detail.001.png +0 -0
  132. data/assets/img/favicons/android-chrome-192x192.png +0 -0
  133. data/assets/img/favicons/android-chrome-512x512.png +0 -0
  134. data/assets/img/favicons/apple-touch-icon.png +0 -0
  135. data/assets/img/favicons/favicon-16x16.png +0 -0
  136. data/assets/img/favicons/favicon-32x32.png +0 -0
  137. data/assets/img/favicons/favicon.ico +0 -0
  138. data/assets/img/favicons/site.webmanifest +21 -0
  139. data/assets/img/portfolio-placeholder.svg +20 -0
  140. data/assets/js/academic.js +262 -0
  141. data/assets/js/analytics-dashboard.js +382 -0
  142. data/assets/js/core/dark-mode.js +79 -0
  143. data/assets/js/core/github-cards.js +123 -0
  144. data/assets/js/core/language-filter.js +69 -0
  145. data/assets/js/core/navigation.js +184 -0
  146. data/assets/js/core/scroll-progress.js +45 -0
  147. data/assets/js/core/search-hotkeys.js +62 -0
  148. data/assets/js/core/skip-links.js +62 -0
  149. data/assets/js/loader.js +163 -0
  150. data/assets/js/main.js +23 -0
  151. data/assets/js/math.js +818 -0
  152. data/assets/js/notebook.js +158 -0
  153. data/assets/js/search/analytics.js +91 -0
  154. data/assets/js/search/app.js +271 -0
  155. data/assets/js/search/autocomplete.js +120 -0
  156. data/assets/js/search/engine.js +260 -0
  157. data/assets/js/search/filters.js +38 -0
  158. data/assets/js/search/render.js +217 -0
  159. data/assets/js/search/utils.js +99 -0
  160. data/assets/js/search.js +354 -0
  161. data/assets/js/visualizations.js +816 -0
  162. data/assets/publications/datalog-publications.bib +8 -0
  163. data/assets/publications/datalog-publications.ris +9 -0
  164. data/assets/publications/publications.bib +30 -0
  165. data/assets/templates/diogo-ribeiro-cv.md +31 -0
  166. data/assets/templates/diogo-ribeiro-cv.tex +32 -0
  167. data/bin/datalog +26 -0
  168. data/datalog-theme.gemspec +72 -0
  169. data/lib/datalog/cli.rb +563 -0
  170. data/lib/datalog/plugin_system/dependency_resolver.rb +223 -0
  171. data/lib/datalog/plugin_system.rb +237 -0
  172. data/lib/datalog/plugins/citations.rb +182 -0
  173. data/lib/datalog/plugins/comments.rb +183 -0
  174. data/lib/datalog/plugins/search.rb +45 -0
  175. data/lib/datalog/plugins/slides.rb +121 -0
  176. data/lib/datalog/theme/theme.rb +18 -0
  177. data/lib/datalog/theme/version.rb +7 -0
  178. data/lib/datalog/warning_filter.rb +28 -0
  179. data/lib/datalog-theme.rb +17 -0
  180. metadata +555 -0
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datalog
4
+ module Plugins
5
+ class Comments < Datalog::PluginSystem::Plugin
6
+ id "datalog-comments"
7
+ depends_on "datalog-search"
8
+ priority :low
9
+
10
+ class CommentsTag < Liquid::Tag
11
+ def render(context)
12
+ plugin = Datalog::PluginSystem.plugin("datalog-comments")
13
+ return "" unless plugin
14
+
15
+ plugin.render_comments(context)
16
+ end
17
+ end
18
+
19
+ REQUIRED_FIELDS = {
20
+ "giscus" => %w[repo repo_id category category_id],
21
+ "utterances" => %w[repo],
22
+ "disqus" => %w[shortname]
23
+ }.freeze
24
+
25
+ def initialize(site, config = {})
26
+ super
27
+ @defaults = {
28
+ "provider" => "giscus",
29
+ "mapping" => "pathname",
30
+ "theme" => "light",
31
+ "enabled_by_default" => false
32
+ }.merge(@config)
33
+ @memo = {}
34
+ end
35
+
36
+ def before_render(document, _payload = nil)
37
+ return unless comments_enabled?(document)
38
+
39
+ settings = resolve_settings(document)
40
+ embed = build_embed(document, settings)
41
+
42
+ document.data["datalog_comments"] = settings.merge("embed" => embed)
43
+ document.data["datalog_comments_state"] = storage_key(document)
44
+ @memo[storage_key(document)] = settings.merge("embed" => embed)
45
+ end
46
+
47
+ def custom_liquid_tags
48
+ { "datalog_comments" => CommentsTag }
49
+ end
50
+
51
+ def search_indexing(document)
52
+ key = storage_key(document)
53
+ settings = @memo[key]
54
+ return {} unless settings
55
+
56
+ {
57
+ "comments" => {
58
+ "provider" => settings["provider"],
59
+ "mapping" => settings["mapping"]
60
+ }
61
+ }
62
+ end
63
+
64
+ def render_comments(context)
65
+ page = context.registers[:page] || {}
66
+ key = page["datalog_comments_state"]
67
+ settings = @memo[key]
68
+ return "" unless settings
69
+
70
+ <<~HTML
71
+ <div class="datalog-comments" aria-label="Comments">
72
+ #{settings['embed']}
73
+ </div>
74
+ HTML
75
+ end
76
+
77
+ private
78
+
79
+ def comments_enabled?(document)
80
+ front_matter = document.data.fetch("comments", nil)
81
+ if front_matter.nil?
82
+ @defaults["enabled_by_default"]
83
+ else
84
+ front_matter != false
85
+ end
86
+ end
87
+
88
+ def resolve_settings(document)
89
+ front_matter = document.data.fetch("comments", {})
90
+ front_settings = front_matter.is_a?(Hash) ? front_matter.transform_keys(&:to_s) : {}
91
+ @defaults.merge(front_settings)
92
+ end
93
+
94
+ def build_embed(document, settings)
95
+ provider = settings["provider"]
96
+ missing = missing_required_fields(provider, settings)
97
+ unless missing.empty?
98
+ logger.warn(
99
+ "datalog comments",
100
+ %(Missing required settings for #{provider.inspect}: #{missing.join(', ')})
101
+ )
102
+ return %(<p class="datalog-comments-missing">Comments provider "#{provider}" missing required settings: #{missing.join(', ')}. Check your front matter or datalog configuration.</p>)
103
+ end
104
+
105
+ case provider
106
+ when "giscus"
107
+ render_giscus(settings)
108
+ when "utterances"
109
+ render_utterances(settings)
110
+ when "disqus"
111
+ render_disqus(document, settings)
112
+ else
113
+ %(<p class="datalog-comments-unsupported">Comments provider "#{settings['provider']}" is not supported.</p>)
114
+ end
115
+ end
116
+
117
+ def render_giscus(settings)
118
+ %(<script src="https://giscus.app/client.js"
119
+ data-repo="#{settings['repo']}"
120
+ data-repo-id="#{settings['repo_id']}"
121
+ data-category="#{settings['category']}"
122
+ data-category-id="#{settings['category_id']}"
123
+ data-mapping="#{settings['mapping']}"
124
+ data-strict="0"
125
+ data-reactions-enabled="1"
126
+ data-emit-metadata="0"
127
+ data-input-position="bottom"
128
+ data-theme="#{settings['theme']}"
129
+ data-lang="#{settings['lang'] || 'en'}"
130
+ crossorigin="anonymous"
131
+ async></script>)
132
+ end
133
+
134
+ def render_utterances(settings)
135
+ %(<script src="https://utteranc.es/client.js"
136
+ repo="#{settings['repo']}"
137
+ issue-term="#{settings['mapping']}"
138
+ theme="#{settings['theme']}"
139
+ crossorigin="anonymous"
140
+ async></script>)
141
+ end
142
+
143
+ def render_disqus(document, settings)
144
+ shortname = settings["shortname"] || ""
145
+ nonce = nonce_attribute(document)
146
+ <<~HTML
147
+ <div id="disqus_thread"></div>
148
+ <script#{nonce}>
149
+ var disqus_config = function () {
150
+ this.page.url = window.location.href;
151
+ this.page.identifier = '#{settings['mapping']}';
152
+ };
153
+ (function() {
154
+ var d = document, s = d.createElement('script');
155
+ s.src = 'https://#{shortname}.disqus.com/embed.js';
156
+ s.setAttribute('data-timestamp', +new Date());
157
+ (d.head || d.body).appendChild(s);
158
+ })();
159
+ </script>
160
+ <noscript>Please enable JavaScript to view the comments powered by Disqus.</noscript>
161
+ HTML
162
+ end
163
+
164
+ def nonce_attribute(document)
165
+ nonce = document.data.fetch("csp_nonce", nil)
166
+ nonce ? %( nonce="#{nonce}") : ""
167
+ end
168
+
169
+ def storage_key(document)
170
+ document.relative_path || document.path
171
+ end
172
+
173
+ def missing_required_fields(provider, settings)
174
+ required = REQUIRED_FIELDS.fetch(provider.to_s, [])
175
+ required.select { |key| settings[key].to_s.strip.empty? }
176
+ end
177
+
178
+ def logger
179
+ Jekyll.logger
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jekyll/utils"
4
+
5
+ module Datalog
6
+ module Plugins
7
+ class Search < Datalog::PluginSystem::Plugin
8
+ id "datalog-search"
9
+ priority :highest
10
+
11
+ def initialize(site, config = {})
12
+ super
13
+ @index_metadata = config.fetch("metadata", {})
14
+ end
15
+
16
+ def before_render(document, _payload = nil)
17
+ document.data["datalog_search"] = base_metadata(document)
18
+ end
19
+
20
+ def search_indexing(document)
21
+ base_metadata(document)
22
+ end
23
+
24
+ private
25
+
26
+ def base_metadata(document)
27
+ # Get collection label safely - only Jekyll::Document has the collection method
28
+ collection_label = if document.respond_to?(:collection) && document.collection
29
+ document.collection.label
30
+ else
31
+ document.data["collection"]
32
+ end
33
+
34
+ data = {
35
+ "id" => document.data["id"] || document.data["permalink"] || document.url,
36
+ "title" => document.data["title"],
37
+ "url" => document.url,
38
+ "collection" => collection_label
39
+ }
40
+
41
+ Jekyll::Utils.deep_merge_hashes(@index_metadata, data.compact)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datalog
4
+ module Plugins
5
+ class Slides < Datalog::PluginSystem::Plugin
6
+ id "datalog-slides"
7
+ depends_on "datalog-search"
8
+ priority :normal
9
+
10
+ class SlidesTag < Liquid::Tag
11
+ def render(context)
12
+ plugin = Datalog::PluginSystem.plugin("datalog-slides")
13
+ return "" unless plugin
14
+
15
+ plugin.render_slides(context)
16
+ end
17
+ end
18
+
19
+ def initialize(site, config = {})
20
+ super
21
+ @defaults = {
22
+ "cdn" => "https://unpkg.com/reveal.js@5",
23
+ "width" => "100%",
24
+ "height" => "600px"
25
+ }.merge(@config)
26
+ end
27
+
28
+ def before_render(document, _payload = nil)
29
+ deck = document.data["slides"] || document.data["deck"]
30
+ return unless deck
31
+
32
+ normalized = normalize_deck(deck)
33
+ return unless normalized
34
+
35
+ document.data["datalog_slides"] = normalized
36
+ document.data["datalog_slides_state"] = storage_key(document)
37
+ slide_state(storage_key(document))[:deck] = normalized
38
+ end
39
+
40
+ def custom_liquid_tags
41
+ { "datalog_slides" => SlidesTag }
42
+ end
43
+
44
+ def search_indexing(document)
45
+ key = storage_key(document)
46
+ state = @storage&.dig(key, :deck)
47
+ return {} unless state
48
+
49
+ {
50
+ "slides" => {
51
+ "src" => state["src"],
52
+ "title" => state["title"],
53
+ "theme" => state["theme"]
54
+ }
55
+ }
56
+ end
57
+
58
+ def render_slides(context)
59
+ key = (context.registers[:page] || {})["datalog_slides_state"]
60
+ deck = @storage&.dig(key, :deck)
61
+ return "" unless deck
62
+
63
+ render_slides_markup(deck)
64
+ end
65
+
66
+ private
67
+
68
+ def normalize_deck(deck)
69
+ attributes = case deck
70
+ when String
71
+ { "src" => deck }
72
+ when Hash
73
+ deck.transform_keys(&:to_s)
74
+ else
75
+ {}
76
+ end
77
+
78
+ return nil if attributes.empty?
79
+
80
+ merged = @defaults.merge(attributes)
81
+ merged["title"] ||= merged["name"] || merged["src"]
82
+ merged["notes"] ||= attributes["notes"]
83
+ merged
84
+ end
85
+
86
+ def render_slides_markup(deck)
87
+ src = deck["src"]
88
+ return placeholder_markup unless src
89
+
90
+ <<~HTML.strip
91
+ <div class="datalog-slides-embed" data-provider="reveal">
92
+ <iframe src="#{src}" title="#{deck['title'] || 'Slide deck'}" loading="lazy" allowfullscreen style="width: #{deck['width']}; height: #{deck['height']};"></iframe>
93
+ <p class="datalog-slides-actions">
94
+ <a href="#{src}" target="_blank" rel="noopener">Open slides</a>
95
+ #{cdn_hint(deck)}
96
+ </p>
97
+ </div>
98
+ HTML
99
+ end
100
+
101
+ def cdn_hint(deck)
102
+ return "" unless deck["cdn"]
103
+
104
+ %(<span class="datalog-slides-cdn">Powered by <code>#{deck['cdn']}</code></span>)
105
+ end
106
+
107
+ def placeholder_markup
108
+ "<div class=\"datalog-slides-embed datalog-slides-embed--missing\">Slides unavailable</div>"
109
+ end
110
+
111
+ def storage_key(document)
112
+ document.relative_path || document.path
113
+ end
114
+
115
+ def slide_state(key)
116
+ @storage ||= {}
117
+ @storage[key] ||= {}
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jekyll"
4
+ require_relative "theme/version"
5
+
6
+ module Datalog
7
+ module Theme
8
+ class << self
9
+ def register
10
+ Jekyll::Hooks.register :site, :after_init do |_site|
11
+ Jekyll.logger.info "Datalog Theme:", "Loaded version #{VERSION}"
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ Datalog::Theme.register
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datalog
4
+ module Theme
5
+ VERSION = "0.6.0" unless const_defined?(:VERSION)
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datalog
4
+ module WarningFilter
5
+ FILTERS = [
6
+ /literal string will be frozen/,
7
+ /already initialized constant Datalog::Theme::VERSION/
8
+ ].freeze
9
+
10
+ def warn(message)
11
+ return if FILTERS.any? { |pattern| pattern.match?(message) }
12
+
13
+ super
14
+ end
15
+ end
16
+ end
17
+
18
+ Warning.singleton_class.prepend(Datalog::WarningFilter) if defined?(Warning)
19
+
20
+ module Kernel
21
+ alias __datalog_warn warn
22
+
23
+ def warn(message = nil, *args)
24
+ return if message && Datalog::WarningFilter::FILTERS.any? { |pattern| pattern.match?(message) }
25
+
26
+ __datalog_warn(message, *args)
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "datalog/theme/version"
4
+
5
+ module Datalog
6
+ module Theme
7
+ class << self
8
+ def root
9
+ @root ||= File.expand_path("..", __dir__)
10
+ end
11
+
12
+ def assets_path
13
+ File.join(root, "assets")
14
+ end
15
+ end
16
+ end
17
+ end