datalog-theme 0.7.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 (203) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +188 -0
  3. data/CITATION.cff +2 -2
  4. data/README.md +25 -17
  5. data/_data/cdn-integrity.yml +0 -30
  6. data/_data/i18n/en.yml +297 -0
  7. data/_data/i18n/es.yml +297 -0
  8. data/_data/i18n/pt.yml +297 -0
  9. data/_data/js_manifest.json +16 -0
  10. data/_includes/analytics/dashboard.html +3 -1
  11. data/_includes/components/api-function.html +20 -1
  12. data/_includes/components/author-bio.html +21 -11
  13. data/_includes/components/author-list.html +32 -0
  14. data/_includes/components/citation-tools.html +33 -25
  15. data/_includes/components/comments-thread.html +90 -0
  16. data/_includes/components/contact-form.html +120 -0
  17. data/_includes/components/correction-report.html +82 -0
  18. data/_includes/components/enhanced-code-block.html +1 -1
  19. data/_includes/components/enhanced-toc.html +6 -7
  20. data/_includes/components/license-link.html +13 -0
  21. data/_includes/components/license-notice.html +28 -0
  22. data/_includes/components/moderation-inbox.html +139 -0
  23. data/_includes/components/reactions.html +49 -0
  24. data/_includes/components/reading-list.html +31 -0
  25. data/_includes/components/reading-mode-toggle.html +65 -0
  26. data/_includes/components/reading-state-bookmark.html +27 -0
  27. data/_includes/components/reading-state-panel.html +71 -0
  28. data/_includes/components/reproducibility.html +61 -0
  29. data/_includes/components/responsive-image.html +3 -3
  30. data/_includes/components/revision-history.html +39 -0
  31. data/_includes/components/revision-notice.html +35 -0
  32. data/_includes/components/series-nav.html +64 -0
  33. data/_includes/components/subscribe-form.html +88 -0
  34. data/_includes/components/subscription-manage.html +68 -0
  35. data/_includes/components/webmentions.html +48 -0
  36. data/_includes/csp-meta.html +135 -11
  37. data/_includes/footer.html +22 -20
  38. data/_includes/head.html +111 -63
  39. data/_includes/header/navigation.html +12 -15
  40. data/_includes/header.html +28 -19
  41. data/_includes/layouts/default/article.html +9 -7
  42. data/_includes/meta/dynamic-services-config.html +14 -0
  43. data/_includes/meta/math-config.html +18 -10
  44. data/_includes/meta/person-json.html +27 -0
  45. data/_includes/meta/publisher.html +45 -0
  46. data/_includes/meta/schema.html +67 -30
  47. data/_includes/meta/scholarly.html +121 -0
  48. data/_includes/meta/scripts-loader.html +18 -32
  49. data/_includes/meta/webmention-discovery.html +14 -0
  50. data/_includes/post/related-posts.html +4 -7
  51. data/_includes/scripts.html +57 -0
  52. data/_includes/search/index-data.json +9 -34
  53. data/_layouts/dataset.html +5 -3
  54. data/_layouts/default.html +16 -8
  55. data/_layouts/notebook.html +1 -0
  56. data/_layouts/package.html +4 -2
  57. data/_layouts/page.html +15 -0
  58. data/_layouts/portfolio.html +1 -0
  59. data/_layouts/post.html +102 -23
  60. data/_layouts/project.html +4 -3
  61. data/_layouts/research.html +20 -8
  62. data/_plugins/analytics_dashboard.rb +9 -3
  63. data/_plugins/authors.rb +133 -0
  64. data/_plugins/config_validator.rb +231 -23
  65. data/_plugins/critical_css_check.rb +42 -0
  66. data/_plugins/csp_generator.rb +18 -28
  67. data/_plugins/datalog_bibliography.rb +9 -7
  68. data/_plugins/datalog_comments.rb +8 -5
  69. data/_plugins/datalog_slides.rb +9 -8
  70. data/_plugins/i18n.rb +13 -12
  71. data/_plugins/image_optimizer.rb +241 -178
  72. data/_plugins/licenses.rb +135 -0
  73. data/_plugins/math_preprocessor.rb +59 -7
  74. data/_plugins/notebook_converter.rb +23 -4
  75. data/_plugins/plugin_loader.rb +3 -1
  76. data/_plugins/publications_generator.rb +8 -2
  77. data/_plugins/references.rb +238 -0
  78. data/_plugins/reproducibility.rb +150 -0
  79. data/_plugins/revisions.rb +101 -0
  80. data/_plugins/rouge_highlight_filter.rb +42 -0
  81. data/_plugins/scholarly.rb +50 -0
  82. data/_plugins/search_code_blocks.rb +30 -0
  83. data/_plugins/search_normalizer.rb +15 -53
  84. data/_plugins/search_pages.rb +3 -4
  85. data/_plugins/series.rb +104 -0
  86. data/_plugins/statements.rb +87 -0
  87. data/_sass/_academic-dashboard.scss +262 -0
  88. data/_sass/_base.scss +20 -1
  89. data/_sass/_comments-thread.scss +159 -0
  90. data/_sass/_components.scss +64 -1153
  91. data/_sass/_features.scss +17 -0
  92. data/_sass/_layout.scss +385 -1
  93. data/_sass/_mathematical.scss +27 -0
  94. data/_sass/_moderation.scss +222 -0
  95. data/_sass/_notebooks.scss +322 -0
  96. data/_sass/_open-science-badges.scss +56 -0
  97. data/_sass/{_phase1-enhancements.scss → _post-components.scss} +5 -3
  98. data/_sass/_print.scss +291 -0
  99. data/_sass/_reactions.scss +89 -0
  100. data/_sass/_reading-state.scss +290 -0
  101. data/_sass/_search-page.scss +530 -0
  102. data/_sass/_search.scss +46 -0
  103. data/_sass/_service-forms.scss +204 -0
  104. data/_sass/_subscriptions.scss +140 -0
  105. data/_sass/_syntax-highlighting.scss +212 -97
  106. data/_sass/_theme.scss +40 -19
  107. data/_sass/_typography.scss +130 -0
  108. data/_sass/_utilities.scss +5 -0
  109. data/_sass/_variables.scss +6 -0
  110. data/_sass/_webmentions.scss +125 -0
  111. data/assets/css/main.scss +14 -0
  112. data/assets/js/dist/academic.js +1 -1
  113. data/assets/js/dist/analytics-dashboard.js +1 -1
  114. data/assets/js/dist/chunks/chunk-2DYDWUFX.js +1 -0
  115. data/assets/js/dist/chunks/chunk-PATLC23F.js +1 -0
  116. data/assets/js/dist/chunks/chunk-V7734B2G.js +1 -0
  117. data/assets/js/dist/comments.js +2 -0
  118. data/assets/js/dist/contact.js +1 -0
  119. data/assets/js/dist/core.js +1 -1
  120. data/assets/js/dist/corrections.js +1 -0
  121. data/assets/js/dist/loader.js +1 -1
  122. data/assets/js/dist/math.js +1 -1
  123. data/assets/js/dist/moderation.js +1 -0
  124. data/assets/js/dist/notebook.js +1 -1
  125. data/assets/js/dist/reactions.js +1 -0
  126. data/assets/js/dist/reading-state.js +1 -0
  127. data/assets/js/dist/search.js +1 -1
  128. data/assets/js/dist/sources.json +52 -0
  129. data/assets/js/dist/subscriptions.js +1 -0
  130. data/assets/js/dist/visualizations.js +11 -2
  131. data/assets/js/dist/webmentions.js +1 -0
  132. data/assets/js/loader.js +37 -1
  133. data/datalog-theme.gemspec +35 -23
  134. data/lib/datalog/cli.rb +43 -15
  135. data/lib/datalog/critical_css.rb +168 -0
  136. data/lib/datalog/plugin_system/dependency_resolver.rb +0 -2
  137. data/lib/datalog/plugins/comments.rb +33 -3
  138. data/lib/datalog/theme/installed_files.rb +113 -0
  139. data/lib/datalog/theme/package.rb +57 -0
  140. data/lib/datalog/theme/repository_checkout.rb +94 -0
  141. data/lib/datalog/theme/version.rb +5 -1
  142. data/lib/datalog/warning_filter.rb +5 -11
  143. data/lib/datalog-theme.rb +6 -0
  144. metadata +96 -147
  145. data/_data/academic.yml +0 -217
  146. data/_data/config/author.yml +0 -121
  147. data/_data/datasets.yml +0 -28
  148. data/_data/js_meta.json +0 -371
  149. data/_data/navigation.yml +0 -145
  150. data/_data/projects.yml +0 -41
  151. data/_data/publications.yml +0 -28
  152. data/_data/social.yml +0 -73
  153. data/_data/visualizations.yml +0 -51
  154. data/_includes/components/advanced-search.html +0 -682
  155. data/_includes/components/bookmark-system.html +0 -96
  156. data/_includes/components/comments.html +0 -244
  157. data/_includes/components/content-recommendations.html +0 -228
  158. data/_includes/components/email-preferences.html +0 -200
  159. data/_includes/components/enhanced-metadata.html +0 -228
  160. data/_includes/components/language-switcher.html +0 -396
  161. data/_includes/components/navigation-enhancements.html +0 -454
  162. data/_includes/components/newsletter-signup.html +0 -178
  163. data/_includes/components/popular-posts.html +0 -233
  164. data/_includes/components/reading-progress.html +0 -133
  165. data/_includes/components/reading-time.html +0 -121
  166. data/_includes/components/series-navigation.html +0 -124
  167. data/_includes/components/social-proof.html +0 -34
  168. data/_includes/components/user-preferences.html +0 -566
  169. data/_includes/meta/syntax-config.html +0 -19
  170. data/_layouts/archive.html +0 -282
  171. data/_layouts/post-sidebar.html +0 -183
  172. data/_sass/_phase3-enhancements.scss +0 -874
  173. data/_sass/_phase4-enhancements.scss +0 -1214
  174. data/_sass/_phase5-enhancements.scss +0 -414
  175. data/assets/js/academic.js +0 -262
  176. data/assets/js/analytics-dashboard.js +0 -382
  177. data/assets/js/core/dark-mode.js +0 -79
  178. data/assets/js/core/github-cards.js +0 -123
  179. data/assets/js/core/language-filter.js +0 -69
  180. data/assets/js/core/navigation.js +0 -184
  181. data/assets/js/core/scroll-progress.js +0 -45
  182. data/assets/js/core/search-hotkeys.js +0 -62
  183. data/assets/js/core/skip-links.js +0 -62
  184. data/assets/js/dist/manifest.json +0 -22
  185. data/assets/js/dist/meta.json +0 -371
  186. data/assets/js/main.js +0 -23
  187. data/assets/js/math.js +0 -818
  188. data/assets/js/notebook.js +0 -158
  189. data/assets/js/search/analytics.js +0 -91
  190. data/assets/js/search/app.js +0 -271
  191. data/assets/js/search/autocomplete.js +0 -120
  192. data/assets/js/search/engine.js +0 -260
  193. data/assets/js/search/filters.js +0 -45
  194. data/assets/js/search/render.js +0 -217
  195. data/assets/js/search/utils.js +0 -99
  196. data/assets/js/search.js +0 -354
  197. data/assets/js/visualizations.js +0 -816
  198. data/assets/publications/datalog-publications.bib +0 -8
  199. data/assets/publications/datalog-publications.ris +0 -9
  200. data/assets/publications/publications.bib +0 -30
  201. data/assets/templates/diogo-ribeiro-cv.md +0 -31
  202. data/assets/templates/diogo-ribeiro-cv.tex +0 -32
  203. data/lib/datalog/theme/theme.rb +0 -18
