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
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: shell
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<main class="writing-index{% if page.accent and page.accent != 'blue' %} page-accent--{{ page.accent }}{% endif %} relative max-w-7xl mx-auto px-4 sm:px-8 md:px-16 pt-10 pb-16"
|
|
6
|
+
{% include page/listing-runtime-attrs.html
|
|
7
|
+
default_collection="articles"
|
|
8
|
+
default_path="/pages/articles/"
|
|
9
|
+
default_detail="/articles/:slug/" %}>
|
|
10
|
+
|
|
11
|
+
{%- comment -%}
|
|
12
|
+
Title, header tags and the optional standfirst all come from the
|
|
13
|
+
`articles` entry of content/pages/headers.yml, the one file that carries
|
|
14
|
+
every page title. The standfirst slot is declared by the
|
|
15
|
+
shared include and stays hidden until that file declares one, so nothing
|
|
16
|
+
is invented here to fill it.
|
|
17
|
+
{%- endcomment -%}
|
|
18
|
+
{% include page/page-header.html key=page.key
|
|
19
|
+
header_class="writing-masthead"
|
|
20
|
+
tags_class="writing-masthead-tags code-technical block hidden" %}
|
|
21
|
+
|
|
22
|
+
<div id="articles-listing">
|
|
23
|
+
|
|
24
|
+
{%- comment -%}
|
|
25
|
+
Featured panel. The entry is whichever article runtime.pickFeatured drew
|
|
26
|
+
for this browsing session — a random pick from the articles that carry a
|
|
27
|
+
thumbnail, remembered in session storage so the home page leads on the
|
|
28
|
+
same piece and paging through the archive below does not reshuffle it.
|
|
29
|
+
No article carries a thumbnail, no panel: the panel is built around an
|
|
30
|
+
image column, and one that reserves space for a picture that does not
|
|
31
|
+
exist is worse than no panel at all.
|
|
32
|
+
{%- endcomment -%}
|
|
33
|
+
<div id="articles-lead"></div>
|
|
34
|
+
|
|
35
|
+
<div class="writing-body">
|
|
36
|
+
|
|
37
|
+
{%- comment -%}
|
|
38
|
+
The filter is a rail beside the archive, not a band above it. Six tags
|
|
39
|
+
plus the order and the keyword box read as a list in a column, they
|
|
40
|
+
stay visible while the archive is scanned, and they take the column the
|
|
41
|
+
mockup filled with invented marginalia. Below 1024px the rail collapses
|
|
42
|
+
into the shared disclosure — trigger plus panel, exactly as
|
|
43
|
+
/pages/sources/ — so a phone is not made to scroll past the controls.
|
|
44
|
+
The controller is the shared ListingFilter either way: same tag facet,
|
|
45
|
+
same ?tag= URLs, same keyword query, same sort.
|
|
46
|
+
|
|
47
|
+
This is the sidebar both writing listings wear; /pages/blog/ emits the
|
|
48
|
+
same aside with its own labels. Anything added here has to make sense
|
|
49
|
+
on a Portuguese notebook as well as on an English archive, which is why
|
|
50
|
+
the last group is the licence: the terms are a fact about the corpus,
|
|
51
|
+
not about either page.
|
|
52
|
+
{%- endcomment -%}
|
|
53
|
+
<aside class="writing-rail listing-filter" data-listing-filter-root hidden>
|
|
54
|
+
<button type="button" class="writing-rail-trigger listing-filter-trigger"
|
|
55
|
+
data-listing-filter-trigger aria-expanded="false"
|
|
56
|
+
aria-controls="writing-filter-panel">
|
|
57
|
+
<span class="material-symbols-outlined" aria-hidden="true">filter_list</span>
|
|
58
|
+
<span class="font-label writing-rail-trigger-label">Filter</span>
|
|
59
|
+
</button>
|
|
60
|
+
|
|
61
|
+
<div id="writing-filter-panel" class="writing-rail-panel" data-listing-filter-panel hidden>
|
|
62
|
+
<p class="listing-filter-group-label font-label text-on-surface-variant">Keyword</p>
|
|
63
|
+
<label class="writing-rail-search">
|
|
64
|
+
<span class="sr-only">Filter articles by keyword</span>
|
|
65
|
+
<input type="search" class="listing-filter-search writing-rail-input font-body"
|
|
66
|
+
data-listing-filter-q placeholder="Title, summary, tag…" autocomplete="off">
|
|
67
|
+
<span class="material-symbols-outlined writing-rail-search-icon" aria-hidden="true">search</span>
|
|
68
|
+
</label>
|
|
69
|
+
<div data-listing-filter-chips></div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
{%- comment -%}
|
|
73
|
+
Citation and reuse. Filled from the export manifest's licence record
|
|
74
|
+
and nothing else — no declared licence, no block.
|
|
75
|
+
{%- endcomment -%}
|
|
76
|
+
<section class="writing-rail-licence" data-writing-licence hidden></section>
|
|
77
|
+
</aside>
|
|
78
|
+
|
|
79
|
+
<div class="writing-main">
|
|
80
|
+
{%- comment -%}
|
|
81
|
+
Both halves of this line are read off the live list: the ordering is
|
|
82
|
+
the active sort and the tally is the length of the filtered
|
|
83
|
+
selection. Neither is a standing claim about the archive, and neither
|
|
84
|
+
is authored anywhere.
|
|
85
|
+
{%- endcomment -%}
|
|
86
|
+
<div class="writing-archive-head">
|
|
87
|
+
<h2 class="writing-archive-title code-technical">Archive</h2>
|
|
88
|
+
<span class="writing-archive-note code-technical" data-writing-order></span>
|
|
89
|
+
<span class="writing-archive-tally code-technical" data-writing-tally></span>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div class="writing-archive" id="articles-list">
|
|
93
|
+
<section id="empty-state" class="writing-archive-state">
|
|
94
|
+
<p class="font-body text-on-surface-variant italic">Loading articles...</p>
|
|
95
|
+
</section>
|
|
96
|
+
<section id="no-results" class="writing-archive-state hidden">
|
|
97
|
+
<p class="font-body text-on-surface-variant italic">Nothing matches these filters yet.</p>
|
|
98
|
+
</section>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div id="pagination-controls" class="writing-pagination hidden" aria-label="Article pages"></div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div id="articles-detail" class="hidden">
|
|
107
|
+
<div class="grid grid-cols-1 gap-8 lg:gap-12 items-start text-detail-grid">
|
|
108
|
+
<aside id="articles-detail-sidebar"></aside>
|
|
109
|
+
<div id="articles-detail-main"></div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
</main>
|
|
114
|
+
{% include mathjax.html %}
|
|
115
|
+
|
|
116
|
+
<script>
|
|
117
|
+
(function () {
|
|
118
|
+
'use strict';
|
|
119
|
+
|
|
120
|
+
var runtime = window.ContentRuntime;
|
|
121
|
+
if (!runtime || !runtime.ListingFilter) return;
|
|
122
|
+
|
|
123
|
+
var configRoot = document.querySelector('[data-listing-collection]');
|
|
124
|
+
var collection = (configRoot && configRoot.getAttribute('data-listing-collection')) || 'articles';
|
|
125
|
+
var listingPath = (configRoot && configRoot.getAttribute('data-listing-path')) || '/pages/articles/';
|
|
126
|
+
var detailPattern = (configRoot && configRoot.getAttribute('data-listing-detail')) || '/articles/:slug/';
|
|
127
|
+
if (listingPath.slice(-1) !== '/') listingPath += '/';
|
|
128
|
+
|
|
129
|
+
var listing = runtime.Listing;
|
|
130
|
+
var perPage = parseInt('{{ site.posts_per_page | default: 6 }}', 10);
|
|
131
|
+
var params = new URLSearchParams(window.location.search);
|
|
132
|
+
var selectedArticle = runtime.contentSlug('article') ||
|
|
133
|
+
runtime.slugFromDetailPattern(window.location.pathname, detailPattern);
|
|
134
|
+
var currentPage = Math.max(1, parseInt(params.get('page') || '1', 10));
|
|
135
|
+
|
|
136
|
+
var grid = document.getElementById('articles-list');
|
|
137
|
+
var emptyState = document.getElementById('empty-state');
|
|
138
|
+
var noResults = document.getElementById('no-results');
|
|
139
|
+
var controls = document.getElementById('pagination-controls');
|
|
140
|
+
var listingRoot = document.getElementById('articles-listing');
|
|
141
|
+
var leadRoot = document.getElementById('articles-lead');
|
|
142
|
+
var orderNote = document.querySelector('[data-writing-order]');
|
|
143
|
+
var tallyNote = document.querySelector('[data-writing-tally]');
|
|
144
|
+
var detailRoot = document.getElementById('articles-detail');
|
|
145
|
+
var detailSidebar = document.getElementById('articles-detail-sidebar');
|
|
146
|
+
var detailMain = document.getElementById('articles-detail-main');
|
|
147
|
+
var pageHeader = document.querySelector('[data-content-page-header="{{ page.key }}"]');
|
|
148
|
+
var filterRoot = document.querySelector('[data-listing-filter-root]');
|
|
149
|
+
var licenceEl = document.querySelector('[data-writing-licence]');
|
|
150
|
+
|
|
151
|
+
var SORT_LABELS = {
|
|
152
|
+
updated: 'Order: last revised',
|
|
153
|
+
published: 'Order: date published'
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
var allArticles = [];
|
|
157
|
+
var filterUi = null;
|
|
158
|
+
|
|
159
|
+
/* Drawn once, from the whole archive rather than the current selection:
|
|
160
|
+
* the point of the session-held pick is that the home page and this page
|
|
161
|
+
* agree, and they only can if both draw from the same pool. Recomputing it
|
|
162
|
+
* per filter would also let a tag whose entries have no thumbnail
|
|
163
|
+
* overwrite the stored slug and change what the home page leads with. */
|
|
164
|
+
var featured = null;
|
|
165
|
+
|
|
166
|
+
function entryUrl(slug) {
|
|
167
|
+
return runtime.detailUrl(detailPattern, slug);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function tagHref(tag) {
|
|
171
|
+
return listingPath + '?tag=' + encodeURIComponent(tag);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function loadArticles() {
|
|
175
|
+
return runtime.loadWritingIndex(collection);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function sortItems(a, b, sortKey) {
|
|
179
|
+
if (sortKey === 'random') {
|
|
180
|
+
var ak = (a && (a.slug || a.key)) || '';
|
|
181
|
+
var bk = (b && (b.slug || b.key)) || '';
|
|
182
|
+
return runtime.sessionRandomCompare(ak, bk, collection);
|
|
183
|
+
}
|
|
184
|
+
if (sortKey === 'updated') {
|
|
185
|
+
var au = (a.front_matter && a.front_matter.last_updated) || a.last_updated || (a.front_matter && a.front_matter.date) || a.date || '';
|
|
186
|
+
var bu = (b.front_matter && b.front_matter.last_updated) || b.last_updated || (b.front_matter && b.front_matter.date) || b.date || '';
|
|
187
|
+
return String(bu).localeCompare(String(au));
|
|
188
|
+
}
|
|
189
|
+
var ad = (a.front_matter && a.front_matter.date) || a.date || '';
|
|
190
|
+
var bd = (b.front_matter && b.front_matter.date) || b.date || '';
|
|
191
|
+
return String(bd).localeCompare(String(ad));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* Tab counts are computed from the index, never authored, so a tab can
|
|
195
|
+
* never promise more entries than the archive holds. */
|
|
196
|
+
function tagCounts(articles) {
|
|
197
|
+
var counts = {};
|
|
198
|
+
articles.forEach(function (article) {
|
|
199
|
+
var list = (article.front_matter && article.front_matter.tags) || [];
|
|
200
|
+
(Array.isArray(list) ? list : []).forEach(function (tag) {
|
|
201
|
+
if (!tag) return;
|
|
202
|
+
counts[tag] = (counts[tag] || 0) + 1;
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
return counts;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function buildChipGroups(tags, counts, total) {
|
|
209
|
+
return [
|
|
210
|
+
{
|
|
211
|
+
key: 'tag',
|
|
212
|
+
label: 'Topic',
|
|
213
|
+
options: [{ value: null, label: 'All (' + total + ')' }].concat(tags.map(function (tag) {
|
|
214
|
+
return { value: tag, label: tag + ' (' + counts[tag] + ')' };
|
|
215
|
+
}))
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
key: 'sort',
|
|
219
|
+
label: 'Order by',
|
|
220
|
+
options: [
|
|
221
|
+
{ value: 'published', label: 'Date published' },
|
|
222
|
+
{ value: 'updated', label: 'Last revised' },
|
|
223
|
+
{ value: 'random', label: 'Random' }
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function visibleArticles(state) {
|
|
230
|
+
return runtime.ListingFilter.filterItems(allArticles, state, { sortItems: sortItems });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function thumbnailOf(article) {
|
|
234
|
+
var value = (article && article.front_matter && article.front_matter.thumbnail) || '';
|
|
235
|
+
return String(value).trim();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* The default order is by revision, so a list showing publication dates
|
|
239
|
+
* alone reads as unsorted. `last_updated` is a real exported field: show
|
|
240
|
+
* it beside the publication date whenever the two differ, and nothing at
|
|
241
|
+
* all when they do not. */
|
|
242
|
+
function revisionPart(fm) {
|
|
243
|
+
var revised = runtime.formatDate(fm.last_updated, 'en');
|
|
244
|
+
if (!revised || revised === runtime.formatDate(fm.date, 'en')) return '';
|
|
245
|
+
return '<span class="writing-revised">Revised ' + runtime.escapeHtml(revised) + '</span>';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function setHidden(el, hidden) {
|
|
249
|
+
if (!el) return;
|
|
250
|
+
el.classList.toggle('hidden', !!hidden);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function renderListing(state) {
|
|
254
|
+
Array.from(grid.querySelectorAll('[data-dynamic-article]')).forEach(function (el) { el.remove(); });
|
|
255
|
+
if (emptyState) emptyState.remove();
|
|
256
|
+
if (leadRoot) leadRoot.innerHTML = '';
|
|
257
|
+
|
|
258
|
+
var visible = visibleArticles(state);
|
|
259
|
+
if (orderNote) orderNote.textContent = SORT_LABELS[state.sort] || SORT_LABELS.updated;
|
|
260
|
+
if (tallyNote) {
|
|
261
|
+
tallyNote.textContent = visible.length === 1 ? '1 entry' : visible.length + ' entries';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (visible.length === 0) {
|
|
265
|
+
setHidden(noResults, false);
|
|
266
|
+
if (controls) controls.classList.add('hidden');
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
setHidden(noResults, true);
|
|
270
|
+
|
|
271
|
+
/* The featured entry is dropped from the archive so nothing is listed
|
|
272
|
+
* twice — but only when the current selection actually contains it. The
|
|
273
|
+
* pick is a standing one, held for the session, so a tag filter that
|
|
274
|
+
* excludes it leaves the panel alone and takes nothing out of the list.
|
|
275
|
+
* Later pages are archive only. */
|
|
276
|
+
var rest = visible.slice();
|
|
277
|
+
if (featured) {
|
|
278
|
+
var at = rest.findIndex(function (item) { return item.slug === featured.slug; });
|
|
279
|
+
if (at !== -1) rest.splice(at, 1);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
var totalPages = Math.max(1, Math.ceil(rest.length / perPage));
|
|
283
|
+
if (currentPage > totalPages) currentPage = totalPages;
|
|
284
|
+
if (currentPage === 1) renderLead(featured);
|
|
285
|
+
|
|
286
|
+
rest.slice((currentPage - 1) * perPage, currentPage * perPage).forEach(renderRow);
|
|
287
|
+
listing.renderPagination(controls, currentPage, totalPages);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* A thumbnail that 404s would leave a hole where the image column is, so
|
|
291
|
+
* the frame removes itself and the panel or row falls back to the
|
|
292
|
+
* typographic layout it has without one. */
|
|
293
|
+
function dropPlateOnError(scope, frame, illustratedClass) {
|
|
294
|
+
var image = frame && frame.querySelector('img');
|
|
295
|
+
if (!image) return;
|
|
296
|
+
image.addEventListener('error', function () {
|
|
297
|
+
frame.remove();
|
|
298
|
+
scope.classList.remove(illustratedClass);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/* Lead panel. Binds title, date, revision, tags, description and the
|
|
303
|
+
* thumbnail — the same strip as the rows below it, language pill included
|
|
304
|
+
* in the omission. The mockup's topology figure, its DOI/arXiv line and its
|
|
305
|
+
* "Critical Notes" list have no field beNewsreader them, so none is rendered.
|
|
306
|
+
*
|
|
307
|
+
* The panel reads down the left and stops on the action: kicker, ledger
|
|
308
|
+
* line, title, summary, tags, then "Read article" — with the plate holding
|
|
309
|
+
* the right. The action used to sit on the same line as the tag chips,
|
|
310
|
+
* hard right, which put the one thing to press furthest from the sentence
|
|
311
|
+
* that argues for pressing it. */
|
|
312
|
+
function renderLead(article) {
|
|
313
|
+
if (!leadRoot || !article) return;
|
|
314
|
+
var fm = article.front_matter || {};
|
|
315
|
+
var href = entryUrl(article.slug);
|
|
316
|
+
var thumbnail = thumbnailOf(article);
|
|
317
|
+
var meta = runtime.renderMetaStrip([
|
|
318
|
+
runtime.formatDate(fm.date, 'en'),
|
|
319
|
+
revisionPart(fm)
|
|
320
|
+
]);
|
|
321
|
+
|
|
322
|
+
var html = '<article class="writing-lead' + (thumbnail ? ' writing-lead--illustrated' : '') + '">';
|
|
323
|
+
html += '<span class="shard-accent writing-lead-shard" aria-hidden="true"></span>';
|
|
324
|
+
html += '<span class="shard-accent shard-accent-beta writing-lead-shard writing-lead-shard--beta" aria-hidden="true"></span>';
|
|
325
|
+
html += '<div class="writing-lead-inner">';
|
|
326
|
+
html += '<div class="writing-lead-body">';
|
|
327
|
+
html += '<div class="writing-lead-rail">';
|
|
328
|
+
html += '<span class="ledger-chip writing-lead-featured">Featured</span>';
|
|
329
|
+
if (meta) html += meta;
|
|
330
|
+
html += '</div>';
|
|
331
|
+
/* Thin category rule under the meta strip — accent hairline only; tags
|
|
332
|
+
remain in the foot chips. */
|
|
333
|
+
html += '<div class="writing-lead-category-bar" aria-hidden="true"></div>';
|
|
334
|
+
html += '<h2 class="section-title writing-lead-title"><a href="' +
|
|
335
|
+
runtime.escapeAttr(href) + '">' +
|
|
336
|
+
runtime.escapeHtml(fm.title || article.slug) + '</a></h2>';
|
|
337
|
+
|
|
338
|
+
var preview = runtime.listingPreview(article);
|
|
339
|
+
if (preview) {
|
|
340
|
+
html += '<p class="writing-lead-copy font-body">' +
|
|
341
|
+
runtime.escapeHtml(preview) + '</p>';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
var chips = runtime.renderTagChips(fm.tags, { hrefFor: tagHref });
|
|
345
|
+
var cta = runtime.renderCtaLink(href, 'Read article', { className: 'writing-lead-cta' });
|
|
346
|
+
html += '<div class="writing-lead-foot">' + chips + cta + '</div>';
|
|
347
|
+
html += '</div>';
|
|
348
|
+
|
|
349
|
+
if (thumbnail) {
|
|
350
|
+
html += '<div class="writing-lead-media">' +
|
|
351
|
+
runtime.renderPosterMedia(runtime.buildUrl(thumbnail), fm.title || article.slug) +
|
|
352
|
+
'</div>';
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
html += '</div></article>';
|
|
356
|
+
leadRoot.innerHTML = html;
|
|
357
|
+
|
|
358
|
+
var panel = leadRoot.querySelector('.writing-lead');
|
|
359
|
+
if (panel) {
|
|
360
|
+
dropPlateOnError(panel, panel.querySelector('.writing-lead-media'), 'writing-lead--illustrated');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* Archive entry. The shared ledger row carries exactly the fields that
|
|
365
|
+
* exist: title, description, tag chips, link, and the strip this page
|
|
366
|
+
* hands it — the date it was published and the day it was last revised.
|
|
367
|
+
* The language pill is not among them: every article here is in English,
|
|
368
|
+
* so on this page the pill separated the entries that declare the field
|
|
369
|
+
* from the entries that do not, which is a fact about the front matter
|
|
370
|
+
* and not about the writing. The ordinal, the reading time and the
|
|
371
|
+
* "MONOGRAPH #42" stamp the mockup drew around it are backed by nothing
|
|
372
|
+
* and are gone. The plate on the right is the `thumbnail` field, and only
|
|
373
|
+
* the entries that declare one get it. */
|
|
374
|
+
function renderRow(article) {
|
|
375
|
+
var fm = article.front_matter || {};
|
|
376
|
+
var row = runtime.renderLedgerRow(article, {
|
|
377
|
+
href: entryUrl(article.slug),
|
|
378
|
+
locale: 'en',
|
|
379
|
+
tagHref: tagHref,
|
|
380
|
+
ctaLabel: 'Read article',
|
|
381
|
+
metaParts: [
|
|
382
|
+
runtime.formatDate(fm.date, 'en'),
|
|
383
|
+
revisionPart(fm)
|
|
384
|
+
]
|
|
385
|
+
});
|
|
386
|
+
row.setAttribute('data-dynamic-article', 'true');
|
|
387
|
+
|
|
388
|
+
var thumbnail = thumbnailOf(article);
|
|
389
|
+
if (thumbnail) {
|
|
390
|
+
var body = document.createElement('div');
|
|
391
|
+
body.className = 'writing-row-body';
|
|
392
|
+
while (row.firstChild) body.appendChild(row.firstChild);
|
|
393
|
+
row.appendChild(body);
|
|
394
|
+
row.insertAdjacentHTML('beforeend',
|
|
395
|
+
runtime.renderPosterMedia(runtime.buildUrl(thumbnail), fm.title || article.slug));
|
|
396
|
+
row.classList.add('writing-row--illustrated');
|
|
397
|
+
dropPlateOnError(row, row.querySelector('.ledger-poster'), 'writing-row--illustrated');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
grid.insertBefore(row, noResults);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function showListing() {
|
|
404
|
+
if (listingRoot) listingRoot.classList.remove('hidden');
|
|
405
|
+
if (detailRoot) detailRoot.classList.add('hidden');
|
|
406
|
+
if (pageHeader) pageHeader.classList.remove('hidden');
|
|
407
|
+
if (filterUi) filterUi.setVisible(true);
|
|
408
|
+
if (detailMain) detailMain.innerHTML = '';
|
|
409
|
+
if (detailSidebar) detailSidebar.innerHTML = '';
|
|
410
|
+
runtime.removeRuntimeBackLink();
|
|
411
|
+
runtime.removeArticleToc();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function renderArticleDetail(article, articles) {
|
|
415
|
+
showListing();
|
|
416
|
+
if (listingRoot) listingRoot.classList.add('hidden');
|
|
417
|
+
if (pageHeader) pageHeader.classList.add('hidden');
|
|
418
|
+
if (filterUi) filterUi.setVisible(false);
|
|
419
|
+
if (detailRoot) detailRoot.classList.remove('hidden');
|
|
420
|
+
|
|
421
|
+
var fm = article.front_matter || {};
|
|
422
|
+
var lang = fm.language || 'enus';
|
|
423
|
+
|
|
424
|
+
if (detailSidebar) {
|
|
425
|
+
detailSidebar.innerHTML = runtime.buildTextSidebar({
|
|
426
|
+
frontMatter: fm,
|
|
427
|
+
fallbackTitle: article.slug,
|
|
428
|
+
dateHtml: runtime.renderDateEvolution(fm.date, fm.last_updated, 'en')
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
var index = (articles || []).findIndex(function (item) { return item.slug === article.slug; });
|
|
433
|
+
|
|
434
|
+
await runtime.renderRuntimeDetailSection({
|
|
435
|
+
detailAttribute: 'data-dynamic-article-detail',
|
|
436
|
+
bodyMarkdown: article.body_markdown,
|
|
437
|
+
frontMatter: fm,
|
|
438
|
+
mainColumn: detailMain,
|
|
439
|
+
sidebarEl: detailSidebar,
|
|
440
|
+
footerNav: {
|
|
441
|
+
lang: lang,
|
|
442
|
+
previousUrl: index >= 0 && index < articles.length - 1 ? entryUrl(articles[index + 1].slug) : null,
|
|
443
|
+
nextUrl: index > 0 ? entryUrl(articles[index - 1].slug) : null
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function initFilter() {
|
|
449
|
+
// One entry per tag actually in use, so the rail never offers a filter
|
|
450
|
+
// that would return nothing.
|
|
451
|
+
var tags = Array.from(new Set(allArticles.reduce(function (acc, article) {
|
|
452
|
+
var list = (article.front_matter && article.front_matter.tags) || [];
|
|
453
|
+
return acc.concat(Array.isArray(list) ? list : []);
|
|
454
|
+
}, []).filter(Boolean))).sort();
|
|
455
|
+
|
|
456
|
+
filterUi = runtime.ListingFilter.create({
|
|
457
|
+
root: filterRoot,
|
|
458
|
+
initialQuery: params.get('q') || '',
|
|
459
|
+
initialTag: params.get('tag') || params.get('category') || null,
|
|
460
|
+
initialSort: params.get('sort') || 'published',
|
|
461
|
+
defaultSort: 'published',
|
|
462
|
+
tagParam: 'tag',
|
|
463
|
+
chipGroups: buildChipGroups(tags, tagCounts(allArticles), allArticles.length),
|
|
464
|
+
onChange: function (state) {
|
|
465
|
+
currentPage = 1;
|
|
466
|
+
renderListing(state);
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
if (filterRoot) filterRoot.hidden = false;
|
|
470
|
+
renderListing(filterUi.getState());
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/* Citation and reuse, the rail's last group. Every word is the exported
|
|
474
|
+
* licence record; the page states no terms of its own, and a manifest
|
|
475
|
+
* without a licence leaves no block. Twin of the rascunhos copy — the two
|
|
476
|
+
* listings wear the same sidebar, each in its own language. */
|
|
477
|
+
function mountLicence(manifest) {
|
|
478
|
+
if (!licenceEl) return;
|
|
479
|
+
var licence = manifest && manifest.content_license;
|
|
480
|
+
if (!licence || !licence.name) return;
|
|
481
|
+
|
|
482
|
+
var parts = ['<span class="writing-rail-licence-label font-label">Licensing & reuse</span>'];
|
|
483
|
+
var named = licence.id ? licence.name + ' (' + licence.id + ')' : licence.name;
|
|
484
|
+
parts.push('<p class="writing-rail-licence-copy font-body">Articles published under ' +
|
|
485
|
+
runtime.escapeHtml(named) + '.</p>');
|
|
486
|
+
if (licence.rights_holder) {
|
|
487
|
+
parts.push('<p class="writing-rail-licence-copy font-body">Attribution to ' +
|
|
488
|
+
runtime.escapeHtml(licence.rights_holder) + '.</p>');
|
|
489
|
+
}
|
|
490
|
+
var href = licence.page_path || licence.url;
|
|
491
|
+
if (href) parts.push(runtime.renderCtaLink(href, 'Licence terms'));
|
|
492
|
+
|
|
493
|
+
licenceEl.innerHTML = parts.join('');
|
|
494
|
+
licenceEl.hidden = false;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function mountRailLicence() {
|
|
498
|
+
return runtime.loadJson('manifest.json')
|
|
499
|
+
.then(mountLicence)
|
|
500
|
+
.catch(function (error) { console.error(error); });
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
loadArticles().then(function (items) {
|
|
504
|
+
allArticles = items.slice();
|
|
505
|
+
|
|
506
|
+
var selected = selectedArticle && runtime.resolveWritingAlias(allArticles, selectedArticle);
|
|
507
|
+
if (selectedArticle) {
|
|
508
|
+
if (selected && selected.slug && selected.slug !== selectedArticle) {
|
|
509
|
+
runtime.adoptCanonicalDetailUrl(selected);
|
|
510
|
+
}
|
|
511
|
+
var slugToLoad = selected ? selected.slug : selectedArticle;
|
|
512
|
+
return runtime.loadCollectionItem(collection, slugToLoad).then(function (article) {
|
|
513
|
+
return renderArticleDetail(article, allArticles);
|
|
514
|
+
}).catch(function (error) {
|
|
515
|
+
runtime.reportDetailLoadError(error, {
|
|
516
|
+
emptyState: emptyState,
|
|
517
|
+
grid: grid,
|
|
518
|
+
noResults: noResults,
|
|
519
|
+
message: 'This article could not be loaded right now.'
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
featured = runtime.pickFeatured(allArticles);
|
|
525
|
+
initFilter();
|
|
526
|
+
return mountRailLicence();
|
|
527
|
+
}).catch(function (error) {
|
|
528
|
+
console.error(error);
|
|
529
|
+
if (emptyState) {
|
|
530
|
+
emptyState.querySelector('p').textContent = 'Articles could not be loaded right now.';
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
})();
|
|
534
|
+
</script>
|