pandorga 1.2.1
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 +7 -0
- data/CHANGELOG.md +77 -0
- data/DESIGN.md +1434 -0
- data/LICENSE +21 -0
- data/README.md +81 -0
- data/_data/themes/dark.yml +97 -0
- data/_data/themes/light.yml +120 -0
- data/_data/themes/shared.yml +37 -0
- data/_data/translations.yml +85 -0
- data/_includes/chord-tab.html +38 -0
- data/_includes/content-runtime/00-config.html +22 -0
- data/_includes/content-runtime/10-utils.html +176 -0
- data/_includes/content-runtime/20-markdown.html +264 -0
- data/_includes/content-runtime/25-code-blocks.html +315 -0
- data/_includes/content-runtime/30-includes-bib.html +190 -0
- data/_includes/content-runtime/30-includes-xcite.html +122 -0
- data/_includes/content-runtime/30-includes.html +365 -0
- data/_includes/content-runtime/40-listing.html +225 -0
- data/_includes/content-runtime/41-listing-helpers.html +71 -0
- data/_includes/content-runtime/42-listing-filter.html +208 -0
- data/_includes/content-runtime/43-blocks.html +379 -0
- data/_includes/content-runtime/50-detail.html +861 -0
- data/_includes/content-runtime/55-toc.html +176 -0
- data/_includes/content-runtime/60-fragments.html +567 -0
- data/_includes/content-runtime/90-bootstrap.html +76 -0
- data/_includes/cut-in.html +10 -0
- data/_includes/cv/cv-box.html +18 -0
- data/_includes/dict-card.html +24 -0
- data/_includes/figure.html +36 -0
- data/_includes/home/band-index.html +43 -0
- data/_includes/home/band-listing.html +49 -0
- data/_includes/mathjax.html +10 -0
- data/_includes/ongoing-product.html +1 -0
- data/_includes/page/content-runtime.html +20 -0
- data/_includes/page/date_evolution.html +19 -0
- data/_includes/page/figure-lightbox.html +102 -0
- data/_includes/page/formatted_date.html +15 -0
- data/_includes/page/hero-default.html +18 -0
- data/_includes/page/listing-runtime-attrs.html +8 -0
- data/_includes/page/mermaid.html +101 -0
- data/_includes/page/network-graph.html +1253 -0
- data/_includes/page/page-header.html +41 -0
- data/_includes/page/professional-connections.html +2 -0
- data/_includes/page/site-footer.html +101 -0
- data/_includes/page/timeline-section.html +868 -0
- data/_includes/plotly.html +22 -0
- data/_includes/theme/dev-reexport.html +100 -0
- data/_includes/theme/favicons.html +4 -0
- data/_includes/theme/font-loader.html +55 -0
- data/_includes/theme/shell-theme-boot.html +115 -0
- data/_includes/theme/site-logo.html +6 -0
- data/_includes/theme/tailwind-theme-colors.json.liquid +65 -0
- data/_includes/theme/tailwind-theme-fonts.json.liquid +6 -0
- data/_includes/theme/theme-context.liquid +20 -0
- data/_includes/theme/theme-toggle.html +94 -0
- data/_includes/theme/theme-vars-block.liquid +161 -0
- data/_includes/theme/theme-vars.html +16 -0
- data/_includes/transcription.html +80 -0
- data/_includes/tweet.html +22 -0
- data/_includes/video.html +11 -0
- data/_includes/xcite.html +3 -0
- data/_includes/youtube-short.html +26 -0
- data/_includes/youtube.html +12 -0
- data/_layouts/article.html +27 -0
- data/_layouts/articles.html +534 -0
- data/_layouts/bibliography.html +417 -0
- data/_layouts/blog.html +544 -0
- data/_layouts/cv.html +393 -0
- data/_layouts/home.html +319 -0
- data/_layouts/network.html +9 -0
- data/_layouts/pandorga/articles.html +4 -0
- data/_layouts/pandorga/bibliography.html +4 -0
- data/_layouts/pandorga/blog.html +4 -0
- data/_layouts/pandorga/cv.html +4 -0
- data/_layouts/pandorga/media.html +4 -0
- data/_layouts/pandorga/network.html +4 -0
- data/_layouts/pandorga/portfolio.html +4 -0
- data/_layouts/post.html +7 -0
- data/_layouts/product.html +5 -0
- data/_layouts/project.html +27 -0
- data/_layouts/projects.html +534 -0
- data/_layouts/resources.html +387 -0
- data/_layouts/shell.html +203 -0
- data/_layouts/text.html +246 -0
- data/_plugins/bibliography_artifact.rb +57 -0
- data/_plugins/content_backend.rb +37 -0
- data/_plugins/content_collections.rb +51 -0
- data/_plugins/content_detail_paths.rb +58 -0
- data/_plugins/content_headers.rb +73 -0
- data/_plugins/content_keys.rb +310 -0
- data/_plugins/content_local_serve.rb +168 -0
- data/_plugins/content_site_adapter.rb +159 -0
- data/_plugins/content_validators_hook.rb +20 -0
- data/_plugins/content_writing_index.rb +52 -0
- data/_plugins/content_writing_paths.rb +55 -0
- data/_plugins/force_utf8_encoding.rb +13 -0
- data/_plugins/git_chronology.rb +211 -0
- data/_plugins/git_metadata.rb +43 -0
- data/_plugins/job_sections.rb +25 -0
- data/_plugins/lib/content_validators.rb +27 -0
- data/_plugins/lib/resource_validator.rb +56 -0
- data/_plugins/lib/tag_validator.rb +153 -0
- data/_plugins/lib/text_excerpt.rb +56 -0
- data/_plugins/lib/writing_entry.rb +107 -0
- data/_plugins/local_detail_rewrites.rb +194 -0
- data/_plugins/timeline.rb +507 -0
- data/_plugins/writing_slug_limits.rb +70 -0
- data/assets/css/article.css +1330 -0
- data/assets/css/base.css +1432 -0
- data/assets/css/cv.css +13 -0
- data/assets/css/global-link.css +94 -0
- data/assets/css/ledger/bibliography.css +307 -0
- data/assets/css/ledger/chrome.css +118 -0
- data/assets/css/ledger/cv.css +836 -0
- data/assets/css/ledger/detail.css +1270 -0
- data/assets/css/ledger/footer.css +298 -0
- data/assets/css/ledger/home.css +1490 -0
- data/assets/css/ledger/network.css +106 -0
- data/assets/css/ledger/projects.css +716 -0
- data/assets/css/ledger/rascunhos.css +379 -0
- data/assets/css/ledger/sources.css +327 -0
- data/assets/css/ledger/writing.css +754 -0
- data/assets/tailwind-play.js +15 -0
- data/examples/full/Gemfile +7 -0
- data/examples/full/_config.yml +92 -0
- data/examples/full/_redirects +3 -0
- data/examples/full/content/collections/articles/2026-01-01-welcome.md +9 -0
- data/examples/full/content/collections/data/contacts.yml +3 -0
- data/examples/full/content/collections/data/education.yml +4 -0
- data/examples/full/content/collections/jobs/research-fellow.md +12 -0
- data/examples/full/content/collections/posts/2026-01-02-note.md +9 -0
- data/examples/full/content/collections/products/ledger-tool.md +6 -0
- data/examples/full/content/collections/projects/demo-kit.md +10 -0
- data/examples/full/content/collections/resources/style-guide.md +7 -0
- data/examples/full/content/pages/cv/summary.md +1 -0
- data/examples/full/content/pages/headers.yml +20 -0
- data/examples/full/index.html +4 -0
- data/examples/minimal/Gemfile +7 -0
- data/examples/minimal/_config.yml +47 -0
- data/examples/minimal/_redirects +3 -0
- data/examples/minimal/content/collections/articles/2026-01-01-hello.md +10 -0
- data/examples/minimal/content/collections/articles/2026-01-02-second.md +9 -0
- data/examples/minimal/content/pages/headers.yml +6 -0
- data/examples/minimal/index.html +4 -0
- data/exe/pandorga +7 -0
- data/functions/api/studio/[[path]].js +562 -0
- data/functions/api/studio/_lib/auth.js +131 -0
- data/functions/api/studio/_lib/blocks.js +156 -0
- data/functions/api/studio/_lib/gemini.js +62 -0
- data/functions/api/studio/_lib/github.js +278 -0
- data/functions/api/studio/_lib/guidelines.js +143 -0
- data/functions/api/studio/_lib/paths.js +76 -0
- data/functions/api/studio/_lib/prompts.js +165 -0
- data/functions/api/studio/_lib/rate-limit.js +40 -0
- data/functions/api/studio/_lib/rewrite.js +136 -0
- data/functions/api/studio/_lib/sanitize.js +143 -0
- data/lib/pandorga/cli.rb +66 -0
- data/lib/pandorga/commands/doctor.rb +66 -0
- data/lib/pandorga/commands/export.rb +30 -0
- data/lib/pandorga/commands/install_functions.rb +41 -0
- data/lib/pandorga/commands/new.rb +113 -0
- data/lib/pandorga/commands/publish.rb +53 -0
- data/lib/pandorga/commands/serve.rb +34 -0
- data/lib/pandorga/commands/studio_schema.rb +309 -0
- data/lib/pandorga/jekyll/generator.rb +74 -0
- data/lib/pandorga/jekyll/plugins/bibliography_artifact.rb +57 -0
- data/lib/pandorga/jekyll/plugins/content_backend.rb +37 -0
- data/lib/pandorga/jekyll/plugins/content_collections.rb +51 -0
- data/lib/pandorga/jekyll/plugins/content_detail_paths.rb +58 -0
- data/lib/pandorga/jekyll/plugins/content_headers.rb +73 -0
- data/lib/pandorga/jekyll/plugins/content_keys.rb +310 -0
- data/lib/pandorga/jekyll/plugins/content_local_serve.rb +168 -0
- data/lib/pandorga/jekyll/plugins/content_site_adapter.rb +159 -0
- data/lib/pandorga/jekyll/plugins/content_validators_hook.rb +20 -0
- data/lib/pandorga/jekyll/plugins/content_writing_index.rb +52 -0
- data/lib/pandorga/jekyll/plugins/content_writing_paths.rb +55 -0
- data/lib/pandorga/jekyll/plugins/force_utf8_encoding.rb +13 -0
- data/lib/pandorga/jekyll/plugins/git_chronology.rb +211 -0
- data/lib/pandorga/jekyll/plugins/git_metadata.rb +43 -0
- data/lib/pandorga/jekyll/plugins/job_sections.rb +25 -0
- data/lib/pandorga/jekyll/plugins/lib/content_validators.rb +27 -0
- data/lib/pandorga/jekyll/plugins/lib/resource_validator.rb +56 -0
- data/lib/pandorga/jekyll/plugins/lib/tag_validator.rb +153 -0
- data/lib/pandorga/jekyll/plugins/lib/text_excerpt.rb +56 -0
- data/lib/pandorga/jekyll/plugins/lib/writing_entry.rb +107 -0
- data/lib/pandorga/jekyll/plugins/local_detail_rewrites.rb +194 -0
- data/lib/pandorga/jekyll/plugins/timeline.rb +507 -0
- data/lib/pandorga/jekyll/plugins/writing_slug_limits.rb +70 -0
- data/lib/pandorga/jekyll.rb +15 -0
- data/lib/pandorga/object_store_env.rb +51 -0
- data/lib/pandorga/registry/schema.yml +115 -0
- data/lib/pandorga/registry.rb +279 -0
- data/lib/pandorga/version.rb +5 -0
- data/lib/pandorga.rb +19 -0
- data/scripts/cdn-allowlist.json +19 -0
- data/scripts/content/backfill-content-keys.rb +99 -0
- data/scripts/content/band_art.rb +362 -0
- data/scripts/content/export-content-json.rb +741 -0
- data/scripts/content/flatten-ansi-capture.mjs +153 -0
- data/scripts/content/hero_portraits.rb +28 -0
- data/scripts/content/publish-content-to-object-store.sh +78 -0
- data/scripts/content/publish-content-to-r2.sh +5 -0
- data/scripts/content/sync-content-json-to-r2.rb +167 -0
- data/scripts/content/sync-content-redirects.rb +12 -0
- data/scripts/generate-configuration-doc.rb +127 -0
- data/scripts/lib/content_license.rb +50 -0
- data/scripts/lib/content_storage_config.rb +70 -0
- data/scripts/lib/normalize-display-math.mjs +52 -0
- data/scripts/lib/r2_json_sync.rb +76 -0
- data/scripts/lib/withdrawn_public_objects.rb +23 -0
- data/scripts/lib/writing_index_contract.rb +61 -0
- data/scripts/pdf/md-to-pdf.mjs +1400 -0
- data/scripts/pdf/md-to-pdf.sh +27 -0
- data/scripts/test/fixtures/dual-template/_config.yml +32 -0
- data/scripts/test/fixtures/dual-template/index.html +4 -0
- data/scripts/test/render-parity/article-note.json +5 -0
- data/scripts/test/render-parity/blockquote.json +5 -0
- data/scripts/test/render-parity/blocks/ansi-basic.json +7 -0
- data/scripts/test/render-parity/blocks/ansi-bold-is-bright.json +7 -0
- data/scripts/test/render-parity/blocks/ansi-drops-cursor-moves.json +7 -0
- data/scripts/test/render-parity/blocks/ansi-escapes-markup.json +7 -0
- data/scripts/test/render-parity/blocks/ansi-extended-colour.json +7 -0
- data/scripts/test/render-parity/blocks/ansi-real-control-byte.json +7 -0
- data/scripts/test/render-parity/blocks/archive-card-date-once.json +19 -0
- data/scripts/test/render-parity/blocks/archive-card-empty-foot.json +7 -0
- data/scripts/test/render-parity/blocks/archive-card-footer-dates.json +22 -0
- data/scripts/test/render-parity/blocks/archive-card-meta-parts.json +26 -0
- data/scripts/test/render-parity/blocks/archive-card.json +32 -0
- data/scripts/test/render-parity/blocks/cta-link.json +13 -0
- data/scripts/test/render-parity/blocks/lang-pill-pt.json +8 -0
- data/scripts/test/render-parity/blocks/lang-pill.json +8 -0
- data/scripts/test/render-parity/blocks/ledger-row-meta-parts.json +29 -0
- data/scripts/test/render-parity/blocks/ledger-row.json +35 -0
- data/scripts/test/render-parity/blocks/meta-strip.json +27 -0
- data/scripts/test/render-parity/blocks/poster-media-image.json +8 -0
- data/scripts/test/render-parity/blocks/poster-media-video.json +9 -0
- data/scripts/test/render-parity/blocks/section-banner.json +13 -0
- data/scripts/test/render-parity/blocks/system-card-custom-meta.json +22 -0
- data/scripts/test/render-parity/blocks/system-card.json +48 -0
- data/scripts/test/render-parity/blocks/tag-chips-links.json +12 -0
- data/scripts/test/render-parity/blocks/tag-chips.json +9 -0
- data/scripts/test/render-parity/cut-in-blank-line.json +9 -0
- data/scripts/test/render-parity/cut-in-right.json +9 -0
- data/scripts/test/render-parity/cut-in.json +8 -0
- data/scripts/test/render-parity/figure.json +5 -0
- data/scripts/test/render-parity/load-runtime.mjs +337 -0
- data/scripts/test/render-parity/ongoing-product.json +5 -0
- data/scripts/test/render-parity/plotly.json +5 -0
- data/scripts/test/render-parity/poem.json +5 -0
- data/scripts/test/render-parity/run-absent-field-checks.mjs +232 -0
- data/scripts/test/render-parity/run-block-fixture.mjs +86 -0
- data/scripts/test/render-parity/run-detail-parity-checks.mjs +123 -0
- data/scripts/test/render-parity/run-field-flow-checks.mjs +227 -0
- data/scripts/test/render-parity/run-fixture.mjs +159 -0
- data/scripts/test/render-parity/selectors.mjs +43 -0
- data/scripts/test/render-parity/video.json +5 -0
- data/scripts/test/render-parity/youtube-short.json +10 -0
- data/scripts/test/render-parity/youtube.json +8 -0
- data/scripts/test/test-absent-field-absent-element.rb +43 -0
- data/scripts/test/test-cdn-pins.rb +44 -0
- data/scripts/test/test-cloudflare-watch-paths-doc.rb +31 -0
- data/scripts/test/test-configuration-doc.rb +36 -0
- data/scripts/test/test-content-detail-urls.rb +346 -0
- data/scripts/test/test-content-keys.rb +110 -0
- data/scripts/test/test-content-license.rb +45 -0
- data/scripts/test/test-content-render-parity.rb +38 -0
- data/scripts/test/test-content-validators.rb +12 -0
- data/scripts/test/test-css-structure.rb +72 -0
- data/scripts/test/test-cv-export-contract.rb +158 -0
- data/scripts/test/test-detail-render-parity.rb +40 -0
- data/scripts/test/test-dev-reexport.rb +122 -0
- data/scripts/test/test-doctor.rb +39 -0
- data/scripts/test/test-dual-template-instances.rb +87 -0
- data/scripts/test/test-dual-template-runtime.rb +167 -0
- data/scripts/test/test-examples-build.rb +60 -0
- data/scripts/test/test-export-content-json-incremental.rb +138 -0
- data/scripts/test/test-export.rb +53 -0
- data/scripts/test/test-exported-fields-reach-the-view.rb +60 -0
- data/scripts/test/test-form-control-theming.rb +143 -0
- data/scripts/test/test-git-chronology.rb +414 -0
- data/scripts/test/test-hero-portraits.mjs +97 -0
- data/scripts/test/test-hero-portraits.rb +162 -0
- data/scripts/test/test-home-band-art.rb +135 -0
- data/scripts/test/test-install-functions.rb +39 -0
- data/scripts/test/test-language-export.rb +79 -0
- data/scripts/test/test-listing-filter-contract.rb +119 -0
- data/scripts/test/test-math-row-breaks.mjs +79 -0
- data/scripts/test/test-math-row-breaks.rb +18 -0
- data/scripts/test/test-new.rb +113 -0
- data/scripts/test/test-no-personal-data.rb +85 -0
- data/scripts/test/test-public-cv-export.rb +164 -0
- data/scripts/test/test-r2-config-consistency.rb +57 -0
- data/scripts/test/test-r2-json-sync.rb +193 -0
- data/scripts/test/test-registry-home.rb +34 -0
- data/scripts/test/test-runtime-js-syntax.rb +64 -0
- data/scripts/test/test-slim-writing-index.rb +126 -0
- data/scripts/test/test-studio-api-contract.rb +99 -0
- data/scripts/test/test-studio-content-files.rb +79 -0
- data/scripts/test/test-studio-liquid-roundtrip.mjs +179 -0
- data/scripts/test/test-studio-rate-limit.mjs +70 -0
- data/scripts/test/test-studio-rewrite-api-contract.rb +59 -0
- data/scripts/test/test-studio-rewrite-sanitize.mjs +210 -0
- data/scripts/test/test-studio-schema-composition.rb +126 -0
- data/scripts/test/test-studio-slug-config.rb +167 -0
- data/scripts/test/test-template-packages.rb +51 -0
- data/scripts/test/test-text-excerpt.rb +51 -0
- data/scripts/test/test-unwrap-media-src.mjs +39 -0
- data/scripts/test/test-unwrap-media-src.rb +19 -0
- data/scripts/test/test-withdrawn-content-export.rb +69 -0
- data/scripts/test/test-writing-index-export-slugs.rb +84 -0
- data/scripts/test/test-writing-slug-limits.rb +61 -0
- data/scripts/test/test-xcite-chronology.rb +106 -0
- data/scripts/validate.sh +74 -0
- data/scripts/visual/build-cv-pages-json.js +33 -0
- data/scripts/visual/build-site-pages-json.js +94 -0
- data/scripts/visual/publish-visual-inspection-to-r2.sh +42 -0
- data/scripts/visual/take-screenshots.js +88 -0
- data/scripts/visual/visual-inspection-pages.json +113 -0
- data/studio/assets/index-B_j0pMD5.css +1 -0
- data/studio/assets/index-Ch4YEv9g.js +285 -0
- data/studio/index.html +27 -0
- data/studio/schema.yml +869 -0
- data/studio-app/README.md +27 -0
- data/studio-app/index.html +26 -0
- data/studio-app/package-lock.json +2499 -0
- data/studio-app/package.json +26 -0
- data/studio-app/skills-lock.json +47 -0
- data/studio-app/src/api/client.js +71 -0
- data/studio-app/src/editor/body-editor.js +70 -0
- data/studio-app/src/editor/liquid-core.js +382 -0
- data/studio-app/src/editor/liquid.js +77 -0
- data/studio-app/src/lib/document.js +207 -0
- data/studio-app/src/lib/list-controls.js +317 -0
- data/studio-app/src/lib/schema.js +66 -0
- data/studio-app/src/lib/yaml-doc.js +100 -0
- data/studio-app/src/main.js +3693 -0
- data/studio-app/src/media/browser.js +578 -0
- data/studio-app/src/preview/render.js +430 -0
- data/studio-app/src/styles/studio.css +2826 -0
- data/studio-app/vite.config.js +25 -0
- data/templates/articles/README.md +40 -0
- data/templates/articles/fixtures/article.md +9 -0
- data/templates/articles/screenshots/README.md +6 -0
- data/templates/articles/studio.yml +73 -0
- data/templates/articles/template.yml +21 -0
- data/templates/bibliography/README.md +41 -0
- data/templates/bibliography/fixtures/references.json +9 -0
- data/templates/bibliography/screenshots/README.md +6 -0
- data/templates/bibliography/studio.yml +66 -0
- data/templates/bibliography/template.yml +18 -0
- data/templates/blog/README.md +38 -0
- data/templates/blog/fixtures/post.md +9 -0
- data/templates/blog/screenshots/README.md +6 -0
- data/templates/blog/studio.yml +69 -0
- data/templates/blog/template.yml +20 -0
- data/templates/cv/README.md +34 -0
- data/templates/cv/fixtures/job.md +12 -0
- data/templates/cv/screenshots/README.md +6 -0
- data/templates/cv/studio.yml +200 -0
- data/templates/cv/template.yml +25 -0
- data/templates/media/README.md +38 -0
- data/templates/media/fixtures/resource.md +7 -0
- data/templates/media/screenshots/README.md +6 -0
- data/templates/media/studio.yml +58 -0
- data/templates/media/template.yml +17 -0
- data/templates/network/README.md +34 -0
- data/templates/network/fixtures/sources.json +3 -0
- data/templates/network/screenshots/README.md +6 -0
- data/templates/network/studio.yml +28 -0
- data/templates/network/template.yml +10 -0
- data/templates/portfolio/README.md +39 -0
- data/templates/portfolio/fixtures/project.md +10 -0
- data/templates/portfolio/screenshots/README.md +6 -0
- data/templates/portfolio/studio.yml +89 -0
- data/templates/portfolio/template.yml +18 -0
- metadata +505 -0
data/_layouts/text.html
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: shell
|
|
3
|
+
---
|
|
4
|
+
{% comment %}
|
|
5
|
+
Detail view — private/studio build.
|
|
6
|
+
|
|
7
|
+
Public detail pages never reach this layout: every collection is
|
|
8
|
+
`output: false` in _config.yml and `content/` is excluded from the build, so
|
|
9
|
+
/articles/:slug/ and /posts/:slug/ are rewritten to the listing layouts and
|
|
10
|
+
assembled in the browser by _includes/content-runtime/50-detail.html. What
|
|
11
|
+
this file renders is the studio/private path only.
|
|
12
|
+
|
|
13
|
+
It is therefore written to emit *exactly* the class contract that
|
|
14
|
+
`buildTextSidebar` emits, so assets/css/ledger/detail.css styles both paths
|
|
15
|
+
from one set of rules — and so this markup doubles as the reference for the
|
|
16
|
+
three fields the runtime still has to learn to render (tags, dates, the
|
|
17
|
+
language pill).
|
|
18
|
+
{% endcomment %}
|
|
19
|
+
{% comment %}
|
|
20
|
+
Roughly half the corpus never declares `language`, so the UI language falls
|
|
21
|
+
back the same way the runtime's resolveTocLang does: posts are Portuguese,
|
|
22
|
+
everything else English. The pill below is *not* rendered from this fallback
|
|
23
|
+
— it binds to the declared field only.
|
|
24
|
+
{% endcomment %}
|
|
25
|
+
{% assign lang = page.language %}
|
|
26
|
+
{% unless lang and lang != "" %}
|
|
27
|
+
{% if page.collection == "posts" %}{% assign lang = "ptbr" %}{% else %}{% assign lang = "enus" %}{% endif %}
|
|
28
|
+
{% endunless %}
|
|
29
|
+
{% assign t = site.data.translations %}
|
|
30
|
+
|
|
31
|
+
{% assign back_url = "/#" %}
|
|
32
|
+
{% if page.back_url %}
|
|
33
|
+
{% assign back_url = page.back_url %}
|
|
34
|
+
{% elsif page.collection == "posts" %}
|
|
35
|
+
{% assign back_url = "/pages/blog/" %}
|
|
36
|
+
{% elsif page.collection == "projects" %}
|
|
37
|
+
{% assign back_url = "/pages/projects/" %}
|
|
38
|
+
{% elsif page.collection == "products" %}
|
|
39
|
+
{% assign back_url = "/pages/cv/#products" %}
|
|
40
|
+
{% elsif page.collection == "articles" %}
|
|
41
|
+
{% assign back_url = "/pages/articles/" %}
|
|
42
|
+
{% endif %}
|
|
43
|
+
|
|
44
|
+
{% assign back_label = t.back[lang] %}
|
|
45
|
+
{% if page.back_label %}
|
|
46
|
+
{% assign back_label = page.back_label %}
|
|
47
|
+
{% elsif page.collection == "posts" %}{% assign back_label = t.back_to_drafts[lang] %}
|
|
48
|
+
{% elsif page.collection == "projects" %}{% assign back_label = t.back_to_projects[lang] %}
|
|
49
|
+
{% elsif page.collection == "products" %}{% assign back_label = t.back_to_cv[lang] %}
|
|
50
|
+
{% elsif page.collection == "articles" %}{% assign back_label = t.back_to_articles[lang] %}
|
|
51
|
+
{% endif %}
|
|
52
|
+
|
|
53
|
+
{% comment %}Tag chips link back to the listing that filters on them.{% endcomment %}
|
|
54
|
+
{% assign tag_base = nil %}
|
|
55
|
+
{% if page.collection == "posts" %}{% assign tag_base = "/pages/blog/?tag=" %}
|
|
56
|
+
{% elsif page.collection == "articles" %}{% assign tag_base = "/pages/articles/?tag=" %}
|
|
57
|
+
{% elsif page.collection == "projects" %}{% assign tag_base = "/pages/projects/?tag=" %}
|
|
58
|
+
{% endif %}
|
|
59
|
+
|
|
60
|
+
{% assign lang_label = nil %}
|
|
61
|
+
{% if page.language == "enus" %}{% assign lang_label = "EN" %}
|
|
62
|
+
{% elsif page.language == "ptbr" %}{% assign lang_label = "PT" %}
|
|
63
|
+
{% endif %}
|
|
64
|
+
|
|
65
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-8 md:px-16 py-12 text-page{% if page.accent and page.accent != '' and page.accent != 'blue' %} page-accent--{{ page.accent }}{% endif %}">
|
|
66
|
+
<div class="grid grid-cols-1 gap-8 lg:gap-12 items-start text-detail-grid">
|
|
67
|
+
<aside>
|
|
68
|
+
<div class="md:sticky md:top-32 text-detail-sidebar">
|
|
69
|
+
{% if page.collection == "posts" or page.collection == "articles" or page.layout == "product" or page.layout == "project" %}
|
|
70
|
+
<div class="article-sidebar-heading mb-8">
|
|
71
|
+
{% if page.icon %}
|
|
72
|
+
<span class="material-symbols-outlined text-5xl md:text-6xl text-secondary mb-4 block leading-none">{{ page.icon }}</span>
|
|
73
|
+
{% endif %}
|
|
74
|
+
<h1 class="text-title">{{ page.title }}</h1>
|
|
75
|
+
{% if page.collection == "posts" and page.thumbnail %}
|
|
76
|
+
<figure class="article-sidebar-thumbnail">
|
|
77
|
+
<img src="{{ page.thumbnail }}" alt="" loading="lazy">
|
|
78
|
+
</figure>
|
|
79
|
+
{% elsif page.description %}
|
|
80
|
+
<p class="article-sidebar-description">{{ page.description }}</p>
|
|
81
|
+
{% elsif page.layout == "project" and page.tagline %}
|
|
82
|
+
<p class="article-sidebar-description">{{ page.tagline }}</p>
|
|
83
|
+
{% endif %}
|
|
84
|
+
</div>
|
|
85
|
+
{% endif %}
|
|
86
|
+
|
|
87
|
+
<div class="flex flex-col gap-6 text-tertiary font-label text-xs article-meta mb-8">
|
|
88
|
+
<div class="flex flex-col gap-2">
|
|
89
|
+
{% comment %}
|
|
90
|
+
Classification. `tags` replaced `category` on articles and posts;
|
|
91
|
+
an entry with no tags renders no chip row at all.
|
|
92
|
+
{% endcomment %}
|
|
93
|
+
{% if page.tags and page.tags.size > 0 %}
|
|
94
|
+
<div class="ledger-chip-row">
|
|
95
|
+
{% for tag in page.tags %}
|
|
96
|
+
{% if tag_base %}
|
|
97
|
+
<a class="ledger-chip" href="{{ tag_base }}{{ tag | url_encode }}">{{ tag }}</a>
|
|
98
|
+
{% else %}
|
|
99
|
+
<span class="ledger-chip">{{ tag }}</span>
|
|
100
|
+
{% endif %}
|
|
101
|
+
{% endfor %}
|
|
102
|
+
</div>
|
|
103
|
+
{% endif %}
|
|
104
|
+
|
|
105
|
+
{% if page.collection == "posts" or page.collection == "articles" %}
|
|
106
|
+
{% include page/date_evolution.html stacked=true %}
|
|
107
|
+
{% endif %}
|
|
108
|
+
|
|
109
|
+
{% if page.layout == "product" %}
|
|
110
|
+
{% if page.company %}<span class="text-secondary font-bold">{{ page.company }}</span>{% endif %}
|
|
111
|
+
{% if page.product %}<span>{{ page.product }}</span>{% endif %}
|
|
112
|
+
{% if page.start_date %}<span>{{ page.start_date }}{% if page.status == "ongoing" or page.end_date == nil or page.end_date == "" %} – Ongoing{% elsif page.end_date %} – {{ page.end_date }}{% endif %}</span>{% endif %}
|
|
113
|
+
{% endif %}
|
|
114
|
+
|
|
115
|
+
{% if page.layout == "project" %}
|
|
116
|
+
{% if page.label %}<span class="text-tertiary font-bold">{{ page.label }}</span>{% endif %}
|
|
117
|
+
{% if page.start_date %}<span>{{ page.start_date }}{% if page.status == "ongoing" %} – Ongoing{% elsif page.end_date %} – {{ page.end_date }}{% endif %}</span>{% endif %}
|
|
118
|
+
{% endif %}
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
{% if layout.sidebar_extra %}
|
|
122
|
+
{{ layout.sidebar_extra }}
|
|
123
|
+
{% endif %}
|
|
124
|
+
|
|
125
|
+
{% if page.collection == "posts" or page.collection == "articles" or page.collection == "products" or page.collection == "projects" %}
|
|
126
|
+
<div class="detail-actions" data-detail-actions>
|
|
127
|
+
<a class="detail-action" href="{{ back_url }}" aria-label="Back">
|
|
128
|
+
<span class="material-symbols-outlined text-sm" aria-hidden="true">chevron_left</span>
|
|
129
|
+
<span class="detail-action-label">Back</span>
|
|
130
|
+
</a>
|
|
131
|
+
{% if page.collection == "posts" or page.collection == "articles" %}
|
|
132
|
+
<button type="button" class="detail-action" data-detail-share aria-label="Share">
|
|
133
|
+
<span class="material-symbols-outlined text-sm" aria-hidden="true">share</span>
|
|
134
|
+
<span class="detail-action-label">Share</span>
|
|
135
|
+
</button>
|
|
136
|
+
<button type="button" class="detail-action" data-detail-cite aria-label="Cite">
|
|
137
|
+
<span class="material-symbols-outlined text-sm" aria-hidden="true">format_quote</span>
|
|
138
|
+
<span class="detail-action-label">Cite</span>
|
|
139
|
+
</button>
|
|
140
|
+
{% if page.pdf_url %}
|
|
141
|
+
<a class="detail-action" href="{{ page.pdf_url }}" rel="noopener" target="_blank" aria-label="PDF">
|
|
142
|
+
<span class="material-symbols-outlined text-sm" aria-hidden="true">picture_as_pdf</span>
|
|
143
|
+
<span class="detail-action-label">PDF</span>
|
|
144
|
+
</a>
|
|
145
|
+
{% endif %}
|
|
146
|
+
{% endif %}
|
|
147
|
+
<div class="detail-type-size" data-prose-size>
|
|
148
|
+
<button type="button" class="detail-type-size-trigger" aria-haspopup="listbox" aria-expanded="false" aria-label="Reading size 0 pt">
|
|
149
|
+
<span class="material-symbols-outlined" aria-hidden="true">format_size</span>
|
|
150
|
+
<span class="detail-type-size-value" data-prose-size-label>0</span>
|
|
151
|
+
<span class="material-symbols-outlined detail-type-size-caret" aria-hidden="true">expand_more</span>
|
|
152
|
+
</button>
|
|
153
|
+
<div class="detail-type-size-menu" role="listbox" aria-label="Reading size" hidden>
|
|
154
|
+
<button type="button" class="detail-type-size-step" data-prose-offset="-4" role="option" aria-selected="false" aria-label="-4 pt" tabindex="-1">-4</button>
|
|
155
|
+
<button type="button" class="detail-type-size-step" data-prose-offset="-3" role="option" aria-selected="false" aria-label="-3 pt" tabindex="-1">-3</button>
|
|
156
|
+
<button type="button" class="detail-type-size-step" data-prose-offset="-2" role="option" aria-selected="false" aria-label="-2 pt" tabindex="-1">-2</button>
|
|
157
|
+
<button type="button" class="detail-type-size-step" data-prose-offset="-1" role="option" aria-selected="false" aria-label="-1 pt" tabindex="-1">-1</button>
|
|
158
|
+
<button type="button" class="detail-type-size-step" data-prose-offset="0" role="option" aria-selected="true" aria-label="0 pt" tabindex="-1">0</button>
|
|
159
|
+
<button type="button" class="detail-type-size-step" data-prose-offset="1" role="option" aria-selected="false" aria-label="+1 pt" tabindex="-1">+1</button>
|
|
160
|
+
<button type="button" class="detail-type-size-step" data-prose-offset="2" role="option" aria-selected="false" aria-label="+2 pt" tabindex="-1">+2</button>
|
|
161
|
+
<button type="button" class="detail-type-size-step" data-prose-offset="3" role="option" aria-selected="false" aria-label="+3 pt" tabindex="-1">+3</button>
|
|
162
|
+
<button type="button" class="detail-type-size-step" data-prose-offset="4" role="option" aria-selected="false" aria-label="+4 pt" tabindex="-1">+4</button>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
{% endif %}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</aside>
|
|
170
|
+
|
|
171
|
+
<div class="text-detail-main">
|
|
172
|
+
<div class="text-reading-column text-page-main" data-detail-view>
|
|
173
|
+
{% capture drop_cap_nl %}
|
|
174
|
+
{% endcapture %}
|
|
175
|
+
{% assign drop_cap_body = content | strip | split: drop_cap_nl %}
|
|
176
|
+
{% assign drop_cap_first_line = "" %}
|
|
177
|
+
{% for drop_cap_line in drop_cap_body %}
|
|
178
|
+
{% assign drop_cap_trimmed = drop_cap_line | strip %}
|
|
179
|
+
{% if drop_cap_trimmed != "" %}
|
|
180
|
+
{% assign drop_cap_first_line = drop_cap_trimmed %}
|
|
181
|
+
{% break %}
|
|
182
|
+
{% endif %}
|
|
183
|
+
{% endfor %}
|
|
184
|
+
{% assign drop_cap_mark = "" %}
|
|
185
|
+
{% if page.drop_cap %}
|
|
186
|
+
{% assign drop_cap_lead = drop_cap_first_line | slice: 0, 1 %}
|
|
187
|
+
{% if drop_cap_lead != "#" %}
|
|
188
|
+
{% assign drop_cap_mark = " has-drop-cap" %}
|
|
189
|
+
{% endif %}
|
|
190
|
+
{% endif %}
|
|
191
|
+
<div class="article-content{{ drop_cap_mark }}" markdown="1">
|
|
192
|
+
{{ content }}
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
{% if page.tab %}
|
|
196
|
+
{% assign tab_data = site.data.tabs[page.tab] %}
|
|
197
|
+
{% include chord-tab.html tab=tab_data %}
|
|
198
|
+
{% endif %}
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
{% if page.collection == "posts" or page.collection == "articles" %}
|
|
202
|
+
<nav class="text-page-nav text-reading-column w-full" aria-label="Article navigation">
|
|
203
|
+
{% if page.previous %}
|
|
204
|
+
<a href="{{ page.previous.url }}">
|
|
205
|
+
<span class="material-symbols-outlined text-sm">arrow_left_alt</span>
|
|
206
|
+
{{ t.previous_text[lang] }}
|
|
207
|
+
</a>
|
|
208
|
+
{% else %}
|
|
209
|
+
<span></span>
|
|
210
|
+
{% endif %}
|
|
211
|
+
{% if page.next %}
|
|
212
|
+
<a href="{{ page.next.url }}">
|
|
213
|
+
{{ t.next_text[lang] }}
|
|
214
|
+
<span class="material-symbols-outlined text-sm">arrow_right_alt</span>
|
|
215
|
+
</a>
|
|
216
|
+
{% elsif page.collection == "posts" %}
|
|
217
|
+
<a href="#" id="random-post-link">
|
|
218
|
+
{{ t.random_text[lang] }}
|
|
219
|
+
<span class="material-symbols-outlined text-sm">shuffle</span>
|
|
220
|
+
</a>
|
|
221
|
+
{% else %}
|
|
222
|
+
<span></span>
|
|
223
|
+
{% endif %}
|
|
224
|
+
</nav>
|
|
225
|
+
|
|
226
|
+
<section class="article-cite-block text-reading-column w-full" data-article-cite-block>
|
|
227
|
+
<p class="article-cite-label font-label">{{ t.cite_this_text[lang] | default: "Cite this text" }}</p>
|
|
228
|
+
<div class="code-block" data-code-language="bibtex">
|
|
229
|
+
<div class="code-block-bar">
|
|
230
|
+
<span class="code-block-tab font-label">BibTeX</span>
|
|
231
|
+
</div>
|
|
232
|
+
<pre><code class="language-bibtex">@misc{% raw %}{{% endraw %}{{ page.slug | default: page.title | slugify }},
|
|
233
|
+
title = {% raw %}{{% endraw %}{{ page.title }}{% raw %}}{% endraw %},
|
|
234
|
+
author = {% raw %}{{% endraw %}{{ page.author | default: site.pandorga.identity.name | default: site.author }}{% raw %}}{% endraw %},{% if page.date %}
|
|
235
|
+
year = {% raw %}{{% endraw %}{{ page.date | date: "%Y" }}{% raw %}}{% endraw %},{% endif %}
|
|
236
|
+
url = {% raw %}{{% endraw %}{{ page.url | absolute_url }}{% raw %}}{% endraw %}
|
|
237
|
+
{% raw %}}{% endraw %}</code></pre>
|
|
238
|
+
</div>
|
|
239
|
+
</section>
|
|
240
|
+
{% endif %}
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
{% include mathjax.html %}
|
|
246
|
+
{% if page.mermaid %}{% include page/mermaid.html %}{% endif %}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
# Generates a build-time references.bib artifact in a temporary build location
|
|
7
|
+
# for jekyll-scholar if needed, compiled from YAML entries in content/collections/bibliography/.
|
|
8
|
+
# Editors edit YAML in content/collections/bibliography/ (one file per citation key);
|
|
9
|
+
# BibTeX is never edited by hand in content/.
|
|
10
|
+
module Jekyll
|
|
11
|
+
class BibliographyArtifactGenerator < Generator
|
|
12
|
+
safe true
|
|
13
|
+
priority :highest
|
|
14
|
+
|
|
15
|
+
def generate(site)
|
|
16
|
+
source_dir = Pathname.new(site.source).join("content/collections/bibliography")
|
|
17
|
+
return unless source_dir.directory?
|
|
18
|
+
|
|
19
|
+
artifact_dir = Pathname.new(site.source).join(".jekyll-cache")
|
|
20
|
+
artifact_dir.mkpath
|
|
21
|
+
artifact_file = artifact_dir.join("generated_references.bib")
|
|
22
|
+
|
|
23
|
+
# Decouple blocking I/O from CPU-intensive YAML parsing
|
|
24
|
+
# Pre-read all file contents into memory sequentially
|
|
25
|
+
files = source_dir.glob("*.{yml,yaml}").sort
|
|
26
|
+
file_contents = files.map do |file|
|
|
27
|
+
[file.basename(".*").to_s, file, file.read]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
bib_entries = []
|
|
31
|
+
file_contents.each do |key, file, content|
|
|
32
|
+
begin
|
|
33
|
+
data = YAML.safe_load(content, permitted_classes: [Date, Time], aliases: true) || {}
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
Jekyll.logger.warn "BibliographyArtifactGenerator:", "Failed to load #{file}: #{e.message}"
|
|
36
|
+
next
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
type = data.delete("type") || "misc"
|
|
40
|
+
fields = []
|
|
41
|
+
data.each do |k, v|
|
|
42
|
+
next if v.nil? || v.to_s.empty?
|
|
43
|
+
fields << " #{k} = {#{v}}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
bib_entries << "@#{type}{#{key},\n#{fields.join(",\n")}\n}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
artifact_file.write(bib_entries.join("\n\n"))
|
|
50
|
+
|
|
51
|
+
if site.config["scholar"]
|
|
52
|
+
site.config["scholar"]["source"] = ".jekyll-cache"
|
|
53
|
+
site.config["scholar"]["bibliography"] = "generated_references.bib"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "jekyll"
|
|
4
|
+
|
|
5
|
+
# Maps pandorga.content.backend / base_url onto the legacy content_api_base_url
|
|
6
|
+
# and local_content_serve flags the runtime and exporters already understand.
|
|
7
|
+
# Refs: PLT §5.2, PLT-AC-6
|
|
8
|
+
module Jekyll
|
|
9
|
+
module PandorgaContentBackend
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def apply!(site)
|
|
13
|
+
require "pandorga/registry"
|
|
14
|
+
|
|
15
|
+
config = site.config
|
|
16
|
+
backend = PandorgaRegistry.content_backend(config)
|
|
17
|
+
base = PandorgaRegistry.content_base_url(config)
|
|
18
|
+
|
|
19
|
+
config["pandorga"] ||= {}
|
|
20
|
+
config["pandorga"]["content"] ||= {}
|
|
21
|
+
config["pandorga"]["content"]["backend"] = backend
|
|
22
|
+
|
|
23
|
+
if PandorgaRegistry.static_backend?(config)
|
|
24
|
+
config["content_api_base_url"] = ""
|
|
25
|
+
# Serve exported JSON from _site unless the site opts out explicitly.
|
|
26
|
+
config["local_content_serve"] = true unless config.key?("local_content_serve")
|
|
27
|
+
else
|
|
28
|
+
config["content_api_base_url"] = base
|
|
29
|
+
config["pandorga"]["content"]["base_url"] = base
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
Jekyll::Hooks.register :site, :after_init do |site|
|
|
36
|
+
Jekyll::PandorgaContentBackend.apply!(site)
|
|
37
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "set"
|
|
5
|
+
require "pandorga/registry"
|
|
6
|
+
|
|
7
|
+
# Loads editorial Markdown from /content into Jekyll collections at build time.
|
|
8
|
+
# This keeps /content excluded from the default Jekyll read path while still
|
|
9
|
+
# providing collection docs for product/project pages, timeline, network graph,
|
|
10
|
+
# CV jobs, and related plugins. Articles and posts stay API-backed; their metadata
|
|
11
|
+
# is indexed separately by ContentWritingIndex.
|
|
12
|
+
module Jekyll
|
|
13
|
+
class ContentCollections < Generator
|
|
14
|
+
safe true
|
|
15
|
+
priority :highest
|
|
16
|
+
|
|
17
|
+
COLLECTION_PATHS = PandorgaRegistry::DEFAULT_SHELL_COLLECTIONS
|
|
18
|
+
|
|
19
|
+
def generate(site)
|
|
20
|
+
return if site.config["shell_content_agnostic"]
|
|
21
|
+
|
|
22
|
+
content_root = File.join(site.source, site.config["content_root"] || "content")
|
|
23
|
+
return unless File.directory?(content_root)
|
|
24
|
+
|
|
25
|
+
pages = PandorgaRegistry.pages(site.config)
|
|
26
|
+
paths = pages.empty? ? COLLECTION_PATHS : PandorgaRegistry.shell_collection_dirs(pages)
|
|
27
|
+
|
|
28
|
+
paths.each do |collection_name, relative_path|
|
|
29
|
+
inject_documents(site, collection_name, File.join(content_root, relative_path))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def inject_documents(site, collection_name, directory)
|
|
36
|
+
collection = site.collections[collection_name]
|
|
37
|
+
return unless collection && File.directory?(directory)
|
|
38
|
+
|
|
39
|
+
existing_paths = collection.docs.map(&:path).to_set
|
|
40
|
+
|
|
41
|
+
Dir.glob(File.join(directory, "**", "*.{md,markdown}")).sort.each do |absolute_path|
|
|
42
|
+
relative_path = Pathname.new(absolute_path).relative_path_from(Pathname.new(site.source)).to_s
|
|
43
|
+
next if existing_paths.include?(relative_path)
|
|
44
|
+
|
|
45
|
+
doc = Jekyll::Document.new(relative_path, site: site, collection: collection)
|
|
46
|
+
doc.read
|
|
47
|
+
collection.docs << doc
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Shared detail URL rewrite rules for Cloudflare _redirects and local jekyll serve.
|
|
4
|
+
module ContentDetailPaths
|
|
5
|
+
POST_SHELL = "/pages/blog/"
|
|
6
|
+
ARTICLE_SHELL = "/pages/articles/"
|
|
7
|
+
PRODUCT_SHELL = "/pages/cv/"
|
|
8
|
+
PROJECT_SHELL = "/pages/projects/"
|
|
9
|
+
|
|
10
|
+
POST_DETAIL_RE = %r{\A/posts/[^./][^/]*/?\z}
|
|
11
|
+
BLOG_DETAIL_RE = %r{\A/blog/[^./][^/]*/?\z}
|
|
12
|
+
ARTICLE_DETAIL_RE = %r{\A/articles/[^./][^/]*/?\z}
|
|
13
|
+
PRODUCT_DETAIL_RE = %r{\A/products/[^./][^/]*/?\z}
|
|
14
|
+
PROJECT_DETAIL_RE = %r{\A/projects/[^./][^/]*/?\z}
|
|
15
|
+
|
|
16
|
+
REWRITES = [
|
|
17
|
+
[POST_DETAIL_RE, POST_SHELL],
|
|
18
|
+
[BLOG_DETAIL_RE, POST_SHELL],
|
|
19
|
+
[ARTICLE_DETAIL_RE, ARTICLE_SHELL],
|
|
20
|
+
[PRODUCT_DETAIL_RE, PRODUCT_SHELL],
|
|
21
|
+
[PROJECT_DETAIL_RE, PROJECT_SHELL]
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
24
|
+
RESERVED = [
|
|
25
|
+
POST_SHELL,
|
|
26
|
+
ARTICLE_SHELL,
|
|
27
|
+
PRODUCT_SHELL,
|
|
28
|
+
PROJECT_SHELL
|
|
29
|
+
].freeze
|
|
30
|
+
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
def rewrite_path(path)
|
|
34
|
+
normalized = path.end_with?("/") ? path : "#{path}/"
|
|
35
|
+
return nil if RESERVED.include?(normalized)
|
|
36
|
+
|
|
37
|
+
REWRITES.each do |pattern, target|
|
|
38
|
+
return target if pattern.match?(path) || pattern.match?(normalized)
|
|
39
|
+
end
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def post_detail_path(slug)
|
|
44
|
+
"/posts/#{slug}/"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def article_detail_path(slug)
|
|
48
|
+
"/articles/#{slug}/"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def product_detail_path(slug)
|
|
52
|
+
"/products/#{slug}/"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def project_detail_path(slug)
|
|
56
|
+
"/projects/#{slug}/"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Loads editorial files from /content into site.data without making /content part
|
|
4
|
+
# of the Jekyll render graph. The site must be able to build even when the
|
|
5
|
+
# content directory is absent; external storage becomes the publishing path.
|
|
6
|
+
#
|
|
7
|
+
# Example: content/pages/blog/sidebar-intro.md becomes
|
|
8
|
+
# site.data["content_fragments"]["blog/sidebar-intro"]
|
|
9
|
+
# Example: content/collections/data/education.yml becomes
|
|
10
|
+
# site.data["education"]
|
|
11
|
+
|
|
12
|
+
require "yaml"
|
|
13
|
+
require "pathname"
|
|
14
|
+
require "date"
|
|
15
|
+
|
|
16
|
+
module Jekyll
|
|
17
|
+
class ContentLayerGenerator < Generator
|
|
18
|
+
safe true
|
|
19
|
+
priority :highest
|
|
20
|
+
|
|
21
|
+
def generate(site)
|
|
22
|
+
return if site.config["shell_content_agnostic"]
|
|
23
|
+
|
|
24
|
+
content_path = File.join(site.source, site.config["content_root"] || "content")
|
|
25
|
+
site.data["content_fragments"] ||= {}
|
|
26
|
+
|
|
27
|
+
load_page_content(site, content_path)
|
|
28
|
+
load_structured_data(site, content_path)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def load_page_content(site, content_path)
|
|
34
|
+
pages_path = File.join(content_path, "pages")
|
|
35
|
+
return unless Dir.exist?(pages_path)
|
|
36
|
+
|
|
37
|
+
Dir.glob(File.join(pages_path, "**", "*.md")).sort.each do |file|
|
|
38
|
+
key = relative_key(file, pages_path)
|
|
39
|
+
site.data["content_fragments"][key] = File.read(file)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def load_structured_data(site, content_path)
|
|
44
|
+
data_path = File.join(content_path, "collections", "data")
|
|
45
|
+
return unless Dir.exist?(data_path)
|
|
46
|
+
|
|
47
|
+
Dir.glob(File.join(data_path, "**", "*.{yml,yaml}")).sort.each do |file|
|
|
48
|
+
parts = relative_key(file, data_path).split("/")
|
|
49
|
+
write_nested_data(site.data, parts, load_yaml(file))
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def write_nested_data(target, parts, value)
|
|
54
|
+
key = parts.first
|
|
55
|
+
if parts.length == 1
|
|
56
|
+
target[key] = value
|
|
57
|
+
return
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
target[key] ||= {}
|
|
61
|
+
write_nested_data(target[key], parts.drop(1), value)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def relative_key(file, base)
|
|
65
|
+
pathname = Pathname.new(file).relative_path_from(Pathname.new(base)).to_s
|
|
66
|
+
pathname.sub(/\.(md|ya?ml)\z/, "")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def load_yaml(file)
|
|
70
|
+
YAML.safe_load_file(file, permitted_classes: [Date, Time], aliases: true) || {}
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|