@@ -1,260 +0,0 @@
1
- import {
2
- buildSnippet,
3
- fuzzyIncludes,
4
- isCodeQuery,
5
- isMathQuery,
6
- normalize,
7
- normalizeCode,
8
- tokenize
9
- } from "./utils.js";
10
-
11
- export function createSearchEngine(initialDocuments = []) {
12
- let documents = Array.isArray(initialDocuments) ? [...initialDocuments] : [];
13
-
14
- function setDocuments(nextDocuments) {
15
- documents = Array.isArray(nextDocuments) ? [...nextDocuments] : [];
16
- }
17
-
18
- function search(rawQuery, filters = {}) {
19
- const query = (rawQuery || "").trim();
20
- if (!query) {
21
- return [];
22
- }
23
-
24
- const normalizedQuery = normalize(query);
25
- const queryTokens = tokenize(query);
26
- const mathQuery = isMathQuery(query);
27
- const codeQuery = isCodeQuery(query);
28
- const {
29
- type: typeFilter = "",
30
- language: languageFilter = "",
31
- difficulty: difficultyFilter = "",
32
- tags: selectedTags = new Set()
33
- } = filters;
34
-
35
- return documents
36
- .map((doc) =>
37
- evaluateDocument(doc, {
38
- rawQuery: query,
39
- normalizedQuery,
40
- queryTokens,
41
- mathQuery,
42
- codeQuery,
43
- typeFilter,
44
- languageFilter,
45
- difficultyFilter,
46
- selectedTags
47
- })
48
- )
49
- .filter((match) => match && match.score > 0)
50
- .sort((a, b) => b.score - a.score || a.title.localeCompare(b.title));
51
- }
52
-
53
- function suggestions(limit = 150) {
54
- return buildSuggestionPool(documents, limit);
55
- }
56
-
57
- function getDocuments() {
58
- return [...documents];
59
- }
60
-
61
- return { setDocuments, search, suggestions, getDocuments };
62
- }
63
-
64
- export function buildSuggestionPool(items = [], limit = 150) {
65
- const suggestionSet = new Set();
66
- items.forEach((item) => {
67
- if (item.title) {
68
- suggestionSet.add(item.title);
69
- }
70
- if (Array.isArray(item.languages)) {
71
- item.languages.forEach((lang) => suggestionSet.add(lang));
72
- }
73
- if (Array.isArray(item.tags)) {
74
- item.tags.forEach((tag) => suggestionSet.add(`#${tag}`));
75
- }
76
- if (Array.isArray(item.math)) {
77
- item.math.slice(0, 3).forEach((expression) => suggestionSet.add(expression));
78
- }
79
- });
80
- return Array.from(suggestionSet).slice(0, limit);
81
- }
82
-
83
- function evaluateDocument(doc, context) {
84
- const {
85
- rawQuery,
86
- normalizedQuery,
87
- queryTokens,
88
- mathQuery,
89
- codeQuery,
90
- typeFilter,
91
- languageFilter,
92
- difficultyFilter,
93
- selectedTags
94
- } = context;
95
-
96
- if (typeFilter && doc.type !== typeFilter) {
97
- return null;
98
- }
99
-
100
- const docLanguages = Array.isArray(doc.languages) ? doc.languages : [];
101
- if (languageFilter && !docLanguages.includes(languageFilter)) {
102
- return null;
103
- }
104
-
105
- if (difficultyFilter) {
106
- const difficulty = String(doc.difficulty || "").toLowerCase();
107
- if (!difficulty || difficulty !== difficultyFilter) {
108
- return null;
109
- }
110
- }
111
-
112
- if (selectedTags && selectedTags.size > 0) {
113
- const normalizedTags = normalizedArray(doc, "tags");
114
- for (const tag of selectedTags) {
115
- if (!normalizedTags.has(tag)) {
116
- return null;
117
- }
118
- }
119
- }
120
-
121
- if (!rawQuery) {
122
- return null;
123
- }
124
-
125
- const normalizedData = doc.normalized || {};
126
- const normalizedContent = normalizedData.content || normalize(doc.content || "");
127
- const normalizedSummary = normalizedData.summary || normalize(doc.summary || "");
128
- const normalizedTitle = normalizedData.title || normalize(doc.title || "");
129
- const normalizedTags = arrayFromNormalized(normalizedData.tags, doc.tags);
130
- const normalizedLanguages = arrayFromNormalized(normalizedData.languages, doc.languages);
131
- const normalizedDifficulty = normalizedData.difficulty || normalize(doc.difficulty || "");
132
- const codeBlocks = Array.isArray(doc.code) ? doc.code : [];
133
- const mathSegments = Array.isArray(doc.math) ? doc.math : [];
134
-
135
- let score = 0;
136
- let snippet = "";
137
- let codeSnippet = null;
138
- let mathSnippet = null;
139
-
140
- if (normalizedTitle.includes(normalizedQuery)) {
141
- score += 40;
142
- }
143
-
144
- queryTokens.forEach((token) => {
145
- if (!token) {
146
- return;
147
- }
148
- if (normalizedTitle.includes(token)) {
149
- score += 12;
150
- }
151
- if (normalizedSummary.includes(token)) {
152
- score += 8;
153
- }
154
- if (normalizedContent.includes(token)) {
155
- score += 6;
156
- }
157
- normalizedTags.forEach((tag) => {
158
- if (tag.includes(token)) {
159
- score += 4;
160
- }
161
- });
162
- normalizedLanguages.forEach((lang) => {
163
- if (lang.includes(token)) {
164
- score += 4;
165
- }
166
- });
167
- if (normalizedDifficulty.includes(token)) {
168
- score += 2;
169
- }
170
- });
171
-
172
- if (codeBlocks.length > 0) {
173
- const normalizedCodeQuery = normalizeCode(rawQuery);
174
- for (const block of codeBlocks) {
175
- const language = String(block.language || "").toLowerCase();
176
- const codeText = block.code || "";
177
- const normalizedCode = normalizeCode(codeText);
178
- if (normalizedCode.includes(normalizedCodeQuery)) {
179
- score += 30;
180
- codeSnippet = { language, code: codeText };
181
- break;
182
- }
183
- if (codeQuery && fuzzyIncludes(normalizedCode, normalizedCodeQuery)) {
184
- score += 18;
185
- codeSnippet = { language, code: codeText };
186
- break;
187
- }
188
- }
189
- if (!codeSnippet && codeBlocks[0]) {
190
- codeSnippet = {
191
- language: String(codeBlocks[0].language || "").toLowerCase(),
192
- code: codeBlocks[0].code || ""
193
- };
194
- }
195
- }
196
-
197
- if (mathSegments.length > 0) {
198
- const normalizedMathQuery = normalizedQuery.replace(/\\/g, "");
199
- for (const expression of mathSegments) {
200
- const normalizedExpression = normalize(String(expression));
201
- if (normalizedExpression.includes(normalizedMathQuery)) {
202
- score += 26;
203
- mathSnippet = expression;
204
- break;
205
- }
206
- }
207
- if (mathQuery && !mathSnippet && mathSegments[0]) {
208
- mathSnippet = mathSegments[0];
209
- score += 10;
210
- }
211
- }
212
-
213
- if (normalizedContent.includes(normalizedQuery)) {
214
- const index = normalizedContent.indexOf(normalizedQuery);
215
- snippet = buildSnippet(doc.content || "", index, rawQuery.length);
216
- score += 10;
217
- } else if (normalizedSummary.includes(normalizedQuery)) {
218
- const index = normalizedSummary.indexOf(normalizedQuery);
219
- snippet = buildSnippet(doc.summary || "", index, rawQuery.length);
220
- score += 6;
221
- }
222
-
223
- if (score === 0) {
224
- return null;
225
- }
226
-
227
- return {
228
- id: `${doc.url}`,
229
- title: doc.title,
230
- url: doc.url,
231
- summary: doc.summary,
232
- snippet,
233
- codeSnippet,
234
- mathSnippet,
235
- tags: doc.tags || [],
236
- languages: doc.languages || [],
237
- difficulty: doc.difficulty || "",
238
- type: doc.type || "page",
239
- date: doc.date,
240
- readingTime: doc.reading_time,
241
- score
242
- };
243
- }
244
-
245
- function arrayFromNormalized(normalizedValues, fallback) {
246
- if (Array.isArray(normalizedValues) && normalizedValues.length > 0) {
247
- return normalizedValues;
248
- }
249
- return Array.isArray(fallback) ? fallback.map((value) => normalize(value)) : [];
250
- }
251
-
252
- function normalizedArray(doc, key) {
253
- const normalized = doc.normalized || {};
254
- const values = normalized[key];
255
- if (Array.isArray(values) && values.length > 0) {
256
- return new Set(values);
257
- }
258
- const fallback = Array.isArray(doc[key]) ? doc[key] : [];
259
- return new Set(fallback.map((value) => normalize(value)));
260
- }
@@ -1,45 +0,0 @@
1
- export function renderTagFilters(container, documents, selectedTags) {
2
- if (!container) {
3
- return;
4
- }
5
-
6
- const tagSet = new Set();
7
- documents.forEach((item) => {
8
- const source = item.tags || item.topics || [];
9
- source.forEach((tag) => {
10
- if (!tag) {
11
- return;
12
- }
13
-
14
- // Trim as well: a whitespace-only tag is truthy and used to render a
15
- // button with no accessible name at all.
16
- const normalized = String(tag).trim().toLowerCase();
17
- if (normalized) {
18
- tagSet.add(normalized);
19
- }
20
- });
21
- });
22
-
23
- const tags = Array.from(tagSet).sort();
24
- if (tags.length === 0) {
25
- const p = document.createElement("p");
26
- p.className = "search-filter__empty";
27
- p.textContent = "No tags available yet.";
28
- container.replaceChildren(p);
29
- return;
30
- }
31
-
32
- const fragment = document.createDocumentFragment();
33
- tags.forEach((tag) => {
34
- const button = document.createElement("button");
35
- button.type = "button";
36
- button.className = "search-filter__tag";
37
- button.textContent = tag;
38
- button.dataset.filterTag = tag;
39
- button.setAttribute("aria-pressed", selectedTags.has(tag) ? "true" : "false");
40
- fragment.appendChild(button);
41
- });
42
-
43
- container.replaceChildren();
44
- container.appendChild(fragment);
45
- }
@@ -1,217 +0,0 @@
1
- import { formatType, highlightText, toTitleCase } from "./utils.js";
2
-
3
- export function renderResults(results, query, elements) {
4
- const { resultsList, resultsMeta, emptyState, template } = elements;
5
-
6
- if (!resultsList || !resultsMeta || !emptyState || !template) {
7
- return;
8
- }
9
-
10
- resultsList.replaceChildren();
11
-
12
- if (!query) {
13
- resultsMeta.textContent = "Enter a query to begin.";
14
- emptyState.hidden = true;
15
- return;
16
- }
17
-
18
- if (results.length === 0) {
19
- resultsMeta.textContent = `No matches for “${query}”.`;
20
- emptyState.hidden = false;
21
- return;
22
- }
23
-
24
- emptyState.hidden = true;
25
- resultsMeta.textContent = `${results.length} result${results.length === 1 ? "" : "s"} for “${query}”.`;
26
-
27
- const fragment = document.createDocumentFragment();
28
-
29
- results.forEach((result) => {
30
- const clone = template.content.cloneNode(true);
31
- const typeEl = clone.querySelector("[data-result-type]");
32
- const difficultyEl = clone.querySelector("[data-result-difficulty]");
33
- const languageEl = clone.querySelector("[data-result-languages]");
34
- const linkEl = clone.querySelector("[data-result-link]");
35
- const summaryEl = clone.querySelector("[data-result-summary]");
36
- const codeEl = clone.querySelector("[data-result-code]");
37
- const codeCodeEl = codeEl ? codeEl.querySelector("code") : null;
38
- const mathEl = clone.querySelector("[data-result-math]");
39
- const mathPreviewEl = clone.querySelector("[data-result-math-preview]");
40
- const mathCodeEl = clone.querySelector("[data-result-math-code]");
41
- const mathCopyButton = clone.querySelector("[data-result-math-copy]");
42
- const excerptEl = clone.querySelector("[data-result-excerpt]");
43
- const tagsEl = clone.querySelector("[data-result-tags]");
44
- const dateEl = clone.querySelector("[data-result-date]");
45
-
46
- if (typeEl) {
47
- typeEl.textContent = formatType(result.type);
48
- }
49
-
50
- if (difficultyEl) {
51
- if (result.difficulty) {
52
- difficultyEl.textContent = `Difficulty: ${toTitleCase(result.difficulty)}`;
53
- difficultyEl.hidden = false;
54
- } else {
55
- difficultyEl.hidden = true;
56
- }
57
- }
58
-
59
- if (languageEl) {
60
- if (result.languages && result.languages.length > 0) {
61
- languageEl.textContent = `Languages: ${result.languages.map((lang) => toTitleCase(lang)).join(", ")}`;
62
- languageEl.hidden = false;
63
- } else {
64
- languageEl.hidden = true;
65
- }
66
- }
67
-
68
- if (linkEl) {
69
- linkEl.textContent = result.title || result.url;
70
- linkEl.setAttribute("href", result.url);
71
- }
72
-
73
- if (summaryEl) {
74
- summaryEl.replaceChildren();
75
- summaryEl.insertAdjacentHTML("beforeend", highlightText(result.summary || "", query));
76
- }
77
-
78
- if (mathEl && mathCodeEl) {
79
- if (result.mathSnippet) {
80
- mathEl.hidden = false;
81
- mathCodeEl.textContent = result.mathSnippet;
82
- if (mathPreviewEl) {
83
- if (window.DatalogMath && typeof window.DatalogMath.renderLatex === "function") {
84
- window.DatalogMath.renderLatex(mathPreviewEl, result.mathSnippet, {
85
- display: false,
86
- enhance: false
87
- });
88
- } else if (window.MathJax && typeof window.MathJax.typesetPromise === "function") {
89
- mathPreviewEl.innerHTML = `\\(${result.mathSnippet}\\)`;
90
- window.MathJax.typesetPromise([mathPreviewEl]).catch(() => {
91
- mathPreviewEl.textContent = result.mathSnippet;
92
- });
93
- } else {
94
- mathPreviewEl.textContent = result.mathSnippet;
95
- }
96
- }
97
- if (mathCopyButton) {
98
- mathCopyButton.addEventListener("click", () => copyToClipboard(result.mathSnippet, mathEl));
99
- }
100
- } else {
101
- mathEl.hidden = true;
102
- }
103
- }
104
-
105
- if (excerptEl) {
106
- if (result.snippet) {
107
- excerptEl.replaceChildren();
108
- excerptEl.insertAdjacentHTML("beforeend", highlightText(result.snippet, query));
109
- excerptEl.hidden = false;
110
- } else {
111
- excerptEl.hidden = true;
112
- }
113
- }
114
-
115
- if (codeEl && codeCodeEl) {
116
- if (result.codeSnippet && result.codeSnippet.code) {
117
- const language = result.codeSnippet.language || "text";
118
- codeEl.classList.remove("language-none");
119
- codeEl.classList.add(`language-${language}`);
120
- codeCodeEl.className = `language-${language}`;
121
- const code = result.codeSnippet.code;
122
- if (window.Prism && window.Prism.languages) {
123
- const grammar = window.Prism.languages[language] || window.Prism.languages.markup;
124
- codeCodeEl.innerHTML = window.Prism.highlight(code, grammar, language);
125
- } else {
126
- codeCodeEl.textContent = code;
127
- }
128
- codeEl.hidden = false;
129
- } else {
130
- codeEl.hidden = true;
131
- }
132
- }
133
-
134
- if (tagsEl) {
135
- tagsEl.replaceChildren();
136
- (result.tags || []).forEach((tag) => {
137
- const li = document.createElement("li");
138
- li.textContent = `#${tag}`;
139
- tagsEl.appendChild(li);
140
- });
141
- }
142
-
143
- if (dateEl) {
144
- if (result.date) {
145
- const date = new Date(result.date);
146
- if (!Number.isNaN(date.getTime())) {
147
- dateEl.textContent = date.toLocaleDateString(undefined, {
148
- year: "numeric",
149
- month: "short",
150
- day: "numeric"
151
- });
152
- dateEl.hidden = false;
153
- } else {
154
- dateEl.hidden = true;
155
- }
156
- } else {
157
- dateEl.hidden = true;
158
- }
159
- }
160
-
161
- fragment.appendChild(clone);
162
- });
163
-
164
- resultsList.appendChild(fragment);
165
- }
166
-
167
- function copyToClipboard(value, container) {
168
- if (!value) {
169
- return;
170
- }
171
-
172
- const provideFeedback = () => {
173
- if (!container) {
174
- return;
175
- }
176
- const button = container.querySelector("[data-result-math-copy]");
177
- const sr = button ? button.querySelector(".visually-hidden") : null;
178
- if (button && sr) {
179
- const original = button.getAttribute("data-sr-original") || sr.textContent;
180
- if (!button.getAttribute("data-sr-original")) {
181
- button.setAttribute("data-sr-original", original || "Copy LaTeX");
182
- }
183
- sr.textContent = "Copied LaTeX";
184
- button.classList.add("is-copied");
185
- window.setTimeout(() => {
186
- sr.textContent = button.getAttribute("data-sr-original") || "Copy LaTeX";
187
- button.classList.remove("is-copied");
188
- }, 2000);
189
- }
190
- };
191
-
192
- const fallback = () => {
193
- const temp = document.createElement("textarea");
194
- temp.value = value;
195
- temp.setAttribute("aria-hidden", "true");
196
- temp.style.position = "fixed";
197
- temp.style.opacity = "0";
198
- document.body.appendChild(temp);
199
- temp.select();
200
- try {
201
- document.execCommand("copy");
202
- provideFeedback();
203
- } catch (error) {
204
- console.warn("Unable to copy LaTeX snippet", error);
205
- }
206
- document.body.removeChild(temp);
207
- };
208
-
209
- if (navigator.clipboard && navigator.clipboard.writeText) {
210
- navigator.clipboard
211
- .writeText(value)
212
- .then(() => provideFeedback())
213
- .catch(() => fallback());
214
- } else {
215
- fallback();
216
- }
217
- }
@@ -1,99 +0,0 @@
1
- export function normalize(value = "") {
2
- const source = value == null ? "" : String(value);
3
- try {
4
- return source.toLowerCase().normalize("NFKD").replace(/\p{Diacritic}/gu, "");
5
- } catch (error) {
6
- return source.toLowerCase();
7
- }
8
- }
9
-
10
- export function normalizeCode(value = "") {
11
- return normalize(value).replace(/\s+/g, "");
12
- }
13
-
14
- export function tokenize(query = "") {
15
- return normalize(query)
16
- .split(/[^a-z0-9\\._]+/)
17
- .filter(Boolean);
18
- }
19
-
20
- export function isMathQuery(query = "") {
21
- return /\\|\^|_|\{|\}|\$/.test(query);
22
- }
23
-
24
- export function isCodeQuery(query = "") {
25
- return /`|::|->|=>|\(|\)|\{|\}|\[|\]|=|\bdef\b|\bfunction\b|\bclass\b/.test(query);
26
- }
27
-
28
- export function fuzzyIncludes(source, query) {
29
- if (!source || !query) {
30
- return false;
31
- }
32
- let idx = 0;
33
- for (let i = 0; i < source.length && idx < query.length; i += 1) {
34
- if (source[i] === query[idx]) {
35
- idx += 1;
36
- }
37
- }
38
- return idx === query.length;
39
- }
40
-
41
- export function stripHtml(value) {
42
- if (!value) {
43
- return "";
44
- }
45
- const temp = document.createElement("div");
46
- temp.innerHTML = value;
47
- return temp.textContent || temp.innerText || "";
48
- }
49
-
50
- export function buildSnippet(content, index, length) {
51
- if (!content) {
52
- return "";
53
- }
54
- const cleanContent = stripHtml(content);
55
- const start = Math.max(0, index - 80);
56
- const end = Math.min(cleanContent.length, index + length + 80);
57
- return `${cleanContent.slice(start, end)}${end < cleanContent.length ? "…" : ""}`;
58
- }
59
-
60
- export function escapeRegex(value) {
61
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
62
- }
63
-
64
- export function highlightText(text, query) {
65
- if (!text || !query) {
66
- return text;
67
- }
68
- const tokens = Array.from(new Set(tokenize(query))).sort((a, b) => b.length - a.length);
69
- let highlighted = text;
70
- tokens.forEach((token) => {
71
- if (!token) return;
72
- const regex = new RegExp(`(${escapeRegex(token)})`, "gi");
73
- highlighted = highlighted.replace(regex, "<mark>$1</mark>");
74
- });
75
- return highlighted;
76
- }
77
-
78
- export function toTitleCase(value) {
79
- return (value || "")
80
- .toString()
81
- .split(/[-_\s]+/)
82
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
83
- .join(" ");
84
- }
85
-
86
- export function formatType(type) {
87
- switch (type) {
88
- case "post":
89
- return "Tutorial & Blog";
90
- case "project":
91
- return "Project";
92
- case "research":
93
- return "Research";
94
- case "dataset":
95
- return "Dataset";
96
- default:
97
- return toTitleCase(type || "Page");
98
- }
99
- }