unaltraweb 0.3.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 (444) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/Makefile +412 -0
  4. data/README.md +226 -0
  5. data/_config.yml +842 -0
  6. data/_data/i18n/ca.yml +278 -0
  7. data/_data/i18n/en.yml +278 -0
  8. data/_data/i18n/es.yml +278 -0
  9. data/_includes/audio.liquid +30 -0
  10. data/_includes/bib_search.liquid +5 -0
  11. data/_includes/blog-list.liquid +66 -0
  12. data/_includes/book-citation.liquid +33 -0
  13. data/_includes/citation.liquid +26 -0
  14. data/_includes/custom_head.liquid +3 -0
  15. data/_includes/custom_scripts.liquid +5 -0
  16. data/_includes/cv/list.liquid +5 -0
  17. data/_includes/cv/list_groups.liquid +51 -0
  18. data/_includes/cv/map.liquid +29 -0
  19. data/_includes/cv/nested_list.liquid +16 -0
  20. data/_includes/cv/time_table.liquid +118 -0
  21. data/_includes/cv-download-card.liquid +26 -0
  22. data/_includes/developer-mode-switcher.liquid +24 -0
  23. data/_includes/disqus.liquid +13 -0
  24. data/_includes/distill_scripts.liquid +298 -0
  25. data/_includes/documentation-download-detail.liquid +76 -0
  26. data/_includes/documentation-downloads.liquid +71 -0
  27. data/_includes/documentation-profile-attributes.liquid +4 -0
  28. data/_includes/documentation-section-toc.liquid +9 -0
  29. data/_includes/documentation-sidebar.liquid +112 -0
  30. data/_includes/figure.liquid +87 -0
  31. data/_includes/footer.liquid +66 -0
  32. data/_includes/giscus.liquid +25 -0
  33. data/_includes/head.liquid +220 -0
  34. data/_includes/header.liquid +544 -0
  35. data/_includes/latest_posts.liquid +61 -0
  36. data/_includes/localized-date.liquid +5 -0
  37. data/_includes/manual-bibliography.liquid +2 -0
  38. data/_includes/manual-featured-readings.liquid +12 -0
  39. data/_includes/manual-other-readings.liquid +12 -0
  40. data/_includes/manual-page-toc.liquid +11 -0
  41. data/_includes/manual-sidebar.liquid +58 -0
  42. data/_includes/manual-teachers.liquid +74 -0
  43. data/_includes/metadata.liquid +278 -0
  44. data/_includes/news.liquid +48 -0
  45. data/_includes/newsletter.liquid +68 -0
  46. data/_includes/page-hero.liquid +96 -0
  47. data/_includes/page-link.liquid +8 -0
  48. data/_includes/pagination.liquid +22 -0
  49. data/_includes/post-reading-time.liquid +29 -0
  50. data/_includes/previous-next.liquid +37 -0
  51. data/_includes/profile-card.liquid +49 -0
  52. data/_includes/profile-feature-matrix.liquid +75 -0
  53. data/_includes/profile-highlights.liquid +122 -0
  54. data/_includes/project-grid.liquid +26 -0
  55. data/_includes/project-resource-link.liquid +45 -0
  56. data/_includes/project-resources.liquid +21 -0
  57. data/_includes/projects.liquid +38 -0
  58. data/_includes/projects_horizontal.liquid +37 -0
  59. data/_includes/publications-metrics-summary.liquid +69 -0
  60. data/_includes/publications-summary.liquid +127 -0
  61. data/_includes/reading-biblio-controls.liquid +65 -0
  62. data/_includes/reading-cover-card.liquid +93 -0
  63. data/_includes/reading-rating.liquid +11 -0
  64. data/_includes/recommended-readings.liquid +30 -0
  65. data/_includes/related_posts.liquid +39 -0
  66. data/_includes/repository/repo.liquid +48 -0
  67. data/_includes/repository/repo_trophies.liquid +44 -0
  68. data/_includes/repository/repo_user.liquid +35 -0
  69. data/_includes/resume/awards.liquid +19 -0
  70. data/_includes/resume/basics.liquid +28 -0
  71. data/_includes/resume/certificates.liquid +35 -0
  72. data/_includes/resume/education.liquid +54 -0
  73. data/_includes/resume/interests.liquid +34 -0
  74. data/_includes/resume/languages.liquid +31 -0
  75. data/_includes/resume/projects.liquid +32 -0
  76. data/_includes/resume/publications.liquid +28 -0
  77. data/_includes/resume/references.liquid +31 -0
  78. data/_includes/resume/skills.liquid +33 -0
  79. data/_includes/resume/volunteer.liquid +52 -0
  80. data/_includes/resume/work.liquid +52 -0
  81. data/_includes/scripts.liquid +364 -0
  82. data/_includes/search-data.js.liquid +341 -0
  83. data/_includes/selected_papers.liquid +3 -0
  84. data/_includes/t.liquid +31 -0
  85. data/_includes/team-grid.liquid +172 -0
  86. data/_includes/video.liquid +97 -0
  87. data/_layouts/about.liquid +93 -0
  88. data/_layouts/archive.liquid +45 -0
  89. data/_layouts/bib.liquid +677 -0
  90. data/_layouts/book-review.liquid +225 -0
  91. data/_layouts/book-shelf.liquid +66 -0
  92. data/_layouts/cv.liquid +127 -0
  93. data/_layouts/default.liquid +70 -0
  94. data/_layouts/distill.liquid +132 -0
  95. data/_layouts/documentation-download-detail.liquid +5 -0
  96. data/_layouts/documentation-home.liquid +100 -0
  97. data/_layouts/documentation-page.liquid +45 -0
  98. data/_layouts/manual-bib.liquid +73 -0
  99. data/_layouts/manual-chapter.liquid +99 -0
  100. data/_layouts/manual-featured-bib.liquid +100 -0
  101. data/_layouts/manual-home.liquid +120 -0
  102. data/_layouts/none.liquid +1 -0
  103. data/_layouts/outputs.liquid +29 -0
  104. data/_layouts/page.liquid +52 -0
  105. data/_layouts/post.liquid +95 -0
  106. data/_layouts/profile.liquid +36 -0
  107. data/_layouts/profiles.liquid +36 -0
  108. data/_layouts/project.liquid +7 -0
  109. data/_layouts/repositories.liquid +58 -0
  110. data/_layouts/theses.liquid +126 -0
  111. data/_plugins/bibliography_profiles.rb +164 -0
  112. data/_plugins/callouts.rb +104 -0
  113. data/_plugins/code_blocks.rb +277 -0
  114. data/_plugins/computation_figure_images.rb +266 -0
  115. data/_plugins/computation_sources.rb +42 -0
  116. data/_plugins/content_search_index.rb +210 -0
  117. data/_plugins/details.rb +23 -0
  118. data/_plugins/external-posts.rb +124 -0
  119. data/_plugins/figure_captions.rb +800 -0
  120. data/_plugins/file-exists.rb +22 -0
  121. data/_plugins/google-scholar-citations.rb +86 -0
  122. data/_plugins/hide-custom-bibtex.rb +18 -0
  123. data/_plugins/inspirehep-citations.rb +57 -0
  124. data/_plugins/localized_visual_sources.rb +145 -0
  125. data/_plugins/manual_release_metadata.rb +47 -0
  126. data/_plugins/mermaid_mmd_images.rb +233 -0
  127. data/_plugins/profile-pages.rb +28 -0
  128. data/_plugins/remove-accents.rb +32 -0
  129. data/_plugins/reproducible_build_time.rb +55 -0
  130. data/_plugins/search-data.rb +83 -0
  131. data/_plugins/theme-cache-bust.rb +56 -0
  132. data/_plugins/vega_visual_images.rb +290 -0
  133. data/_plugins/web_capture_images.rb +154 -0
  134. data/_sass/_base.scss +2133 -0
  135. data/_sass/_blog.scss +67 -0
  136. data/_sass/_callouts.scss +100 -0
  137. data/_sass/_custom.scss +437 -0
  138. data/_sass/_cv.scss +274 -0
  139. data/_sass/_developer.scss +97 -0
  140. data/_sass/_distill.scss +185 -0
  141. data/_sass/_documentation.scss +2220 -0
  142. data/_sass/_layout.scss +218 -0
  143. data/_sass/_manual.scss +2691 -0
  144. data/_sass/_profile.scss +323 -0
  145. data/_sass/_projects.scss +163 -0
  146. data/_sass/_reading.scss +465 -0
  147. data/_sass/_site-custom.scss +3 -0
  148. data/_sass/_tabs.scss +48 -0
  149. data/_sass/_team.scss +230 -0
  150. data/_sass/_themes.scss +254 -0
  151. data/_sass/_typograms.scss +132 -0
  152. data/_sass/_variables.scss +52 -0
  153. data/_sass/font-awesome/_animated.scss +152 -0
  154. data/_sass/font-awesome/_bordered-pulled.scss +20 -0
  155. data/_sass/font-awesome/_core.scss +49 -0
  156. data/_sass/font-awesome/_fixed-width.scss +7 -0
  157. data/_sass/font-awesome/_functions.scss +57 -0
  158. data/_sass/font-awesome/_icons.scss +13 -0
  159. data/_sass/font-awesome/_list.scss +18 -0
  160. data/_sass/font-awesome/_mixins.scss +65 -0
  161. data/_sass/font-awesome/_rotated-flipped.scss +31 -0
  162. data/_sass/font-awesome/_screen-reader.scss +14 -0
  163. data/_sass/font-awesome/_shims.scss +1578 -0
  164. data/_sass/font-awesome/_sizing.scss +16 -0
  165. data/_sass/font-awesome/_stacked.scss +32 -0
  166. data/_sass/font-awesome/_variables.scss +5044 -0
  167. data/_sass/font-awesome/brands.scss +30 -0
  168. data/_sass/font-awesome/fontawesome.scss +21 -0
  169. data/_sass/font-awesome/regular.scss +27 -0
  170. data/_sass/font-awesome/solid.scss +27 -0
  171. data/_sass/font-awesome/v4-shims.scss +11 -0
  172. data/_sass/tabler-icons/tabler-icons-filled.scss +1646 -0
  173. data/_sass/tabler-icons/tabler-icons-outline.scss +9859 -0
  174. data/_sass/tabler-icons/tabler-icons.scss +11463 -0
  175. data/_scripts/cronitor-analytics-setup.js +9 -0
  176. data/_scripts/giscus-setup.js +48 -0
  177. data/_scripts/google-analytics-setup.js +9 -0
  178. data/_scripts/open-panel-analytics-setup.js +14 -0
  179. data/_scripts/photoswipe-setup.js +11 -0
  180. data/assets/audio/epicaly-short-113909.mp3 +0 -0
  181. data/assets/bibliography/2018-12-22-distill.bib +7 -0
  182. data/assets/css/academicons.min.css +1 -0
  183. data/assets/css/bootstrap-toc.min.css +4 -0
  184. data/assets/css/bootstrap.min.css +7 -0
  185. data/assets/css/bootstrap.min.css.map +1 -0
  186. data/assets/css/jekyll-pygments-themes-github.css +209 -0
  187. data/assets/css/jekyll-pygments-themes-native.css +232 -0
  188. data/assets/css/jupyter-grade3.css +3758 -0
  189. data/assets/css/jupyter-monokai.css +3758 -0
  190. data/assets/css/jupyter.css +383 -0
  191. data/assets/css/main.scss +36 -0
  192. data/assets/css/mdb.min.css +48 -0
  193. data/assets/css/mdb.min.css.map +1 -0
  194. data/assets/css/scholar-icons.css +386 -0
  195. data/assets/fonts/academicons.ttf +0 -0
  196. data/assets/fonts/academicons.woff +0 -0
  197. data/assets/fonts/scholar-icons.ttf +0 -0
  198. data/assets/fonts/scholar-icons.woff +0 -0
  199. data/assets/fonts/tabler-icons-filled.ttf +0 -0
  200. data/assets/fonts/tabler-icons-filled.woff +0 -0
  201. data/assets/fonts/tabler-icons-filled.woff2 +0 -0
  202. data/assets/fonts/tabler-icons-outline.ttf +0 -0
  203. data/assets/fonts/tabler-icons-outline.woff +0 -0
  204. data/assets/fonts/tabler-icons-outline.woff2 +0 -0
  205. data/assets/fonts/tabler-icons.ttf +0 -0
  206. data/assets/fonts/tabler-icons.woff +0 -0
  207. data/assets/fonts/tabler-icons.woff2 +0 -0
  208. data/assets/html/relativity.html +15 -0
  209. data/assets/img/1.jpg +0 -0
  210. data/assets/img/10.jpg +0 -0
  211. data/assets/img/11.jpg +0 -0
  212. data/assets/img/12.jpg +0 -0
  213. data/assets/img/2.jpg +0 -0
  214. data/assets/img/3.jpg +0 -0
  215. data/assets/img/4.jpg +0 -0
  216. data/assets/img/5.jpg +0 -0
  217. data/assets/img/6.jpg +0 -0
  218. data/assets/img/7.jpg +0 -0
  219. data/assets/img/8.jpg +0 -0
  220. data/assets/img/9.jpg +0 -0
  221. data/assets/img/book_covers/the_godfather.jpg +0 -0
  222. data/assets/img/brand/dosquartsdedocs-logo-cafe.svg +31 -0
  223. data/assets/img/brand/dosquartsdedocs-logo-footer-cafe.svg +31 -0
  224. data/assets/img/brand/dosquartsdedocs-logo-white.svg +31 -0
  225. data/assets/img/brand/dosquartsdedocs-logo.svg +31 -0
  226. data/assets/img/prof_pic.jpg +0 -0
  227. data/assets/img/prof_pic_color.png +0 -0
  228. data/assets/img/profile-placeholder.svg +8 -0
  229. data/assets/img/publication_preview/brownian-motion.gif +0 -0
  230. data/assets/img/publication_preview/goodchild1992-gis-cover.jpg +0 -0
  231. data/assets/img/publication_preview/gutierrez2020-jtrg-preview.jpg +0 -0
  232. data/assets/img/publication_preview/harvey1989-postmodernity-cover.jpg +0 -0
  233. data/assets/img/publication_preview/tobler1970-economic-geography-cover.jpg +0 -0
  234. data/assets/img/publication_preview/tuan1977-space-place-cover.jpg +0 -0
  235. data/assets/img/publication_preview/wave-mechanics.gif +0 -0
  236. data/assets/img/publication_preview/zaragozi2025-rooftop-pv-preview.jpg +0 -0
  237. data/assets/img/rhino.png +0 -0
  238. data/assets/img/team/aaron-gutierrez.png +0 -0
  239. data/assets/img/team/alba-font.png +0 -0
  240. data/assets/img/team/alba-viana.png +0 -0
  241. data/assets/img/team/alejandro-gonzalez.png +0 -0
  242. data/assets/img/team/ana-pastor.png +0 -0
  243. data/assets/img/team/antoni-domenech.png +0 -0
  244. data/assets/img/team/benito-zaragozi.png +0 -0
  245. data/assets/img/team/cinta-sanz.png +0 -0
  246. data/assets/img/team/daniel-miravet.png +0 -0
  247. data/assets/img/team/eugenia-altamirano.png +0 -0
  248. data/assets/img/team/fiammetta-brandajs.png +0 -0
  249. data/assets/img/team/francesc-gonzalez.png +0 -0
  250. data/assets/img/team/isabel-paulino.png +0 -0
  251. data/assets/img/team/joan-borras.png +0 -0
  252. data/assets/img/team/julie-wilson.png +0 -0
  253. data/assets/img/team/liliana-sole.png +0 -0
  254. data/assets/img/team/maria-casado.png +0 -0
  255. data/assets/img/team/maria-medina.png +0 -0
  256. data/assets/img/team/marta-nello.png +0 -0
  257. data/assets/img/team/mogrify +1 -0
  258. data/assets/img/team/oscar-saladie.png +0 -0
  259. data/assets/img/team/paolo-russo.png +0 -0
  260. data/assets/img/team/raquel-santos.png +0 -0
  261. data/assets/img/team/salvador-anton.png +0 -0
  262. data/assets/img/team/sunsi-huertas.png +0 -0
  263. data/assets/img/team/user-placeholder.png +0 -0
  264. data/assets/img/team/wilbert-den-hoed.png +0 -0
  265. data/assets/img/team/xavier-delclos.png +0 -0
  266. data/assets/img/template_error.png +0 -0
  267. data/assets/js/bibsearch.js +70 -0
  268. data/assets/js/bootstrap-toc.min.js +5 -0
  269. data/assets/js/bootstrap.bundle.min.js +7 -0
  270. data/assets/js/bootstrap.bundle.min.js.map +1 -0
  271. data/assets/js/callouts.js +115 -0
  272. data/assets/js/chartjs-setup.js +14 -0
  273. data/assets/js/common.js +117 -0
  274. data/assets/js/content-search-match.js +159 -0
  275. data/assets/js/content-search.js +441 -0
  276. data/assets/js/copy_code.js +60 -0
  277. data/assets/js/custom.js +6 -0
  278. data/assets/js/developer-mode.js +103 -0
  279. data/assets/js/diff2html-setup.js +20 -0
  280. data/assets/js/distillpub/overrides.js +27 -0
  281. data/assets/js/distillpub/template.v2.js +9616 -0
  282. data/assets/js/distillpub/template.v2.js.map +1 -0
  283. data/assets/js/distillpub/transforms.v2.js +14557 -0
  284. data/assets/js/distillpub/transforms.v2.js.map +1 -0
  285. data/assets/js/documentation.js +435 -0
  286. data/assets/js/echarts-setup.js +29 -0
  287. data/assets/js/highlight-search-term.js +110 -0
  288. data/assets/js/jupyter_new_tab.js +18 -0
  289. data/assets/js/leaflet-setup.js +22 -0
  290. data/assets/js/manual.js +308 -0
  291. data/assets/js/masonry.js +12 -0
  292. data/assets/js/mathjax-setup.js +46 -0
  293. data/assets/js/mermaid-setup.js +37 -0
  294. data/assets/js/newsletter.js +105 -0
  295. data/assets/js/no_defer.js +24 -0
  296. data/assets/js/plotly-setup.js +52 -0
  297. data/assets/js/profile-nav.js +92 -0
  298. data/assets/js/progress-bar.js +73 -0
  299. data/assets/js/pseudocode-setup.js +18 -0
  300. data/assets/js/search/base-styles.min.js +286 -0
  301. data/assets/js/search/command-score.min.js +7 -0
  302. data/assets/js/search/hotkeys-js/hotkeys.esm.min.js +18 -0
  303. data/assets/js/search/lit/decorators.min.js +7 -0
  304. data/assets/js/search/lit/directives/class-map.min.js +7 -0
  305. data/assets/js/search/lit/directives/join.min.js +7 -0
  306. data/assets/js/search/lit/directives/live.min.js +7 -0
  307. data/assets/js/search/lit/directives/ref.min.js +7 -0
  308. data/assets/js/search/lit/directives/repeat.min.js +7 -0
  309. data/assets/js/search/lit/directives/unsafe-html.min.js +7 -0
  310. data/assets/js/search/lit/index.min.js +7 -0
  311. data/assets/js/search/lit-element/lit-element.min.js +12 -0
  312. data/assets/js/search/lit-html/directives/class-map.min.js +22 -0
  313. data/assets/js/search/lit-html/directives/join.min.js +12 -0
  314. data/assets/js/search/lit-html/directives/live.min.js +28 -0
  315. data/assets/js/search/lit-html/directives/ref.min.js +32 -0
  316. data/assets/js/search/lit-html/directives/repeat.min.js +27 -0
  317. data/assets/js/search/lit-html/directives/unsafe-html.min.js +22 -0
  318. data/assets/js/search/lit-html/is-server.min.js +12 -0
  319. data/assets/js/search/lit-html/lit-html.min.js +12 -0
  320. data/assets/js/search/ninja-action.min.js +101 -0
  321. data/assets/js/search/ninja-footer.min.js +62 -0
  322. data/assets/js/search/ninja-header.min.js +78 -0
  323. data/assets/js/search/ninja-keys.min.js +39 -0
  324. data/assets/js/search/reactive-element/decorators/custom-element.min.js +12 -0
  325. data/assets/js/search/reactive-element/decorators/event-options.min.js +12 -0
  326. data/assets/js/search/reactive-element/decorators/property.min.js +22 -0
  327. data/assets/js/search/reactive-element/decorators/query-all.min.js +17 -0
  328. data/assets/js/search/reactive-element/decorators/query-assigned-elements.min.js +17 -0
  329. data/assets/js/search/reactive-element/decorators/query-assigned-nodes.min.js +17 -0
  330. data/assets/js/search/reactive-element/decorators/query-async.min.js +17 -0
  331. data/assets/js/search/reactive-element/decorators/query.min.js +17 -0
  332. data/assets/js/search/reactive-element/decorators/state.min.js +27 -0
  333. data/assets/js/search/reactive-element/reactive-element.min.js +17 -0
  334. data/assets/js/search-setup.js +17 -0
  335. data/assets/js/shortcut-key.js +11 -0
  336. data/assets/js/tabs.min.js +5 -0
  337. data/assets/js/theme.js +314 -0
  338. data/assets/js/tooltips-setup.js +3 -0
  339. data/assets/js/typograms.js +1341 -0
  340. data/assets/js/vanilla-back-to-top.min.js +1 -0
  341. data/assets/js/vega-setup.js +24 -0
  342. data/assets/js/venobox-setup.js +5 -0
  343. data/assets/js/wechat.js +14 -0
  344. data/assets/js/zoom.js +15 -0
  345. data/assets/json/resume.json +98 -0
  346. data/assets/json/table_data.json +128 -0
  347. data/assets/jupyter/blog.ipynb +48 -0
  348. data/assets/pdf/example_pdf.pdf +0 -0
  349. data/assets/plotly/demo.html +72 -0
  350. data/assets/video/pexels-engin-akyurt-6069112-960x540-30fps.mp4 +0 -0
  351. data/assets/webfonts/fa-brands-400.ttf +0 -0
  352. data/assets/webfonts/fa-brands-400.woff2 +0 -0
  353. data/assets/webfonts/fa-regular-400.ttf +0 -0
  354. data/assets/webfonts/fa-regular-400.woff2 +0 -0
  355. data/assets/webfonts/fa-solid-900.ttf +0 -0
  356. data/assets/webfonts/fa-solid-900.woff2 +0 -0
  357. data/assets/webfonts/fa-v4compatibility.ttf +0 -0
  358. data/assets/webfonts/fa-v4compatibility.woff2 +0 -0
  359. data/docs/Gemfile +5 -0
  360. data/docs/_config.yml +83 -0
  361. data/docs/_documentation/en/01-quick-start.md +115 -0
  362. data/docs/_documentation/en/02-tools.md +146 -0
  363. data/docs/_documentation/en/03-usage.md +107 -0
  364. data/docs/_documentation/en/04-jekyll-basics.md +51 -0
  365. data/docs/_documentation/en/05-pages-collections.md +68 -0
  366. data/docs/_documentation/en/06-github-web-editing.md +92 -0
  367. data/docs/_documentation/en/10-profiles.md +125 -0
  368. data/docs/_documentation/en/11-unaltreselfie.md +39 -0
  369. data/docs/_documentation/en/12-unaltreprojecte.md +38 -0
  370. data/docs/_documentation/en/13-unaltremanual.md +237 -0
  371. data/docs/_documentation/en/14-unaltredocs.md +92 -0
  372. data/docs/_documentation/en/15-profile-checklist.md +39 -0
  373. data/docs/_documentation/en/20-syntax.md +353 -0
  374. data/docs/_documentation/en/21-themes.md +103 -0
  375. data/docs/_documentation/en/22-feature-reference.md +56 -0
  376. data/docs/_documentation/en/23-front-matter.md +51 -0
  377. data/docs/_documentation/en/24-data-files.md +28 -0
  378. data/docs/_documentation/en/30-customization.md +448 -0
  379. data/docs/_documentation/en/31-template.md +55 -0
  380. data/docs/_documentation/en/32-development.md +108 -0
  381. data/docs/_documentation/en/33-reusable-workflows.md +136 -0
  382. data/docs/_documentation/en/40-distribution.md +164 -0
  383. data/docs/_documentation/en/41-bibliometrics.md +137 -0
  384. data/docs/_documentation/en/42-docker-image.md +185 -0
  385. data/docs/_documentation/en/50-faq.md +33 -0
  386. data/docs/_pages/ca/index.md +35 -0
  387. data/docs/_pages/en/index.md +35 -0
  388. data/docs/_pages/es/index.md +35 -0
  389. data/docs/agents/action-prompts/00-start-site-session.txt +10 -0
  390. data/docs/agents/action-prompts/05-create-new-web.txt +5 -0
  391. data/docs/agents/action-prompts/10-content-update.txt +9 -0
  392. data/docs/agents/action-prompts/15-edit-default-content.txt +8 -0
  393. data/docs/agents/action-prompts/20-manual-teaching-materials.txt +18 -0
  394. data/docs/agents/action-prompts/22-manual-style-audit.txt +14 -0
  395. data/docs/agents/action-prompts/23-manual-structure-audit.txt +17 -0
  396. data/docs/agents/action-prompts/25-translation-prepublish.txt +8 -0
  397. data/docs/agents/action-prompts/30-project-site-update.txt +7 -0
  398. data/docs/agents/action-prompts/40-documentation-update.txt +7 -0
  399. data/docs/agents/action-prompts/50-bibliography-entry.txt +7 -0
  400. data/docs/agents/action-prompts/60-bibliometrics-refresh.txt +7 -0
  401. data/docs/agents/action-prompts/70-build-and-review.txt +12 -0
  402. data/docs/agents/manual-authoring-components.md +255 -0
  403. data/docs/agents/mcp-contract.md +162 -0
  404. data/docs/agents/quarto-computation-adoption.md +102 -0
  405. data/docs/assets/img/unaltraweb-docs-hero.svg +36 -0
  406. data/lib/unaltraweb/version.rb +8 -0
  407. data/lib/unaltraweb.rb +31 -0
  408. data/requirements.txt +5 -0
  409. data/scripts/biblio/fetch_scimago_csv.sh +126 -0
  410. data/scripts/biblio/metrics_common.py +154 -0
  411. data/scripts/biblio/metrics_merge_meta.py +73 -0
  412. data/scripts/biblio/metrics_update.py +826 -0
  413. data/scripts/computations/python/Dockerfile +37 -0
  414. data/scripts/computations/r/Dockerfile +31 -0
  415. data/scripts/computations/render.py +1267 -0
  416. data/scripts/cv/render_pdf_preview.sh +36 -0
  417. data/scripts/deploy/publish_branch.sh +80 -0
  418. data/scripts/generate_logo_variants.py +47 -0
  419. data/scripts/manual/Dockerfile +31 -0
  420. data/scripts/manual/build_pdf.py +1780 -0
  421. data/scripts/manual/filters/bibliography.lua +164 -0
  422. data/scripts/manual/filters/code-blocks.lua +102 -0
  423. data/scripts/manual/filters/figure-captions.lua +35 -0
  424. data/scripts/manual/publish_release.sh +281 -0
  425. data/scripts/manual/templates/manual.tex +494 -0
  426. data/scripts/manual/verify_release_assets.py +650 -0
  427. data/scripts/test_gem_build.py +128 -0
  428. data/scripts/test_reproducible_jekyll_build.py +191 -0
  429. data/scripts/test_wheel_install.py +282 -0
  430. data/scripts/unaltraweb-docker-mount.sh +36 -0
  431. data/scripts/unaltraweb-mcp-bootstrap.sh +105 -0
  432. data/scripts/unaltraweb-mcp-cleanup.sh +73 -0
  433. data/scripts/unaltraweb-mcp-project-id.sh +34 -0
  434. data/scripts/validate_distribution.py +563 -0
  435. data/scripts/validate_workflows.py +1285 -0
  436. data/scripts/web_captures/Dockerfile +31 -0
  437. data/scripts/web_captures/capture.mjs +157 -0
  438. data/scripts/web_captures/package-lock.json +56 -0
  439. data/scripts/web_captures/package.json +9 -0
  440. data/scripts/web_captures/render.py +867 -0
  441. data/src/unaltraweb_mcp/component-contract.json +153 -0
  442. data/src/unaltraweb_mcp/component-contract.schema.json +87 -0
  443. data/src/unaltraweb_mcp/docker_mount.py +19 -0
  444. metadata +861 -0
