datalog-theme 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +127 -0
- data/CITATION.cff +2 -2
- data/README.md +4 -2
- data/_data/i18n/en.yml +55 -3
- data/_data/i18n/es.yml +55 -3
- data/_data/i18n/pt.yml +55 -3
- data/_includes/analytics/dashboard.html +24 -9
- data/_includes/components/academic-dashboard.html +168 -133
- data/_includes/components/author-bio.html +7 -5
- data/_includes/components/author-list.html +6 -5
- data/_includes/components/citation-tools.html +2 -1
- data/_includes/components/content-provenance.html +19 -1
- data/_includes/components/correction-report.html +15 -8
- data/_includes/components/hero.html +22 -15
- data/_includes/components/package-index.html +121 -0
- data/_includes/components/package-install.html +27 -50
- data/_includes/components/post-list.html +14 -0
- data/_includes/components/responsive-image.html +15 -21
- data/_includes/footer.html +1 -1
- data/_includes/head.html +70 -11
- data/_includes/layouts/default/article.html +76 -46
- data/_includes/meta/dataset-json.html +171 -0
- data/_includes/meta/math-config.html +15 -1
- data/_includes/meta/package-json.html +69 -0
- data/_includes/meta/person-json.html +36 -13
- data/_includes/meta/publisher.html +15 -0
- data/_includes/meta/schema.html +108 -23
- data/_includes/meta/scholarly.html +10 -5
- data/_includes/post/related-posts.html +17 -43
- data/_includes/scripts.html +2 -2
- data/_includes/search/index-data.json +10 -1
- data/_includes/search/page.html +11 -6
- data/_layouts/archive.html +67 -0
- data/_layouts/dataset.html +2 -1
- data/_layouts/default.html +13 -11
- data/_layouts/docs.html +53 -0
- data/_layouts/home.html +30 -1
- data/_layouts/notebook.html +5 -1
- data/_layouts/package.html +38 -21
- data/_layouts/portfolio.html +1 -0
- data/_layouts/post.html +21 -62
- data/_layouts/project.html +2 -1
- data/_layouts/research.html +14 -2
- data/_plugins/analytics_dashboard.rb +4 -1
- data/_plugins/archive.rb +114 -0
- data/_plugins/authors.rb +50 -1
- data/_plugins/citation_exports.rb +12 -1
- data/_plugins/config_validator.rb +77 -2
- data/_plugins/correction_fallback.rb +110 -0
- data/_plugins/image_optimizer.rb +129 -22
- data/_plugins/math_preprocessor.rb +7 -48
- data/_plugins/notebook_converter.rb +44 -5
- data/_plugins/packages.rb +84 -0
- data/_plugins/page_dates.rb +37 -0
- data/_plugins/references.rb +16 -2
- data/_plugins/related_posts.rb +149 -0
- data/_plugins/search_sections.rb +110 -0
- data/_plugins/site_identity.rb +42 -0
- data/_plugins/social_cards.rb +17 -0
- data/_sass/_academic-dashboard.scss +18 -30
- data/_sass/_base.scss +2 -0
- data/_sass/_components.scss +21 -23
- data/_sass/_docs.scss +143 -0
- data/_sass/_features.scss +6 -0
- data/_sass/_layout.scss +65 -6
- data/_sass/_mathematical.scss +61 -19
- data/_sass/_notebooks.scss +1 -32
- data/_sass/_package-docs.scss +6 -9
- data/_sass/_post-components.scss +23 -23
- data/_sass/_print.scss +1 -1
- data/_sass/_search-page.scss +75 -0
- data/_sass/_search.scss +17 -26
- data/_sass/_syntax-highlighting.scss +23 -1
- data/_sass/_theme.scss +1 -0
- data/_sass/_utilities.scss +87 -40
- data/_sass/_variables.scss +31 -29
- data/assets/css/main.scss +2 -1
- data/assets/js/dist/academic.js +1 -1
- data/assets/js/dist/analytics-dashboard.js +1 -1
- data/assets/js/dist/chunks/chunk-TNVD6UAM.js +1 -0
- data/assets/js/dist/comments.js +1 -1
- data/assets/js/dist/contact.js +1 -1
- data/assets/js/dist/core.js +1 -1
- data/assets/js/dist/corrections.js +5 -1
- data/assets/js/dist/loader.js +1 -1
- data/assets/js/dist/math.js +2 -1
- data/assets/js/dist/moderation.js +1 -1
- data/assets/js/dist/reactions.js +1 -1
- data/assets/js/dist/search.js +1 -1
- data/assets/js/dist/sources.json +20 -16
- data/assets/js/dist/subscriptions.js +1 -1
- data/assets/js/loader.js +6 -2
- data/lib/datalog/audit/checks.rb +269 -0
- data/lib/datalog/audit/known_keys.rb +61 -0
- data/lib/datalog/audit/site_reader.rb +118 -0
- data/lib/datalog/audit/source_file.rb +85 -0
- data/lib/datalog/audit.rb +145 -0
- data/lib/datalog/citations/bibtex.rb +200 -0
- data/lib/datalog/citations/entry.rb +333 -0
- data/lib/datalog/citations/markup.rb +79 -0
- data/lib/datalog/cli.rb +164 -89
- data/lib/datalog/critical_css.rb +126 -21
- data/lib/datalog/latex_speech/words.json +88 -0
- data/lib/datalog/latex_speech.rb +355 -0
- data/lib/datalog/packages/command.rb +51 -0
- data/lib/datalog/packages/refresh.rb +187 -0
- data/lib/datalog/packages.rb +177 -0
- data/lib/datalog/plugin_system.rb +5 -0
- data/lib/datalog/plugins/citations.rb +325 -109
- data/lib/datalog/site_config.rb +26 -0
- data/lib/datalog/social_cards/font.rb +286 -0
- data/lib/datalog/social_cards/fonts/IBMPlexSans-Regular.ttf +0 -0
- data/lib/datalog/social_cards/fonts/IBMPlexSerif-SemiBold.ttf +0 -0
- data/lib/datalog/social_cards/fonts/OFL.txt +92 -0
- data/lib/datalog/social_cards/geometry.rb +51 -0
- data/lib/datalog/social_cards/outline.rb +54 -0
- data/lib/datalog/social_cards/plain_text.rb +258 -0
- data/lib/datalog/social_cards/template.rb +322 -0
- data/lib/datalog/social_cards/template.svg +17 -0
- data/lib/datalog/social_cards/typesetter.rb +178 -0
- data/lib/datalog/social_cards.rb +369 -0
- data/lib/datalog/theme/updater.rb +307 -0
- data/lib/datalog/theme/version.rb +1 -1
- metadata +44 -7
- data/_includes/components/enhanced-code-block.html +0 -212
- data/_includes/components/performance-monitor.html +0 -170
- data/_includes/components/viz-table-fallback.html +0 -19
- data/_plugins/datalog_bibliography.rb +0 -21
- data/assets/js/dist/chunks/chunk-WIRUK3OZ.js +0 -1
|
@@ -1,182 +1,398 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "cgi"
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "../citations/entry"
|
|
6
|
+
require_relative "../citations/markup"
|
|
4
7
|
|
|
5
8
|
module Datalog
|
|
6
9
|
module Plugins
|
|
10
|
+
# In-text citations and the bibliography they build, on a par with the
|
|
11
|
+
# figure, table and statement references of _plugins/references.rb:
|
|
12
|
+
#
|
|
13
|
+
# bibliography: _bibliography/missing-data.bib # or .json (CSL-JSON), or a list
|
|
14
|
+
#
|
|
15
|
+
# Multiple imputation {% cite rubin1987 %} and its critics {% cite allison2001 vanbuuren2018 p="12" %}.
|
|
16
|
+
#
|
|
17
|
+
# The tags write placeholders. Once the page's Markdown is converted, every
|
|
18
|
+
# citation gets its number in the order it appears (numeric) or its author
|
|
19
|
+
# and year (author-year), each linked to its entry, and the bibliography
|
|
20
|
+
# lists the cited works with links back to where each was cited. A key the
|
|
21
|
+
# page's sources do not have, or a key two entries share, stops the build.
|
|
22
|
+
#
|
|
23
|
+
# Entries come from the page's `bibliography` files (else the site's
|
|
24
|
+
# `citations.bibliography`), its `citations:` list, a `references:` list of
|
|
25
|
+
# maps, and `citation_group` in _data/citations.yml. `nocite: [key]` or
|
|
26
|
+
# `nocite: all` lists works the text does not cite.
|
|
7
27
|
class Citations < Datalog::PluginSystem::Plugin
|
|
8
28
|
id "datalog-citations"
|
|
9
|
-
depends_on "datalog-search"
|
|
10
29
|
priority :high
|
|
11
30
|
|
|
31
|
+
Markup = Datalog::Citations::Markup
|
|
32
|
+
STYLES = %w[numeric author-year].freeze
|
|
33
|
+
LOCATORS = %w[p pp chap sec loc].freeze
|
|
34
|
+
KEY = /\A[^\s"'<>&{}%]+\z/
|
|
35
|
+
MARKER = %r{<span class="datalog-cite" data-cite="([^"]*)"(?: data-cite-locator="([^"]*)")?></span>}
|
|
36
|
+
SOURCE_TAG = /\{%-?\s*(?:cite|datalog_cite)\s+([^%]*?)-?%\}/
|
|
37
|
+
|
|
38
|
+
# {% cite key %}, {% cite key1 key2 %}, {% cite key p="12" %}.
|
|
12
39
|
class CiteTag < Liquid::Tag
|
|
13
|
-
def initialize(tag_name, markup,
|
|
40
|
+
def initialize(tag_name, markup, options)
|
|
14
41
|
super
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
42
|
+
@keys, @locator = Citations.parse_markup(markup, tag_name)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def render(context)
|
|
46
|
+
unless Datalog::PluginSystem.plugin("datalog-citations")
|
|
47
|
+
page = context.registers[:page] || {}
|
|
48
|
+
raise Liquid::ArgumentError,
|
|
49
|
+
"#{page['path']} uses {% #{tag_name} %}, which needs the datalog-citations plugin: " \
|
|
50
|
+
"add it to datalog_plugins.enabled in _config.yml"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
locator = @locator ? %( data-cite-locator="#{CGI.escapeHTML(@locator)}") : ""
|
|
54
|
+
%(<span class="datalog-cite" data-cite="#{CGI.escapeHTML(@keys.join(' '))}"#{locator}></span>)
|
|
18
55
|
end
|
|
56
|
+
end
|
|
19
57
|
|
|
58
|
+
# The page's bibliography, where a layout puts it.
|
|
59
|
+
class BibliographyTag < Liquid::Tag
|
|
20
60
|
def render(context)
|
|
61
|
+
page = context.registers[:page] || {}
|
|
21
62
|
plugin = Datalog::PluginSystem.plugin("datalog-citations")
|
|
22
|
-
return
|
|
63
|
+
return plugin.bibliography_html(page) if plugin
|
|
23
64
|
|
|
24
|
-
|
|
65
|
+
if page["datalog_bibliography"]
|
|
66
|
+
Jekyll.logger.warn("datalog-citations", "#{page['path']} sets datalog_bibliography, but " \
|
|
67
|
+
"datalog_plugins.enabled does not list datalog-citations")
|
|
68
|
+
end
|
|
69
|
+
""
|
|
25
70
|
end
|
|
71
|
+
end
|
|
26
72
|
|
|
27
|
-
|
|
73
|
+
# [keys, "p|12"] from `key1 key2 p="12"`.
|
|
74
|
+
def self.parse_markup(markup, tag_name = "cite")
|
|
75
|
+
keys = []
|
|
76
|
+
locator = nil
|
|
77
|
+
tokens(markup).each do |token|
|
|
78
|
+
name, value = token.split("=", 2)
|
|
79
|
+
if value.nil?
|
|
80
|
+
keys << check_key(token, tag_name)
|
|
81
|
+
else
|
|
82
|
+
unless LOCATORS.include?(name)
|
|
83
|
+
raise Liquid::ArgumentError, "{% #{tag_name} %} takes a locator as p=, pp=, chap=, sec= or loc=; " \
|
|
84
|
+
"got #{name}="
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
locator = "#{name}|#{unquote(value)}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
raise Liquid::ArgumentError, "{% #{tag_name} %} needs at least one citation key" if keys.empty?
|
|
28
91
|
|
|
29
|
-
|
|
30
|
-
|
|
92
|
+
[keys.uniq, locator]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# The markup split at spaces outside quotes, character by character: a
|
|
96
|
+
# regular expression over it could take quadratic time on a long word.
|
|
97
|
+
def self.tokens(markup)
|
|
98
|
+
tokens = []
|
|
99
|
+
current = +""
|
|
100
|
+
quote = nil
|
|
101
|
+
markup.to_s.each_char do |char|
|
|
102
|
+
if quote
|
|
103
|
+
quote = nil if char == quote
|
|
104
|
+
elsif ['"', "'"].include?(char)
|
|
105
|
+
quote = char
|
|
106
|
+
elsif char.strip.empty?
|
|
107
|
+
tokens << current unless current.empty?
|
|
108
|
+
current = +""
|
|
109
|
+
next
|
|
110
|
+
end
|
|
111
|
+
current << char
|
|
31
112
|
end
|
|
113
|
+
tokens << current unless current.empty?
|
|
114
|
+
tokens
|
|
32
115
|
end
|
|
33
116
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
117
|
+
def self.unquote(value)
|
|
118
|
+
quoted = value.length >= 2 && ['"', "'"].include?(value[0]) && value[-1] == value[0]
|
|
119
|
+
quoted ? value[1..-2] : value
|
|
120
|
+
end
|
|
38
121
|
|
|
39
|
-
|
|
40
|
-
|
|
122
|
+
def self.check_key(key, tag_name)
|
|
123
|
+
return key if key.match?(KEY)
|
|
124
|
+
|
|
125
|
+
raise Liquid::ArgumentError, "{% #{tag_name} %} takes citation keys without spaces, quotes, <, >, " \
|
|
126
|
+
"&, braces or %; got #{key.inspect}"
|
|
41
127
|
end
|
|
42
128
|
|
|
43
129
|
def initialize(site, config = {})
|
|
44
130
|
super
|
|
45
131
|
@state = {}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def before_render(document, _payload = nil)
|
|
49
|
-
citations = collect_citations(document)
|
|
50
|
-
state = ensure_state(document)
|
|
51
|
-
state[:lookup] = citations
|
|
52
|
-
state[:order] = []
|
|
53
|
-
document.data["datalog_citations"] = citations
|
|
54
|
-
document.data["datalog_bibliography"] = citations.values
|
|
55
|
-
document.data["datalog_citation_state"] = state[:key]
|
|
56
|
-
return unless config["bibliography_title"]
|
|
57
|
-
|
|
58
|
-
document.data["datalog_bibliography_title"] = config["bibliography_title"]
|
|
132
|
+
@files = {}
|
|
59
133
|
end
|
|
60
134
|
|
|
61
135
|
def custom_liquid_tags
|
|
62
136
|
{
|
|
137
|
+
"cite" => CiteTag,
|
|
63
138
|
"datalog_cite" => CiteTag,
|
|
64
139
|
"datalog_bibliography" => BibliographyTag
|
|
65
140
|
}
|
|
66
141
|
end
|
|
67
142
|
|
|
68
|
-
|
|
69
|
-
state = resolve_state(document)
|
|
70
|
-
return {} unless state && state[:lookup]&.any?
|
|
143
|
+
# ------------------------------------------------------------- sources
|
|
71
144
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
145
|
+
def before_render(document, payload = nil)
|
|
146
|
+
state = load_state(document)
|
|
147
|
+
# A page's `page` in Liquid is a copy of its data taken before it
|
|
148
|
+
# renders (a document's reads its data live), so what numbering finds
|
|
149
|
+
# has to reach that copy too.
|
|
150
|
+
state[:page] = payload["page"] if payload.respond_to?(:[]) && payload["page"].is_a?(Hash)
|
|
151
|
+
publish(document, state, "datalog_citation_state" => state[:id])
|
|
152
|
+
title = config["bibliography_title"]
|
|
153
|
+
return unless title && !document.data["datalog_bibliography_title"]
|
|
154
|
+
|
|
155
|
+
publish(document, state, "datalog_bibliography_title" => title)
|
|
78
156
|
end
|
|
79
157
|
|
|
80
|
-
def
|
|
81
|
-
|
|
158
|
+
def publish(document, state, values)
|
|
159
|
+
document.data.merge!(values)
|
|
160
|
+
state[:page]&.merge!(values)
|
|
161
|
+
end
|
|
82
162
|
|
|
83
|
-
|
|
84
|
-
|
|
163
|
+
def load_state(document)
|
|
164
|
+
id = document.relative_path || document.path
|
|
165
|
+
return @state[id] if @state.key?(id)
|
|
85
166
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
167
|
+
entries = {}
|
|
168
|
+
sources(document).each do |label, items|
|
|
169
|
+
items.each do |entry|
|
|
170
|
+
next if entry.key.empty?
|
|
89
171
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
172
|
+
if entries.key?(entry.key)
|
|
173
|
+
raise Jekyll::Errors::FatalException,
|
|
174
|
+
"#{document.relative_path} has two bibliography entries with the key \"#{entry.key}\" " \
|
|
175
|
+
"(in #{entries[entry.key][:source]} and #{label}); each key has to be unique"
|
|
176
|
+
end
|
|
94
177
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
else
|
|
98
|
-
%(<span class="datalog-citation" data-citation="#{normalized_key}">#{text}</span>)
|
|
178
|
+
entries[entry.key] = { entry: entry, source: label }
|
|
179
|
+
end
|
|
99
180
|
end
|
|
181
|
+
@state[id] = { id: id, entries: entries.transform_values { |value| value[:entry] },
|
|
182
|
+
sources: sources_label(document) }
|
|
100
183
|
end
|
|
101
184
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
185
|
+
# [label, [Entry, ...]] for each place the page's entries come from.
|
|
186
|
+
def sources(document)
|
|
187
|
+
data = document.data
|
|
188
|
+
found = bibliography_files(document).map { |path| [path, read_file(document, path)] }
|
|
189
|
+
found << ["citations:", front_matter_entries(data["citations"])]
|
|
190
|
+
# The research layout's `references:` is a hand-written list of strings;
|
|
191
|
+
# only a list of maps is a list of entries.
|
|
192
|
+
found << ["references:", front_matter_entries(Array(data["references"]).grep(Hash))]
|
|
193
|
+
group = data["citation_group"]
|
|
194
|
+
if group
|
|
195
|
+
found << ["_data/citations.yml (#{group})", front_matter_entries(site.data.dig("citations", group.to_s))]
|
|
196
|
+
end
|
|
197
|
+
found
|
|
198
|
+
end
|
|
105
199
|
|
|
106
|
-
|
|
107
|
-
|
|
200
|
+
def front_matter_entries(items)
|
|
201
|
+
Array(items).map { |item| Datalog::Citations::Entry.from_front_matter(item) }
|
|
202
|
+
end
|
|
108
203
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
204
|
+
def sources_label(document)
|
|
205
|
+
labels = bibliography_files(document)
|
|
206
|
+
labels << "citations:" if document.data["citations"]
|
|
207
|
+
labels.empty? ? "it has none" : labels.join(", ")
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def bibliography_files(document)
|
|
211
|
+
own = document.data["bibliography"]
|
|
212
|
+
value = own.nil? ? site_setting("bibliography") : own
|
|
213
|
+
return [] if value == false
|
|
113
214
|
|
|
114
|
-
|
|
215
|
+
Array(value).map(&:to_s).reject(&:empty?)
|
|
115
216
|
end
|
|
116
217
|
|
|
117
|
-
|
|
218
|
+
def read_file(document, path)
|
|
219
|
+
file = resolve(document, path)
|
|
220
|
+
stamp = [File.mtime(file), File.size(file)]
|
|
221
|
+
@files.delete(file) unless @files.dig(file, :stamp) == stamp
|
|
222
|
+
@files[file] ||= { stamp: stamp, entries: parse_file(document, file, path) }
|
|
223
|
+
@files[file][:entries]
|
|
224
|
+
end
|
|
118
225
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
226
|
+
# A path in the site: from its source directory, or from the page's own.
|
|
227
|
+
def resolve(document, path)
|
|
228
|
+
source = File.expand_path(site.source)
|
|
229
|
+
base = File.dirname(File.expand_path(document.path.to_s, source))
|
|
230
|
+
candidates = [File.expand_path(path, source), File.expand_path(path, base)]
|
|
231
|
+
file = candidates.find { |candidate| candidate.start_with?("#{source}/") && File.file?(candidate) }
|
|
232
|
+
return file if file
|
|
123
233
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
234
|
+
raise Jekyll::Errors::FatalException,
|
|
235
|
+
"#{document.relative_path} names the bibliography #{path}, which is not a file in the site"
|
|
236
|
+
end
|
|
127
237
|
|
|
128
|
-
|
|
238
|
+
def parse_file(document, file, path)
|
|
239
|
+
text = File.read(file, encoding: "bom|utf-8")
|
|
240
|
+
case File.extname(file).downcase
|
|
241
|
+
when ".bib", ".bibtex"
|
|
242
|
+
Datalog::Citations::BibTeX.parse(text).map { |record| Datalog::Citations::Entry.from_bibtex(record) }
|
|
243
|
+
when ".json"
|
|
244
|
+
Array(JSON.parse(text)).map { |item| Datalog::Citations::Entry.from_csl(item) }
|
|
245
|
+
else
|
|
246
|
+
raise Jekyll::Errors::FatalException, "#{document.relative_path} names the bibliography #{path}, " \
|
|
247
|
+
"which is neither BibTeX (.bib) nor CSL-JSON (.json)"
|
|
129
248
|
end
|
|
249
|
+
rescue Datalog::Citations::BibTeX::ParseError, JSON::ParserError => e
|
|
250
|
+
raise Jekyll::Errors::FatalException, "#{path}, the bibliography of #{document.relative_path}: #{e.message}"
|
|
130
251
|
end
|
|
131
252
|
|
|
132
|
-
def
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
253
|
+
def site_setting(name)
|
|
254
|
+
settings = site.config["citations"]
|
|
255
|
+
settings.is_a?(Hash) ? settings[name] : nil
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# ----------------------------------------------------------- numbering
|
|
259
|
+
|
|
260
|
+
# After conversion: resolve every placeholder, in the order the page shows them.
|
|
261
|
+
def number(document)
|
|
262
|
+
content = document.content.to_s
|
|
263
|
+
nocite = document.data["nocite"]
|
|
264
|
+
return unless content.include?('class="datalog-cite"') || nocite
|
|
265
|
+
|
|
266
|
+
state = load_state(document)
|
|
267
|
+
style = style(document)
|
|
268
|
+
words = words(document)
|
|
269
|
+
entries = listed_entries(document, state, content, nocite, style)
|
|
270
|
+
labels = Markup.labels(entries, style, words)
|
|
271
|
+
anchors = Markup.anchors(entries)
|
|
272
|
+
backlinks = Hash.new { |hash, key| hash[key] = [] }
|
|
273
|
+
document.content = content.gsub(MARKER) do
|
|
274
|
+
links = Regexp.last_match(1).split.map do |key|
|
|
275
|
+
ref = "cite-ref-#{anchors[key]}-#{backlinks[key].size + 1}"
|
|
276
|
+
backlinks[key] << ref
|
|
277
|
+
%(<a href="#cite-#{anchors[key]}" id="#{ref}">#{CGI.escapeHTML(labels[key])}</a>)
|
|
278
|
+
end
|
|
279
|
+
Markup.marker(links, Markup.locator_text(Regexp.last_match(2), words), style)
|
|
144
280
|
end
|
|
281
|
+
|
|
282
|
+
state[:html] = Markup.list_html(entries, labels, anchors, backlinks, style, words)
|
|
283
|
+
publish(document, state,
|
|
284
|
+
"datalog_bibliography" => entries.map(&:key),
|
|
285
|
+
"datalog_cited_works" => entries.map { |entry| { "reference" => entry.highwire, "json_ld" => entry.json_ld } })
|
|
145
286
|
end
|
|
146
287
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
segments << container if container
|
|
157
|
-
segments << entry[:url] if entry[:url] && !entry[:url].empty?
|
|
158
|
-
segments.compact.join(". ")
|
|
288
|
+
# The cited entries in citation order, then the nocite ones; sorted by
|
|
289
|
+
# author and year in the author-year style.
|
|
290
|
+
def listed_entries(document, state, content, nocite, style)
|
|
291
|
+
cited = content.scan(MARKER).flat_map { |keys, _| keys.split }.uniq
|
|
292
|
+
check_keys(document, state, cited)
|
|
293
|
+
extra = [nocite].flatten.map(&:to_s) == ["all"] ? state[:entries].keys : Array(nocite).map(&:to_s)
|
|
294
|
+
check_keys(document, state, extra, "nocite")
|
|
295
|
+
entries = (cited + (extra - cited)).map { |key| state[:entries][key] }
|
|
296
|
+
style == "author-year" ? entries.sort_by(&:sort_key) : entries
|
|
159
297
|
end
|
|
160
298
|
|
|
161
|
-
def
|
|
162
|
-
|
|
163
|
-
|
|
299
|
+
def check_keys(document, state, keys, tag = "cite")
|
|
300
|
+
missing = keys - state[:entries].keys
|
|
301
|
+
return if missing.empty?
|
|
302
|
+
|
|
303
|
+
verb = tag == "nocite" ? "lists in nocite" : "cites"
|
|
304
|
+
raise Jekyll::Errors::FatalException,
|
|
305
|
+
"#{document.relative_path} #{verb} #{missing.map { |key| "\"#{key}\"" }.join(', ')}, which " \
|
|
306
|
+
"#{missing.size == 1 ? 'is' : 'are'} not in its bibliography (#{state[:sources]})"
|
|
164
307
|
end
|
|
165
308
|
|
|
166
|
-
def
|
|
167
|
-
|
|
168
|
-
|
|
309
|
+
def bibliography_html(page)
|
|
310
|
+
state = @state[page["datalog_citation_state"]]
|
|
311
|
+
state && state[:html] ? state[:html] : ""
|
|
169
312
|
end
|
|
170
313
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
314
|
+
# --------------------------------------------------------------- excerpts
|
|
315
|
+
|
|
316
|
+
# Jekyll renders an excerpt apart from its post, and on a listing page
|
|
317
|
+
# before the post itself may be converted. Its citations read as they do
|
|
318
|
+
# in the post, linked to the post's bibliography.
|
|
319
|
+
def number_excerpt(excerpt, html)
|
|
320
|
+
return html unless html&.include?('class="datalog-cite"')
|
|
321
|
+
|
|
322
|
+
post = excerpt.doc
|
|
323
|
+
state = load_state(post)
|
|
324
|
+
style = style(post)
|
|
325
|
+
words = words(post)
|
|
326
|
+
cited = post.content.to_s.scan(MARKER).flat_map { |keys, _| keys.split }
|
|
327
|
+
cited = source_keys(post) if cited.empty?
|
|
328
|
+
entries = cited.uniq.filter_map { |key| state[:entries][key] }
|
|
329
|
+
entries = entries.sort_by(&:sort_key) if style == "author-year"
|
|
330
|
+
labels = Markup.labels(entries, style, words)
|
|
331
|
+
anchors = Markup.anchors(entries)
|
|
332
|
+
url = "#{post.site.baseurl}#{post.url}"
|
|
333
|
+
html.gsub(MARKER) do
|
|
334
|
+
links = Regexp.last_match(1).split.map do |key|
|
|
335
|
+
next CGI.escapeHTML(key) unless labels[key]
|
|
336
|
+
|
|
337
|
+
%(<a href="#{url}#cite-#{anchors[key]}">#{CGI.escapeHTML(labels[key])}</a>)
|
|
338
|
+
end
|
|
339
|
+
Markup.marker(links, Markup.locator_text(Regexp.last_match(2), words), style)
|
|
340
|
+
end
|
|
175
341
|
end
|
|
176
342
|
|
|
177
|
-
def
|
|
178
|
-
|
|
343
|
+
def source_keys(post)
|
|
344
|
+
source = File.file?(post.path.to_s) ? File.read(post.path, encoding: "bom|utf-8") : ""
|
|
345
|
+
source.scan(SOURCE_TAG).flat_map { |(markup)| Citations.parse_markup(markup).first }
|
|
346
|
+
rescue Liquid::ArgumentError
|
|
347
|
+
[]
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# ----------------------------------------------------------- settings
|
|
351
|
+
|
|
352
|
+
def style(document)
|
|
353
|
+
value = (document.data["citation_style"] || site_setting("style") || "numeric").to_s
|
|
354
|
+
STYLES.include?(value) ? value : "numeric"
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def words(document)
|
|
358
|
+
locale = Datalog::I18n.locale_code(site, document.data["lang"])
|
|
359
|
+
lookup = ->(key, fallback) { Datalog::I18n.lookup(site, locale, "citations.#{key}") || fallback }
|
|
360
|
+
{
|
|
361
|
+
and: lookup.call("and", "and"), et_al: lookup.call("et_al", "et al."),
|
|
362
|
+
no_date: lookup.call("no_date", "n.d."), back: lookup.call("back", "Back to citation {{n}}"),
|
|
363
|
+
locators: LOCATORS.to_h { |kind| [kind, lookup.call("locators.#{kind}", "#{kind}.")] }
|
|
364
|
+
}
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# --------------------------------------------------------------- search
|
|
368
|
+
|
|
369
|
+
# The keys a page cites, for the search index, when the site has search.
|
|
370
|
+
def search_indexing(document)
|
|
371
|
+
return {} unless Datalog::PluginSystem.plugin("datalog-search")
|
|
372
|
+
|
|
373
|
+
keys = Array(document.data["datalog_bibliography"])
|
|
374
|
+
return {} if keys.empty?
|
|
375
|
+
|
|
376
|
+
{ "citations" => { "count" => keys.size, "keys" => keys.sort } }
|
|
179
377
|
end
|
|
180
378
|
end
|
|
181
379
|
end
|
|
182
380
|
end
|
|
381
|
+
|
|
382
|
+
# The tags exist whether or not the plugin is on, so a page that cites on a
|
|
383
|
+
# site without it stops with a message saying what to enable, not with
|
|
384
|
+
# "Unknown tag 'cite'". The plugin registers them again when it loads.
|
|
385
|
+
Liquid::Template.register_tag("cite", Datalog::Plugins::Citations::CiteTag)
|
|
386
|
+
Liquid::Template.register_tag("datalog_cite", Datalog::Plugins::Citations::CiteTag)
|
|
387
|
+
Liquid::Template.register_tag("datalog_bibliography", Datalog::Plugins::Citations::BibliographyTag)
|
|
388
|
+
|
|
389
|
+
Jekyll::Hooks.register %i[pages documents], :post_convert do |document|
|
|
390
|
+
Datalog::PluginSystem.plugin("datalog-citations")&.number(document)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
Jekyll::Excerpt.prepend(Module.new do
|
|
394
|
+
def output
|
|
395
|
+
plugin = Datalog::PluginSystem.plugin("datalog-citations")
|
|
396
|
+
plugin ? plugin.number_excerpt(self, super) : super
|
|
397
|
+
end
|
|
398
|
+
end)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Datalog
|
|
7
|
+
# A site's _config.yml as the CLI's commands read it, which has to be as
|
|
8
|
+
# Jekyll reads it: a date, a time or a YAML alias that builds fine must not
|
|
9
|
+
# stop `datalog update`, `check` or `critical-css`. Each command used to call
|
|
10
|
+
# the safe loader itself, and the updater's permitted no dates, so a site
|
|
11
|
+
# with `launched: 2024-01-01` in its configuration failed the update after
|
|
12
|
+
# the theme checkout had already moved.
|
|
13
|
+
module SiteConfig
|
|
14
|
+
PERMITTED_CLASSES = [Date, Time].freeze
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# The settings in a configuration file, or {} when there is none.
|
|
19
|
+
# A file that is not valid YAML still raises Psych::SyntaxError.
|
|
20
|
+
def load(path)
|
|
21
|
+
return {} unless File.file?(path)
|
|
22
|
+
|
|
23
|
+
YAML.safe_load_file(path, permitted_classes: PERMITTED_CLASSES, aliases: true) || {}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|