@@ -0,0 +1,95 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+ {% assign year = page.date | date: '%Y' %}
5
+ {% assign tags = page.tags | join: '' %}
6
+ {% assign categories = page.categories | join: '' %}
7
+
8
+ {% capture created_on_text %}{% include t.liquid key='post.created_on' default='Created on' %}{% endcapture %}
9
+ {% capture by_text %}{% include t.liquid key='post.by' default='by' %}{% endcapture %}
10
+ {% capture updated_on_text %}{% include t.liquid key='post.last_updated_on' default='last updated on' %}{% endcapture %}
11
+ {% capture references_text %}{% include t.liquid key='post.references' default='References' %}{% endcapture %}
12
+ {% capture published_date %}{% include localized-date.liquid date=page.date format_key='date.formats.long' default_format='%B %-d, %Y' %}{% endcapture %}
13
+ {% if page.last_updated %}
14
+ {% capture updated_date %}{% include localized-date.liquid date=page.last_updated format_key='date.formats.long' default_format='%B %-d, %Y' %}{% endcapture %}
15
+ {% endif %}
16
+ {% capture reading_time_html %}{% include post-reading-time.liquid item=page content=content %}{% endcapture %}
17
+ {% assign reading_time_html = reading_time_html | strip %}
18
+
19
+ {% if page._styles %}
20
+ <style type="text/css">
21
+ {{ page._styles }}
22
+ </style>
23
+ {% endif %}
24
+
25
+ <div class="post">
26
+ <header class="post-header">
27
+ <h1 class="post-title">{{ page.title }}</h1>
28
+ <p class="post-meta">
29
+ {{ created_on_text | strip }} {{ published_date | strip }}
30
+ {% if page.author %}{{ by_text | strip }} {{ page.author }}{% endif %}
31
+ {% if page.last_updated %}, {{ updated_on_text | strip }} {{ updated_date | strip }}{% endif %}
32
+ {% if reading_time_html != '' %} • {{ reading_time_html }}{% endif %}
33
+ {% if page.meta %}• {{ page.meta }}{% endif %}
34
+ </p>
35
+ <p class="post-tags">
36
+ <i class="fa-solid fa-calendar fa-sm"></i> {{ year }}
37
+
38
+ {% if tags != '' %}
39
+ &nbsp; &middot; &nbsp;
40
+ {% for tag in page.tags %}
41
+ <i class="fa-solid fa-hashtag fa-sm"></i> {{ tag }}
42
+ {% unless forloop.last %}
43
+ &nbsp;
44
+ {% endunless %}
45
+ {% endfor %}
46
+ {% endif %}
47
+
48
+ {% if categories != '' %}
49
+ &nbsp; &middot; &nbsp;
50
+ {% for category in page.categories %}
51
+ <i class="fa-solid fa-tag fa-sm"></i> {{ category }}
52
+ {% unless forloop.last %}
53
+ &nbsp;
54
+ {% endunless %}
55
+ {% endfor %}
56
+ {% endif %}
57
+ </p>
58
+ </header>
59
+
60
+ <article class="post-content">
61
+ {% if page.toc and page.toc.beginning %}
62
+ <div id="table-of-contents">
63
+ {% toc %}
64
+ </div>
65
+ <hr>
66
+ {% endif %}
67
+ <div id="markdown-content">
68
+ {{ content }}
69
+ </div>
70
+ </article>
71
+
72
+ {% if page.citation %}
73
+ {% include citation.liquid %}
74
+ {% endif %}
75
+
76
+ {% if page.related_publications %}
77
+ <h2>{{ references_text | strip }}</h2>
78
+ <div class="publications">
79
+ {% bibliography --group_by none --cited %}
80
+ </div>
81
+ {% endif %}
82
+
83
+ {% if site.related_blog_posts and site.related_blog_posts.enabled %}
84
+ {% if page.related_posts == null or page.related_posts %}
85
+ {% include related_posts.liquid %}
86
+ {% endif %}
87
+ {% endif %}
88
+
89
+ {% if site.disqus_shortname and page.disqus_comments %}
90
+ {% include disqus.liquid %}
91
+ {% endif %}
92
+ {% if site.giscus and page.giscus_comments %}
93
+ {% include giscus.liquid %}
94
+ {% endif %}
95
+ </div>
@@ -0,0 +1,36 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+
5
+ <div class="post profile-page">
6
+ {% assign profile_author = page.author | default: site.author %}
7
+ {% assign current_lang = page.lang | default: site.default_lang | default: site.lang %}
8
+ {% assign profile_author_name_key = 'name_' | append: current_lang %}
9
+ {% assign profile_author_name = profile_author[profile_author_name_key] | default: profile_author.name | default: '' %}
10
+ {% assign show_profile_title = true %}
11
+ {% if page.hide_title or page.title == profile_author_name %}
12
+ {% assign show_profile_title = false %}
13
+ {% endif %}
14
+
15
+ {% if show_profile_title or page.description %}
16
+ <header class="post-header">
17
+ {% if show_profile_title %}<h1 class="post-title">{{ page.title }}</h1>{% endif %}
18
+ {% if page.description %}<p class="post-description">{{ page.description }}</p>{% endif %}
19
+ </header>
20
+ {% endif %}
21
+
22
+ {% if profile_author %}
23
+ <div class="profile-page-grid">
24
+ <div class="profile-page-sidebar">
25
+ {% include profile-card.liquid author=profile_author %}
26
+ </div>
27
+ <article class="profile-page-content">
28
+ {{ content }}
29
+ </article>
30
+ </div>
31
+ {% else %}
32
+ <article class="profile-page-content">
33
+ {{ content }}
34
+ </article>
35
+ {% endif %}
36
+ </div>
@@ -0,0 +1,36 @@
1
+ ---
2
+ layout: page
3
+ ---
4
+ <div class="post">
5
+ <article>
6
+ {% if page.profiles %}
7
+ {% for profile in page.profiles %}
8
+ <hr>
9
+ <div class="profile float-{% if profile.align == 'left' %}left{% else %}right{% endif %}">
10
+ {% if profile.image %}
11
+ {% assign profile_image_path = profile.image | prepend: 'assets/img/' %}
12
+ {% if profile.image_circular %}
13
+ {% assign profile_image_class = 'img-fluid z-depth-1 rounded-circle' %}
14
+ {% else %}
15
+ {% assign profile_image_class = 'img-fluid z-depth-1 rounded' %}
16
+ {% endif %}
17
+ {% capture sizes %}(min-width: {{ site.max_width }}) {{ site.max_width | minus: 30 | times: 0.3}}px, (min-width: 576px) 30vw, 95vw"{% endcapture %}
18
+ {% include figure.liquid loading="eager" path=profile_image_path class=profile_image_class sizes=sizes alt=profile.image %}
19
+ {% endif %}
20
+ {% if profile.more_info %}
21
+ <div class="more-info">{{ profile.more_info }}</div>
22
+ {% endif %}
23
+ </div>
24
+
25
+ <div class="clearfix">
26
+ {% if profile.content %}
27
+ {% capture profile_content %}{% include_relative {{ profile.content }} %}{% endcapture %}
28
+ {{ profile_content | markdownify }}
29
+ {% else %}
30
+ {{ content }}
31
+ {% endif %}
32
+ </div>
33
+ {% endfor %}
34
+ {% endif %}
35
+ </article>
36
+ </div>
@@ -0,0 +1,7 @@
1
+ ---
2
+ layout: page
3
+ ---
4
+
5
+ {% include project-resources.liquid project=page variant='page' %}
6
+
7
+ {{ content }}
@@ -0,0 +1,58 @@
1
+ ---
2
+ layout: page
3
+ ---
4
+ {{ content }}
5
+
6
+ {% capture github_users_text %}{% include t.liquid key='repositories.github_users' default='GitHub users' %}{% endcapture %}
7
+ {% capture github_repositories_text %}{% include t.liquid key='repositories.github_repositories' default='GitHub repositories' %}{% endcapture %}
8
+ {% capture empty_repositories_text %}{% include t.liquid key='repositories.empty' default='Add GitHub users or repositories in `_data/repositories.yml` to populate this section.' %}{% endcapture %}
9
+
10
+ {% assign has_github_users = false %}
11
+ {% if site.data.repositories.github_users and site.data.repositories.github_users.size > 0 %}
12
+ {% assign has_github_users = true %}
13
+ {% endif %}
14
+
15
+ {% assign has_github_repos = false %}
16
+ {% if site.data.repositories.github_repos and site.data.repositories.github_repos.size > 0 %}
17
+ {% assign has_github_repos = true %}
18
+ {% endif %}
19
+
20
+ {% if has_github_users %}
21
+ <h2>{{ github_users_text | strip }}</h2>
22
+
23
+ <div class="repositories d-flex flex-wrap flex-md-row flex-column justify-content-between align-items-center">
24
+ {% for user in site.data.repositories.github_users %}
25
+ {% include repository/repo_user.liquid username=user %}
26
+ {% endfor %}
27
+ </div>
28
+
29
+ {% if site.repo_trophies.enabled %}
30
+ <hr>
31
+ {% for user in site.data.repositories.github_users %}
32
+ {% if site.data.repositories.github_users.size > 1 %}
33
+ <h3>{{ user }}</h3>
34
+ {% endif %}
35
+ <div class="repositories d-flex flex-wrap flex-md-row flex-column justify-content-between align-items-center">
36
+ {% include repository/repo_trophies.liquid username=user %}
37
+ </div>
38
+
39
+ {% unless forloop.last %}
40
+ <hr>
41
+ {% endunless %}
42
+ {% endfor %}
43
+ {% endif %}
44
+ {% endif %}
45
+
46
+ {% if has_github_repos %}
47
+ <h2>{{ github_repositories_text | strip }}</h2>
48
+
49
+ <div class="repositories d-flex flex-wrap flex-md-row flex-column justify-content-between align-items-center">
50
+ {% for repo in site.data.repositories.github_repos %}
51
+ {% include repository/repo.liquid repository=repo %}
52
+ {% endfor %}
53
+ </div>
54
+ {% endif %}
55
+
56
+ {% unless has_github_users or has_github_repos %}
57
+ <p>{{ empty_repositories_text | strip }}</p>
58
+ {% endunless %}
@@ -0,0 +1,126 @@
1
+ ---
2
+ layout: page
3
+ ---
4
+
5
+ {%- assign current_lang = page.lang | default: site.default_lang | default: site.lang -%}
6
+ {%- assign theses_for_lang = site.theses | where: "lang", current_lang -%}
7
+ {%- assign theses_without_lang = site.theses | where_exp: "thesis", "thesis.lang == nil" -%}
8
+ {%- if theses_for_lang and theses_for_lang.size > 0 -%}
9
+ {%- assign theses_to_show = theses_for_lang | concat: theses_without_lang -%}
10
+ {%- else -%}
11
+ {%- assign theses_to_show = theses_without_lang -%}
12
+ {%- endif -%}
13
+ {%- assign theses_sorted = theses_to_show | sort: "order" -%}
14
+
15
+ {% capture empty_text %}{% include t.liquid key='theses.empty' default='No theses found yet.' %}{% endcapture %}
16
+ {% capture student_text %}{% include t.liquid key='theses.student' default='Student' %}{% endcapture %}
17
+ {% capture status_text %}{% include t.liquid key='theses.status' default='Status' %}{% endcapture %}
18
+ {% capture institution_text %}{% include t.liquid key='theses.institution' default='Institution' %}{% endcapture %}
19
+ {% capture year_text %}{% include t.liquid key='theses.year' default='Year' %}{% endcapture %}
20
+ {% capture supervisors_text %}{% include t.liquid key='theses.supervisors' default='Supervisors' %}{% endcapture %}
21
+ {% capture image_source_text %}{% include t.liquid key='theses.image_source' default='Source' %}{% endcapture %}
22
+
23
+ {{ content }}
24
+
25
+ {%- if theses_sorted == nil or theses_sorted.size == 0 -%}
26
+ <p>{{ empty_text | strip }}</p>
27
+ {%- endif -%}
28
+
29
+ {%- for t in theses_sorted -%}
30
+ <hr>
31
+
32
+ {%- assign title_key = "title_" | append: current_lang -%}
33
+ {%- assign summary_key = "summary_" | append: current_lang -%}
34
+ {%- assign institution_key = "institution_" | append: current_lang -%}
35
+ {%- assign more_info_key = "more_info_" | append: current_lang -%}
36
+ {%- assign status_key = "status_" | append: current_lang -%}
37
+ {%- assign image_alt_key = "image_alt_" | append: current_lang -%}
38
+ {%- assign image_caption_key = "image_caption_" | append: current_lang -%}
39
+ {%- assign image_source_key = "image_source_" | append: current_lang -%}
40
+
41
+ {%- assign localized_title = t[title_key] | default: t.title -%}
42
+ {%- assign localized_institution = t[institution_key] | default: t.institution -%}
43
+ {%- assign localized_more_info = t[more_info_key] | default: t.more_info -%}
44
+ {%- assign localized_status_raw = t[status_key] | default: t.status -%}
45
+ {%- assign localized_image_alt = t[image_alt_key] | default: t.image_alt | default: t.student -%}
46
+ {%- assign localized_image_caption = t[image_caption_key] | default: t.image_caption -%}
47
+ {%- assign localized_image_source = t[image_source_key] | default: t.image_source -%}
48
+ {%- assign image_source_url = t.image_source_url -%}
49
+ {%- assign image_source_domain = nil -%}
50
+ {%- if image_source_url -%}
51
+ {%- assign image_source_domain = image_source_url | replace: 'https://', '' | replace: 'http://', '' | split: '/' | first -%}
52
+ {%- endif -%}
53
+
54
+ {%- assign status_i18n_key = "theses.status_values." | append: localized_status_raw -%}
55
+ {% capture localized_status %}{% include t.liquid key=status_i18n_key default=localized_status_raw %}{% endcapture %}
56
+
57
+ {%- assign align = t.align | default: "right" -%}
58
+ <div class="profile float-{% if align == 'left' %}left{% else %}right{% endif %}">
59
+
60
+ {%- if t.image -%}
61
+ {%- if t.image contains '://' -%}
62
+ {%- assign profile_image_path = t.image -%}
63
+ {%- else -%}
64
+ {%- assign profile_image_path = t.image | prepend: 'assets/img/' -%}
65
+ {%- endif -%}
66
+
67
+ {%- if t.image_circular -%}
68
+ {%- assign profile_image_class = 'img-fluid z-depth-1 rounded-circle' -%}
69
+ {%- else -%}
70
+ {%- assign profile_image_class = 'img-fluid z-depth-1 rounded' -%}
71
+ {%- endif -%}
72
+
73
+ {%- capture sizes -%}
74
+ (min-width: {{ site.max_width }}) {{ site.max_width | minus: 30 | times: 0.3 }}px,
75
+ (min-width: 576px) 30vw,
76
+ 95vw
77
+ {%- endcapture -%}
78
+
79
+ {%- capture thesis_image_caption -%}
80
+ {%- if localized_image_caption -%}
81
+ {{ localized_image_caption }}
82
+ {%- endif -%}
83
+ {%- if localized_image_source or image_source_url -%}
84
+ {%- if localized_image_caption -%} {% endif -%}
85
+ <span class="thesis-image-source">
86
+ {{ image_source_text | strip }}:
87
+ {%- if image_source_url -%}
88
+ <a href="{{ image_source_url }}" target="_blank" rel="noopener noreferrer">
89
+ {{ localized_image_source | default: image_source_domain }}
90
+ </a>
91
+ {%- else -%}
92
+ {{ localized_image_source }}
93
+ {%- endif -%}
94
+ </span>
95
+ {%- endif -%}
96
+ {%- endcapture -%}
97
+
98
+ {%- if t.image contains '://' -%}
99
+ {% include figure.liquid url=profile_image_path class=profile_image_class sizes=sizes alt=localized_image_alt caption=thesis_image_caption loading="lazy" avoid_scaling=true %}
100
+ {%- else -%}
101
+ {% include figure.liquid path=profile_image_path class=profile_image_class sizes=sizes alt=localized_image_alt caption=thesis_image_caption loading="lazy" %}
102
+ {%- endif -%}
103
+ {%- endif -%}
104
+
105
+ {%- if localized_more_info -%}
106
+ <div class="more-info">{{ localized_more_info }}</div>
107
+ {%- endif -%}
108
+ </div>
109
+
110
+ <div class="clearfix">
111
+ {%- if localized_title -%}<h2>{{ localized_title }}</h2>{%- endif -%}
112
+ {%- if t.student -%}<p><strong>{{ student_text | strip }}:</strong> {{ t.student }}</p>{%- endif -%}
113
+ {%- if localized_status_raw -%}<p><strong>{{ status_text | strip }}:</strong> {{ localized_status | strip }}</p>{%- endif -%}
114
+ {%- if localized_institution -%}<p><strong>{{ institution_text | strip }}:</strong> {{ localized_institution }}</p>{%- endif -%}
115
+ {%- if t.year -%}<p><strong>{{ year_text | strip }}:</strong> {{ t.year }}</p>{%- endif -%}
116
+ {%- if t.supervisors -%}<p><strong>{{ supervisors_text | strip }}:</strong> {{ t.supervisors }}</p>{%- endif -%}
117
+
118
+ {%- if t[summary_key] -%}
119
+ {{ t[summary_key] | markdownify }}
120
+ {%- elsif t.summary -%}
121
+ {{ t.summary | markdownify }}
122
+ {%- else -%}
123
+ {{ t.content }}
124
+ {%- endif -%}
125
+ </div>
126
+ {%- endfor -%}
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+ require "jekyll/scholar"
5
+
6
+ module Unaltraweb
7
+ module BibliographyProfiles
8
+ PROFILE_SORTS = {
9
+ "unaltreselfie" => {
10
+ "sort_by" => %w[year month name title],
11
+ "order" => %w[descending descending ascending ascending],
12
+ "group_by" => "year",
13
+ "group_order" => "descending"
14
+ },
15
+ "unaltreprojecte" => {
16
+ "sort_by" => %w[year month name title],
17
+ "order" => %w[descending descending ascending ascending],
18
+ "group_by" => "year",
19
+ "group_order" => "descending"
20
+ },
21
+ "unaltremanual" => {
22
+ "sort_by" => %w[name year title],
23
+ "order" => %w[ascending ascending ascending],
24
+ "group_by" => "none",
25
+ "group_order" => "ascending",
26
+ "query" => "@*",
27
+ "source" => "_bibliography"
28
+ }
29
+ }.freeze
30
+
31
+ module_function
32
+
33
+ def configure(site)
34
+ profile = site.config.dig("unaltraweb", "site_profile").to_s
35
+ settings = PROFILE_SORTS[profile]
36
+ return unless settings
37
+
38
+ site.config["scholar"] ||= {}
39
+ site.config["scholar"].merge!(settings)
40
+ if profile == "unaltremanual"
41
+ manual = site.config.dig("unaltraweb", "manual") || {}
42
+ manual = {} unless manual.is_a?(Hash)
43
+ bibliography = (manual["bibliography_file"] || "manual.bib").to_s.strip
44
+ unless bibliography.match?(/\A[A-Za-z0-9][A-Za-z0-9_.-]*\.bib\z/) && File.basename(bibliography) == bibliography
45
+ raise Jekyll::Errors::FatalException, "unaltraweb.manual.bibliography_file must be a .bib filename under _bibliography/"
46
+ end
47
+ validate_bibliography_location(site, bibliography)
48
+ site.config["scholar"]["bibliography"] = bibliography
49
+ end
50
+ end
51
+
52
+ def validate_bibliography_location(site, bibliography)
53
+ return unless site.respond_to?(:source) && site.source
54
+
55
+ source = File.realpath(site.source)
56
+ root = File.join(source, "_bibliography")
57
+ return unless File.exist?(root) || File.symlink?(root)
58
+
59
+ resolved_root = File.realpath(root)
60
+ target = File.join(root, bibliography)
61
+ resolved_target = if File.exist?(target) || File.symlink?(target)
62
+ File.realpath(target)
63
+ else
64
+ target
65
+ end
66
+ root_prefix = resolved_root.end_with?(File::SEPARATOR) ? resolved_root : "#{resolved_root}#{File::SEPARATOR}"
67
+ source_prefix = source.end_with?(File::SEPARATOR) ? source : "#{source}#{File::SEPARATOR}"
68
+ confined = (resolved_root == source || resolved_root.start_with?(source_prefix)) && resolved_target.start_with?(root_prefix)
69
+ return if confined
70
+
71
+ raise Jekyll::Errors::FatalException, "unaltraweb.manual.bibliography_file must remain under the site's _bibliography/ directory"
72
+ rescue Errno::ENOENT, Errno::ELOOP
73
+ raise Jekyll::Errors::FatalException, "unaltraweb.manual.bibliography_file must resolve safely under _bibliography/"
74
+ end
75
+
76
+ def strip_web_access(reference, doi = nil, *urls)
77
+ rendered = reference.to_s.dup
78
+ variants = access_variants(doi, *urls).sort_by { |value| -value.length }
79
+ loop do
80
+ previous = rendered.dup
81
+ variants.each do |value|
82
+ escaped = Regexp.escape(value)
83
+ rendered.sub!(%r{\s*(?:doi:\s*)?(?:<a\b[^>]*>\s*)?#{escaped}(?:\s*</a>)?\s*\.?(?=(?:\s*</[a-z][^>]*>)*\s*\z)}i, "")
84
+ end
85
+ break if rendered == previous
86
+ end
87
+ rendered.rstrip
88
+ end
89
+
90
+ def access_variants(doi, *urls)
91
+ values = []
92
+ normalized_doi = doi.to_s.strip.sub(%r{\Ahttps?://(?:dx\.)?doi\.org/}i, "").sub(/\Adoi:\s*/i, "")
93
+ unless normalized_doi.empty?
94
+ values.concat([
95
+ "https://doi.org/#{normalized_doi}",
96
+ "http://doi.org/#{normalized_doi}",
97
+ "http://dx.doi.org/#{normalized_doi}",
98
+ "doi:#{normalized_doi}",
99
+ normalized_doi
100
+ ])
101
+ end
102
+ urls.each { |url| values << url.to_s.strip unless url.to_s.strip.empty? }
103
+ values.flat_map { |value| [value, CGI.escapeHTML(value)] }.uniq
104
+ end
105
+
106
+ def strip_reference_ids(reference)
107
+ reference.to_s.gsub(/\s+id=(['"])[^'"]*\1/i, "")
108
+ end
109
+
110
+ def escape_liquid_markers(value)
111
+ value.to_s.gsub("{{", "<span>{</span>{").gsub("{%", "<span>{</span>%")
112
+ end
113
+
114
+ def fold_sort_value(value)
115
+ BibTeX::Value.new(value.to_s).convert(:latex).to_s.unicode_normalize(:nfkd).gsub(/\p{Mn}/, "")
116
+ end
117
+ end
118
+
119
+ module ScholarProfileRendering
120
+ def resolve_sort_value(entry, key)
121
+ value = super
122
+ return value unless key == "name" && unaltraweb_profile_sorting?
123
+
124
+ BibTeX::Value.new(BibliographyProfiles.fold_sort_value(value))
125
+ end
126
+
127
+ def render_bibliography(entry, index = nil)
128
+ citation_numbers = context && context["citation_numbers"]
129
+ if index && unaltraweb_profile_sorting? && citation_numbers&.key?(entry.key) && unaltraweb_numeric_bibliography_style?
130
+ index = citation_numbers[entry.key]
131
+ end
132
+ super(entry, index)
133
+ end
134
+
135
+ private
136
+
137
+ def unaltraweb_profile_sorting?
138
+ BibliographyProfiles::PROFILE_SORTS.key?(site&.config&.dig("unaltraweb", "site_profile").to_s)
139
+ end
140
+
141
+ def unaltraweb_numeric_bibliography_style?
142
+ @unaltraweb_numeric_bibliography_styles ||= {}
143
+ @unaltraweb_numeric_bibliography_styles[style] ||= styles(style).to_xml.include?('variable="citation-number"')
144
+ end
145
+ end
146
+
147
+ module BibliographyProfileFilters
148
+ def stripBibliographyAccess(input, doi = nil, *urls)
149
+ BibliographyProfiles.strip_web_access(input, doi, *urls)
150
+ end
151
+
152
+ def stripBibliographyIds(input)
153
+ BibliographyProfiles.strip_reference_ids(input)
154
+ end
155
+
156
+ def escapeLiquidMarkers(input)
157
+ BibliographyProfiles.escape_liquid_markers(input)
158
+ end
159
+ end
160
+ end
161
+
162
+ Jekyll::Scholar::Utilities.prepend(Unaltraweb::ScholarProfileRendering) unless Jekyll::Scholar::Utilities.ancestors.include?(Unaltraweb::ScholarProfileRendering)
163
+ Liquid::Template.register_filter(Unaltraweb::BibliographyProfileFilters)
164
+ Jekyll::Hooks.register(:site, :post_read) { |site| Unaltraweb::BibliographyProfiles.configure(site) }
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module Unaltraweb
6
+ module Callouts
7
+ module_function
8
+
9
+ TYPES = %w[info example warning objectives danger].freeze
10
+ DEFAULT_LABELS = {
11
+ "ca" => {
12
+ "info" => "NOTA",
13
+ "example" => "EXEMPLE",
14
+ "warning" => "ADVERTÈNCIA",
15
+ "objectives" => "OBJECTIUS D'APRENENTATGE",
16
+ "danger" => "ATENCIÓ"
17
+ },
18
+ "es" => {
19
+ "info" => "NOTA",
20
+ "example" => "EJEMPLO",
21
+ "warning" => "ADVERTENCIA",
22
+ "objectives" => "OBJETIVOS DE APRENDIZAJE",
23
+ "danger" => "ATENCIÓN"
24
+ },
25
+ "en" => {
26
+ "info" => "NOTE",
27
+ "example" => "EXAMPLE",
28
+ "warning" => "WARNING",
29
+ "objectives" => "LEARNING OBJECTIVES",
30
+ "danger" => "CAUTION"
31
+ }
32
+ }.freeze
33
+
34
+ def transform_html(html, site:, lang:)
35
+ source = html.to_s
36
+ return source unless source.match?(/<blockquote(?:\s|>)/i)
37
+
38
+ fragment = Nokogiri::HTML::DocumentFragment.parse(source)
39
+ labels = labels_for(site, lang)
40
+
41
+ fragment.css("blockquote").each do |blockquote|
42
+ if blockquote.element_children.any? { |child| child.name == "blockquote" }
43
+ add_classes(blockquote, "uw-callout-wrapper")
44
+ next
45
+ end
46
+
47
+ depth = blockquote.ancestors("blockquote").length
48
+ next if depth.zero?
49
+
50
+ type = TYPES[[depth, TYPES.length].min - 1]
51
+ add_classes(blockquote, "uw-callout", "uw-callout-#{type}")
52
+ blockquote["data-callout"] = type
53
+ add_title(blockquote, labels.fetch(type))
54
+ end
55
+
56
+ fragment.to_html
57
+ end
58
+
59
+ def detect_lang(doc)
60
+ configured = doc.data["lang"].to_s
61
+ return normalize_lang(configured) unless configured.empty?
62
+
63
+ relative_path = doc.respond_to?(:relative_path) ? doc.relative_path.to_s : ""
64
+ path_lang = relative_path.tr("\\", "/").split("/")[1].to_s
65
+ fallback = doc.site.config["default_lang"] || doc.site.config["lang"] || "en"
66
+ normalize_lang(path_lang.empty? ? fallback : path_lang)
67
+ end
68
+
69
+ def labels_for(site, lang)
70
+ normalized_lang = normalize_lang(lang)
71
+ default_lang = normalize_lang(site.config["default_lang"] || site.config["lang"] || "en")
72
+ i18n = site.data["i18n"] || {}
73
+ configured = i18n.dig(normalized_lang, "callouts") || i18n.dig(default_lang, "callouts") || {}
74
+ defaults = DEFAULT_LABELS[normalized_lang] || DEFAULT_LABELS[default_lang] || DEFAULT_LABELS.fetch("en")
75
+
76
+ defaults.merge(configured.transform_keys(&:to_s).transform_values(&:to_s))
77
+ end
78
+
79
+ def normalize_lang(lang)
80
+ lang.to_s.downcase.split("-").first.to_s.then { |value| value.empty? ? "en" : value }
81
+ end
82
+
83
+ def add_classes(node, *classes)
84
+ current = node["class"].to_s.split
85
+ node["class"] = (current + classes).uniq.join(" ")
86
+ end
87
+
88
+ def add_title(blockquote, label)
89
+ existing = blockquote.element_children.first
90
+ return if existing && existing["class"].to_s.split.include?("uw-callout-title")
91
+
92
+ title = Nokogiri::XML::Node.new("p", blockquote.document)
93
+ title["class"] = "uw-callout-title"
94
+ title["data-unaltraweb-callout-title"] = "true"
95
+ title.content = label
96
+ blockquote.prepend_child(title)
97
+ end
98
+ end
99
+ end
100
+
101
+ Jekyll::Hooks.register %i[documents pages], :post_convert do |doc|
102
+ lang = Unaltraweb::Callouts.detect_lang(doc)
103
+ doc.content = Unaltraweb::Callouts.transform_html(doc.content, site: doc.site, lang: lang)
104
+ end