@omega.js/web 0.1.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.
- package/LICENSE +98 -0
- package/README.md +640 -0
- package/bin/omega +2 -0
- package/bin/omega-web +2 -0
- package/bin/omg +2 -0
- package/core/_includes/admin/sections/sidebar.json +100 -0
- package/core/_includes/admin/sections/topbar.json +11 -0
- package/core/_includes/backend/sections/sidebar.json +39 -0
- package/core/_includes/backend/sections/topbar.json +16 -0
- package/core/_includes/core/body.html +102 -0
- package/core/_includes/core/foot.html +536 -0
- package/core/_includes/core/head.html +423 -0
- package/core/_includes/core/pricing/price-per-unit.html +33 -0
- package/core/_includes/core/showcase/entry-body.html +94 -0
- package/core/_includes/core/showcase/index-body.html +43 -0
- package/core/_includes/core/showcase/nav.html +60 -0
- package/core/_includes/frontend/sections/footer.json +74 -0
- package/core/_includes/frontend/sections/nav.json +70 -0
- package/core/_includes/global/sections/account.json +31 -0
- package/core/_includes/modules/engagement/giscus.html +26 -0
- package/core/_layouts/blueprint/404.html +10 -0
- package/core/_layouts/blueprint/about.html +10 -0
- package/core/_layouts/blueprint/account/index.html +11 -0
- package/core/_layouts/blueprint/admin/calendar/index.html +590 -0
- package/core/_layouts/blueprint/admin/dashboard/index.html +301 -0
- package/core/_layouts/blueprint/admin/firebase/index.html +214 -0
- package/core/_layouts/blueprint/admin/posts/editor.html +157 -0
- package/core/_layouts/blueprint/admin/posts/index.html +126 -0
- package/core/_layouts/blueprint/admin/stackblitz/index.html +30 -0
- package/core/_layouts/blueprint/admin/studio/index.html +85 -0
- package/core/_layouts/blueprint/admin/users/index.html +272 -0
- package/core/_layouts/blueprint/admin/users/new.html +82 -0
- package/core/_layouts/blueprint/admin/verts/index.html +204 -0
- package/core/_layouts/blueprint/alternatives/alternative.html +15 -0
- package/core/_layouts/blueprint/alternatives/index.html +10 -0
- package/core/_layouts/blueprint/app.html +10 -0
- package/core/_layouts/blueprint/auth/reset.html +19 -0
- package/core/_layouts/blueprint/auth/signin.html +19 -0
- package/core/_layouts/blueprint/auth/signup.html +19 -0
- package/core/_layouts/blueprint/auth/token.html +19 -0
- package/core/_layouts/blueprint/blog/categories/category.html +8 -0
- package/core/_layouts/blueprint/blog/categories/index.html +11 -0
- package/core/_layouts/blueprint/blog/index.html +10 -0
- package/core/_layouts/blueprint/blog/post.html +5 -0
- package/core/_layouts/blueprint/blog/tags/index.html +11 -0
- package/core/_layouts/blueprint/blog/tags/tag.html +8 -0
- package/core/_layouts/blueprint/careers.md +13 -0
- package/core/_layouts/blueprint/collection/category.html +9 -0
- package/core/_layouts/blueprint/collection/document.html +9 -0
- package/core/_layouts/blueprint/collection/index.html +9 -0
- package/core/_layouts/blueprint/connections/callback.html +11 -0
- package/core/_layouts/blueprint/contact.html +14 -0
- package/core/_layouts/blueprint/dashboard/index.html +10 -0
- package/core/_layouts/blueprint/download.html +20 -0
- package/core/_layouts/blueprint/extension/index.html +14 -0
- package/core/_layouts/blueprint/extension/installed.html +11 -0
- package/core/_layouts/blueprint/feedback.html +10 -0
- package/core/_layouts/blueprint/index.html +16 -0
- package/core/_layouts/blueprint/legal/cookies.md +57 -0
- package/core/_layouts/blueprint/legal/privacy.md +109 -0
- package/core/_layouts/blueprint/legal/terms.md +155 -0
- package/core/_layouts/blueprint/payment/checkout.html +15 -0
- package/core/_layouts/blueprint/payment/confirmation.html +14 -0
- package/core/_layouts/blueprint/portal/email-preferences.html +15 -0
- package/core/_layouts/blueprint/pricing.html +20 -0
- package/core/_layouts/blueprint/status.html +10 -0
- package/core/_layouts/blueprint/team/index.html +10 -0
- package/core/_layouts/blueprint/team/member.html +5 -0
- package/core/_layouts/blueprint/updates/index.html +10 -0
- package/core/_layouts/blueprint/updates/update.html +10 -0
- package/core/_layouts/core/root.html +45 -0
- package/core/_layouts/modules/utilities/redirect.html +129 -0
- package/core/css/_studio.scss +237 -0
- package/core/css/components/_index.scss +146 -0
- package/core/css/core/_.scss +0 -0
- package/core/css/core/_alert.scss +87 -0
- package/core/css/core/_animations.scss +343 -0
- package/core/css/core/_bindings.scss +116 -0
- package/core/css/core/_blog-search.scss +57 -0
- package/core/css/core/_chatsy.scss +61 -0
- package/core/css/core/_consent.scss +424 -0
- package/core/css/core/_custom-font-awesome.scss +146 -0
- package/core/css/core/_exit-popup.scss +42 -0
- package/core/css/core/_generated-badge.scss +33 -0
- package/core/css/core/_initialize.scss +29 -0
- package/core/css/core/_lazy-loading.scss +104 -0
- package/core/css/core/_social-sharing.scss +41 -0
- package/core/css/core/_tones.scss +32 -0
- package/core/css/core/_utilities.scss +157 -0
- package/core/css/main.scss +54 -0
- package/core/css/motion/_index.scss +368 -0
- package/core/css/pages/404/index.scss +0 -0
- package/core/css/pages/admin/calendar/index.scss +692 -0
- package/core/css/pages/admin/firebase/index.scss +33 -0
- package/core/css/pages/admin/index.scss +9 -0
- package/core/css/pages/admin/users/index.scss +9 -0
- package/core/css/pages/admin/verts/index.scss +9 -0
- package/core/css/pages/alternatives/[alternative]/index.scss +0 -0
- package/core/css/pages/blog/[slug].scss +55 -0
- package/core/css/pages/dashboard/account/index.scss +242 -0
- package/core/css/pages/download/index.scss +480 -0
- package/core/css/pages/extension/installed/index.scss +317 -0
- package/core/css/pages/feedback/index.scss +31 -0
- package/core/css/pages/payment/confirmation/index.scss +14 -0
- package/core/css/pages/pricing/index.scss +0 -0
- package/core/css/pages/status/index.scss +162 -0
- package/core/css/pages/test/libraries/layers/index.scss +28 -0
- package/core/css/pages/updates/[update].scss +46 -0
- package/core/css/pages/updates/index.scss +13 -0
- package/core/css/shell/_index.scss +207 -0
- package/core/css/tokens/_index.scss +166 -0
- package/core/icons/flags/ar.svg +1 -0
- package/core/icons/flags/bd.svg +1 -0
- package/core/icons/flags/ca.svg +1 -0
- package/core/icons/flags/ch.svg +1 -0
- package/core/icons/flags/cn.svg +1 -0
- package/core/icons/flags/co.svg +1 -0
- package/core/icons/flags/cw.svg +1 -0
- package/core/icons/flags/cz.svg +1 -0
- package/core/icons/flags/de.svg +1 -0
- package/core/icons/flags/dk.svg +1 -0
- package/core/icons/flags/eg.svg +1 -0
- package/core/icons/flags/es.svg +1 -0
- package/core/icons/flags/fi.svg +1 -0
- package/core/icons/flags/fr.svg +1 -0
- package/core/icons/flags/gb.svg +1 -0
- package/core/icons/flags/gq.svg +1 -0
- package/core/icons/flags/gr.svg +1 -0
- package/core/icons/flags/gt.svg +1 -0
- package/core/icons/flags/hn.svg +1 -0
- package/core/icons/flags/id.svg +1 -0
- package/core/icons/flags/ie.svg +1 -0
- package/core/icons/flags/in.svg +1 -0
- package/core/icons/flags/ir.svg +1 -0
- package/core/icons/flags/it.svg +1 -0
- package/core/icons/flags/jp.svg +1 -0
- package/core/icons/flags/kp.svg +1 -0
- package/core/icons/flags/kr.svg +1 -0
- package/core/icons/flags/ly.svg +1 -0
- package/core/icons/flags/ma.svg +1 -0
- package/core/icons/flags/mu.svg +1 -0
- package/core/icons/flags/mx.svg +1 -0
- package/core/icons/flags/ng.svg +1 -0
- package/core/icons/flags/pa.svg +1 -0
- package/core/icons/flags/ph.svg +1 -0
- package/core/icons/flags/pk.svg +1 -0
- package/core/icons/flags/pt.svg +1 -0
- package/core/icons/flags/ru.svg +1 -0
- package/core/icons/flags/sa.svg +1 -0
- package/core/icons/flags/sd.svg +1 -0
- package/core/icons/flags/se.svg +1 -0
- package/core/icons/flags/th.svg +1 -0
- package/core/icons/flags/tm.svg +1 -0
- package/core/icons/flags/tr.svg +1 -0
- package/core/icons/flags/us.svg +1 -0
- package/core/icons/flags/uy.svg +1 -0
- package/core/icons/flags/vn.svg +1 -0
- package/core/icons/flags/za.svg +1 -0
- package/core/images/exit-popup/subscriber-1.jpg +0 -0
- package/core/images/exit-popup/subscriber-2.jpg +0 -0
- package/core/images/exit-popup/subscriber-3.jpg +0 -0
- package/core/images/exit-popup/subscriber-4.jpg +0 -0
- package/core/images/placeholder/photo-1.jpg +0 -0
- package/core/images/placeholder/photo-2.jpg +0 -0
- package/core/images/placeholder/photo-3.jpg +0 -0
- package/core/js/core/analytics-loader.js +253 -0
- package/core/js/core/app-shell.js +136 -0
- package/core/js/core/appearance.js +195 -0
- package/core/js/core/auth.js +457 -0
- package/core/js/core/complete.js +72 -0
- package/core/js/core/consent.js +384 -0
- package/core/js/core/dev-icon-audit.js +63 -0
- package/core/js/core/dev-palette.js +670 -0
- package/core/js/core/dev-sections.js +53 -0
- package/core/js/core/exit-popup.js +276 -0
- package/core/js/core/initialize.js +8 -0
- package/core/js/core/language-switcher.js +167 -0
- package/core/js/core/lazy-loading.js +479 -0
- package/core/js/core/motion.js +28 -0
- package/core/js/core/query-strings.js +146 -0
- package/core/js/core/service-worker.js +28 -0
- package/core/js/core/social-sharing.js +293 -0
- package/core/js/first-paint.js +35 -0
- package/core/js/layouts/modules/utilities/redirect.js +122 -0
- package/core/js/libs/admin-helpers.js +62 -0
- package/core/js/libs/alert-dismiss.js +35 -0
- package/core/js/libs/analytics.js +368 -0
- package/core/js/libs/auth/email.js +144 -0
- package/core/js/libs/auth/errors.js +119 -0
- package/core/js/libs/auth/forms.js +206 -0
- package/core/js/libs/auth/index.js +181 -0
- package/core/js/libs/auth/oauth.js +394 -0
- package/core/js/libs/auth/orphan.js +110 -0
- package/core/js/libs/auth/password-toggle.js +44 -0
- package/core/js/libs/auth/session-params.js +266 -0
- package/core/js/libs/auth/tracking.js +41 -0
- package/core/js/libs/charts.js +516 -0
- package/core/js/libs/consent-region.js +127 -0
- package/core/js/libs/dev.js +150 -0
- package/core/js/libs/graph.js +201 -0
- package/core/js/libs/initialize-tooltips.js +31 -0
- package/core/js/libs/logger.js +38 -0
- package/core/js/libs/omega-copy.js +89 -0
- package/core/js/libs/path-prefix.js +44 -0
- package/core/js/libs/payment-config.js +76 -0
- package/core/js/libs/recaptcha.js +94 -0
- package/core/js/libs/sale-name.js +295 -0
- package/core/js/libs/showcase-frames.js +37 -0
- package/core/js/libs/srcset.js +69 -0
- package/core/js/libs/tracking-consent.js +161 -0
- package/core/js/main.js +144 -0
- package/core/js/pages/404/index.js +57 -0
- package/core/js/pages/admin/calendar/calendar-core.js +701 -0
- package/core/js/pages/admin/calendar/calendar-events.js +853 -0
- package/core/js/pages/admin/calendar/calendar-renderer.js +807 -0
- package/core/js/pages/admin/calendar/campaign-preview.js +105 -0
- package/core/js/pages/admin/calendar/index.js +40 -0
- package/core/js/pages/admin/firebase/index.js +727 -0
- package/core/js/pages/admin/index.js +531 -0
- package/core/js/pages/admin/posts/editor/index.js +230 -0
- package/core/js/pages/admin/posts/index.js +237 -0
- package/core/js/pages/admin/studio/index.js +538 -0
- package/core/js/pages/admin/users/index.js +669 -0
- package/core/js/pages/admin/users/new/index.js +65 -0
- package/core/js/pages/admin/verts/index.js +407 -0
- package/core/js/pages/alternatives/[alternative]/index.js +17 -0
- package/core/js/pages/app/index.js +135 -0
- package/core/js/pages/blog/[slug].js +132 -0
- package/core/js/pages/blog/_search.mjs +108 -0
- package/core/js/pages/blog/index.js +193 -0
- package/core/js/pages/blog/page/[num]/index.js +7 -0
- package/core/js/pages/connections/callback/index.js +148 -0
- package/core/js/pages/contact/index.js +170 -0
- package/core/js/pages/cookies.js +2 -0
- package/core/js/pages/dashboard/account/index.js +366 -0
- package/core/js/pages/dashboard/account/modules/orders.js +82 -0
- package/core/js/pages/dashboard/account/sections/api-keys.js +149 -0
- package/core/js/pages/dashboard/account/sections/billing.js +1667 -0
- package/core/js/pages/dashboard/account/sections/connections.js +333 -0
- package/core/js/pages/dashboard/account/sections/data-request.js +225 -0
- package/core/js/pages/dashboard/account/sections/delete.js +80 -0
- package/core/js/pages/dashboard/account/sections/notifications.js +167 -0
- package/core/js/pages/dashboard/account/sections/orders.js +151 -0
- package/core/js/pages/dashboard/account/sections/profile.js +200 -0
- package/core/js/pages/dashboard/account/sections/referrals.js +217 -0
- package/core/js/pages/dashboard/account/sections/refund.js +253 -0
- package/core/js/pages/dashboard/account/sections/security.js +726 -0
- package/core/js/pages/dashboard/account/sections/team.js +247 -0
- package/core/js/pages/download/index.js +418 -0
- package/core/js/pages/extension/index.js +164 -0
- package/core/js/pages/extension/installed/index.js +135 -0
- package/core/js/pages/feedback/index.js +193 -0
- package/core/js/pages/legal/_document.js +91 -0
- package/core/js/pages/payment/checkout/index.js +420 -0
- package/core/js/pages/payment/checkout/modules/api.js +139 -0
- package/core/js/pages/payment/checkout/modules/dev-section.js +175 -0
- package/core/js/pages/payment/checkout/modules/discount.js +69 -0
- package/core/js/pages/payment/checkout/modules/pricing.js +73 -0
- package/core/js/pages/payment/checkout/modules/state.js +339 -0
- package/core/js/pages/payment/checkout/modules/tracking.js +54 -0
- package/core/js/pages/payment/confirmation/index.js +91 -0
- package/core/js/pages/payment/confirmation/modules/celebration.js +45 -0
- package/core/js/pages/payment/confirmation/modules/state.js +112 -0
- package/core/js/pages/payment/confirmation/modules/tracking.js +133 -0
- package/core/js/pages/payment/confirmation/modules/verify.js +142 -0
- package/core/js/pages/portal/email-preferences/index.js +183 -0
- package/core/js/pages/pricing/index.js +425 -0
- package/core/js/pages/privacy.js +2 -0
- package/core/js/pages/reset/index.js +12 -0
- package/core/js/pages/signin/index.js +22 -0
- package/core/js/pages/signup/index.js +23 -0
- package/core/js/pages/status/index.js +687 -0
- package/core/js/pages/terms.js +2 -0
- package/core/js/pages/test/components/[category]/[name]/index.js +26 -0
- package/core/js/pages/test/libraries/appearance/index.js +220 -0
- package/core/js/pages/test/libraries/form-manager/index.js +328 -0
- package/core/js/pages/test/libraries/layers/index.js +16 -0
- package/core/js/pages/test/libraries/verts/index.js +68 -0
- package/core/js/pages/test/sections/[category]/[name]/index.js +28 -0
- package/core/js/pages/test/styleguide/index.js +35 -0
- package/core/js/pages/token/index.js +203 -0
- package/core/logos/brandmarks/original/apple-app-store.svg +1 -0
- package/core/logos/brandmarks/original/apple.svg +6 -0
- package/core/logos/brandmarks/original/bitbucket.svg +14 -0
- package/core/logos/brandmarks/original/bitcoin.svg +1 -0
- package/core/logos/brandmarks/original/bluesky.svg +7 -0
- package/core/logos/brandmarks/original/bootstrap.svg +43 -0
- package/core/logos/brandmarks/original/brave.svg +1 -0
- package/core/logos/brandmarks/original/chrome.svg +1 -0
- package/core/logos/brandmarks/original/discord.svg +1 -0
- package/core/logos/brandmarks/original/dropbox.svg +10 -0
- package/core/logos/brandmarks/original/edge.svg +1 -0
- package/core/logos/brandmarks/original/facebook-f.svg +1 -0
- package/core/logos/brandmarks/original/facebook.svg +8 -0
- package/core/logos/brandmarks/original/figma.svg +10 -0
- package/core/logos/brandmarks/original/firefox.svg +1 -0
- package/core/logos/brandmarks/original/github.svg +6 -0
- package/core/logos/brandmarks/original/gitlab.svg +17 -0
- package/core/logos/brandmarks/original/google-a-s.svg +1 -0
- package/core/logos/brandmarks/original/google-chrome-webstore.svg +49 -0
- package/core/logos/brandmarks/original/google-play-store.svg +1 -0
- package/core/logos/brandmarks/original/google-web-search.svg +1 -0
- package/core/logos/brandmarks/original/google.svg +1 -0
- package/core/logos/brandmarks/original/gulp.svg +4 -0
- package/core/logos/brandmarks/original/html-5.svg +1 -0
- package/core/logos/brandmarks/original/instagram.svg +1 -0
- package/core/logos/brandmarks/original/javascript.svg +1 -0
- package/core/logos/brandmarks/original/jira.svg +18 -0
- package/core/logos/brandmarks/original/kick.svg +7 -0
- package/core/logos/brandmarks/original/linkedin.svg +1 -0
- package/core/logos/brandmarks/original/linux.svg +28 -0
- package/core/logos/brandmarks/original/messenger.svg +16 -0
- package/core/logos/brandmarks/original/microsoft.svg +1 -0
- package/core/logos/brandmarks/original/npm.svg +8 -0
- package/core/logos/brandmarks/original/opera.svg +1 -0
- package/core/logos/brandmarks/original/patreon.svg +6 -0
- package/core/logos/brandmarks/original/pinterest.svg +6 -0
- package/core/logos/brandmarks/original/reddit.svg +1 -0
- package/core/logos/brandmarks/original/safari.svg +31 -0
- package/core/logos/brandmarks/original/sass.svg +3 -0
- package/core/logos/brandmarks/original/signal.svg +6 -0
- package/core/logos/brandmarks/original/skype.svg +47 -0
- package/core/logos/brandmarks/original/slack.svg +33 -0
- package/core/logos/brandmarks/original/soundcloud.svg +1 -0
- package/core/logos/brandmarks/original/spotify.svg +5 -0
- package/core/logos/brandmarks/original/steam.svg +6 -0
- package/core/logos/brandmarks/original/telegram.svg +14 -0
- package/core/logos/brandmarks/original/tiktok.svg +1 -0
- package/core/logos/brandmarks/original/trello.svg +16 -0
- package/core/logos/brandmarks/original/tumblr.svg +1 -0
- package/core/logos/brandmarks/original/twitch.svg +6 -0
- package/core/logos/brandmarks/original/twitter.svg +6 -0
- package/core/logos/brandmarks/original/unity.svg +6 -0
- package/core/logos/brandmarks/original/x.svg +7 -0
- package/core/logos/brandmarks/original/yahoo-y.svg +1 -0
- package/core/logos/brandmarks/original/youtube-square.svg +1 -0
- package/core/logos/brandmarks/original/youtube.svg +1 -0
- package/core/logos/combomarks/dark/airbnb.svg +30 -0
- package/core/logos/combomarks/dark/slack.svg +38 -0
- package/core/logos/combomarks/dark/spotify.svg +35 -0
- package/core/logos/combomarks/dark/twilio.svg +26 -0
- package/core/logos/combomarks/dark/weebly.svg +35 -0
- package/core/logos/combomarks/gray/airbnb.svg +33 -0
- package/core/logos/combomarks/gray/alphabet.svg +23 -0
- package/core/logos/combomarks/gray/amazon.svg +38 -0
- package/core/logos/combomarks/gray/boring-company.svg +91 -0
- package/core/logos/combomarks/gray/burberry.svg +239 -0
- package/core/logos/combomarks/gray/embark.svg +21 -0
- package/core/logos/combomarks/gray/fitbit.svg +61 -0
- package/core/logos/combomarks/gray/fred-perry.svg +94 -0
- package/core/logos/combomarks/gray/google.svg +27 -0
- package/core/logos/combomarks/gray/hollister.svg +34 -0
- package/core/logos/combomarks/gray/hubspot.svg +27 -0
- package/core/logos/combomarks/gray/kaplan.svg +33 -0
- package/core/logos/combomarks/gray/lenovo.svg +27 -0
- package/core/logos/combomarks/gray/levis.svg +25 -0
- package/core/logos/combomarks/gray/mapbox.svg +37 -0
- package/core/logos/combomarks/gray/netflix.svg +15 -0
- package/core/logos/combomarks/gray/new-balance.svg +32 -0
- package/core/logos/combomarks/gray/nike.svg +17 -0
- package/core/logos/combomarks/gray/paypal.svg +41 -0
- package/core/logos/combomarks/gray/puma.svg +82 -0
- package/core/logos/combomarks/gray/samsung.svg +26 -0
- package/core/logos/combomarks/gray/shopify.svg +36 -0
- package/core/logos/combomarks/gray/slack.svg +42 -0
- package/core/logos/combomarks/gray/spotify.svg +38 -0
- package/core/logos/combomarks/gray/stripe.svg +24 -0
- package/core/logos/combomarks/gray/tnf.svg +30 -0
- package/core/logos/combomarks/gray/twilio.svg +29 -0
- package/core/logos/combomarks/gray/uber.svg +24 -0
- package/core/logos/combomarks/gray/weebly.svg +38 -0
- package/core/logos/combomarks/original/airbnb.svg +33 -0
- package/core/logos/combomarks/original/alphabet.svg +23 -0
- package/core/logos/combomarks/original/amazon.svg +38 -0
- package/core/logos/combomarks/original/asana.svg +8 -0
- package/core/logos/combomarks/original/boring-company.svg +91 -0
- package/core/logos/combomarks/original/discord.svg +6 -0
- package/core/logos/combomarks/original/embark.svg +24 -0
- package/core/logos/combomarks/original/fitbit.svg +60 -0
- package/core/logos/combomarks/original/github.svg +6 -0
- package/core/logos/combomarks/original/gitlab.svg +20 -0
- package/core/logos/combomarks/original/google.svg +28 -0
- package/core/logos/combomarks/original/hubspot.svg +29 -0
- package/core/logos/combomarks/original/instagram.svg +7 -0
- package/core/logos/combomarks/original/kickstarter.svg +6 -0
- package/core/logos/combomarks/original/lenovo.svg +24 -0
- package/core/logos/combomarks/original/linkedin.svg +6 -0
- package/core/logos/combomarks/original/mapbox.svg +39 -0
- package/core/logos/combomarks/original/mastodon.svg +7 -0
- package/core/logos/combomarks/original/medium.svg +6 -0
- package/core/logos/combomarks/original/meta.svg +22 -0
- package/core/logos/combomarks/original/microsoft.svg +13 -0
- package/core/logos/combomarks/original/monday.svg +12 -0
- package/core/logos/combomarks/original/netflix.svg +15 -0
- package/core/logos/combomarks/original/notion.svg +9 -0
- package/core/logos/combomarks/original/paypal.svg +41 -0
- package/core/logos/combomarks/original/reddit.svg +13 -0
- package/core/logos/combomarks/original/samsung.svg +25 -0
- package/core/logos/combomarks/original/shopify.svg +35 -0
- package/core/logos/combomarks/original/slack.svg +47 -0
- package/core/logos/combomarks/original/spotify.svg +37 -0
- package/core/logos/combomarks/original/stackoverflow.svg +28 -0
- package/core/logos/combomarks/original/stripe.svg +24 -0
- package/core/logos/combomarks/original/tiktok.svg +12 -0
- package/core/logos/combomarks/original/tumblr.svg +6 -0
- package/core/logos/combomarks/original/uber.svg +24 -0
- package/core/logos/combomarks/original/weebly.svg +34 -0
- package/core/logos/combomarks/original/whatsapp.svg +20 -0
- package/core/logos/combomarks/original/youtube.svg +10 -0
- package/core/logos/combomarks/original/zoom.svg +7 -0
- package/core/logos/combomarks/white/airbnb.svg +33 -0
- package/core/logos/combomarks/white/alphabet.svg +23 -0
- package/core/logos/combomarks/white/amazon.svg +38 -0
- package/core/logos/combomarks/white/boring-company.svg +91 -0
- package/core/logos/combomarks/white/embark.svg +21 -0
- package/core/logos/combomarks/white/fitbit.svg +61 -0
- package/core/logos/combomarks/white/google.svg +27 -0
- package/core/logos/combomarks/white/hubspot.svg +27 -0
- package/core/logos/combomarks/white/kaplan.svg +33 -0
- package/core/logos/combomarks/white/lenovo.svg +27 -0
- package/core/logos/combomarks/white/mapbox.svg +37 -0
- package/core/logos/combomarks/white/netflix.svg +15 -0
- package/core/logos/combomarks/white/paypal.svg +40 -0
- package/core/logos/combomarks/white/samsung.svg +26 -0
- package/core/logos/combomarks/white/shopify.svg +36 -0
- package/core/logos/combomarks/white/slack.svg +42 -0
- package/core/logos/combomarks/white/spotify.svg +38 -0
- package/core/logos/combomarks/white/stripe.svg +24 -0
- package/core/logos/combomarks/white/twilio.svg +29 -0
- package/core/logos/combomarks/white/uber.svg +24 -0
- package/core/logos/combomarks/white/weebly.svg +38 -0
- package/defaults/pages/404.md +4 -0
- package/defaults/pages/about.md +4 -0
- package/defaults/pages/admin/calendar/index.html +4 -0
- package/defaults/pages/admin/firebase/index.html +4 -0
- package/defaults/pages/admin/index.md +4 -0
- package/defaults/pages/admin/posts/editor.html +4 -0
- package/defaults/pages/admin/posts/index.html +4 -0
- package/defaults/pages/admin/stackblitz/index.html +4 -0
- package/defaults/pages/admin/studio/index.html +4 -0
- package/defaults/pages/admin/users/index.html +4 -0
- package/defaults/pages/admin/users/new.html +4 -0
- package/defaults/pages/admin/verts/index.html +4 -0
- package/defaults/pages/ads-txt.html +11 -0
- package/defaults/pages/alternatives/index.md +4 -0
- package/defaults/pages/app.md +4 -0
- package/defaults/pages/auth/reset.md +4 -0
- package/defaults/pages/auth/signin.md +4 -0
- package/defaults/pages/auth/signup.md +4 -0
- package/defaults/pages/auth/token.md +4 -0
- package/defaults/pages/blog/categories/category.html +21 -0
- package/defaults/pages/blog/categories.md +4 -0
- package/defaults/pages/blog/index-json.html +27 -0
- package/defaults/pages/blog/tags/tag.html +21 -0
- package/defaults/pages/blog/tags.md +4 -0
- package/defaults/pages/blog.md +22 -0
- package/defaults/pages/careers.md +4 -0
- package/defaults/pages/connections/callback.md +4 -0
- package/defaults/pages/contact.md +4 -0
- package/defaults/pages/dashboard/account.md +4 -0
- package/defaults/pages/dashboard/index.md +12 -0
- package/defaults/pages/download.md +4 -0
- package/defaults/pages/extension/index.html +4 -0
- package/defaults/pages/extension/installed.html +4 -0
- package/defaults/pages/feedback.md +4 -0
- package/defaults/pages/feeds/posts-json.html +40 -0
- package/defaults/pages/feeds/posts-xml.html +38 -0
- package/defaults/pages/humans.html +25 -0
- package/defaults/pages/index.md +4 -0
- package/defaults/pages/legal/cookies.md +4 -0
- package/defaults/pages/legal/privacy.md +4 -0
- package/defaults/pages/legal/terms.md +4 -0
- package/defaults/pages/llms-txt.html +46 -0
- package/defaults/pages/opensearch.html +16 -0
- package/defaults/pages/pages-json.html +42 -0
- package/defaults/pages/payment/checkout.md +4 -0
- package/defaults/pages/payment/confirmation.md +4 -0
- package/defaults/pages/portal/email-preferences.md +4 -0
- package/defaults/pages/pricing.md +4 -0
- package/defaults/pages/redirects/account.md +12 -0
- package/defaults/pages/redirects/admin/dashboard.md +11 -0
- package/defaults/pages/redirects/auth/change-password.md +9 -0
- package/defaults/pages/redirects/auth/forgot.md +9 -0
- package/defaults/pages/redirects/auth/join.md +9 -0
- package/defaults/pages/redirects/auth/login.md +9 -0
- package/defaults/pages/redirects/auth/recover.md +9 -0
- package/defaults/pages/redirects/auth/register.md +9 -0
- package/defaults/pages/redirects/auth/reset-password.md +9 -0
- package/defaults/pages/redirects/billing/cancel.md +9 -0
- package/defaults/pages/redirects/billing/refund.md +9 -0
- package/defaults/pages/robots.html +36 -0
- package/defaults/pages/security-txt.html +11 -0
- package/defaults/pages/sitemap.html +37 -0
- package/defaults/pages/status.md +4 -0
- package/defaults/pages/team/index.md +4 -0
- package/defaults/pages/test/account/dashboard.html +16 -0
- package/defaults/pages/test/index.md +12 -0
- package/defaults/pages/test/libraries/appearance.html +268 -0
- package/defaults/pages/test/libraries/cover.html +50 -0
- package/defaults/pages/test/libraries/error.html +30 -0
- package/defaults/pages/test/libraries/firestore.html +202 -0
- package/defaults/pages/test/libraries/form-manager.html +374 -0
- package/defaults/pages/test/libraries/layers.html +52 -0
- package/defaults/pages/test/libraries/lazy-loading.html +450 -0
- package/defaults/pages/test/libraries/verts.html +149 -0
- package/defaults/pages/test/redirect/external.md +13 -0
- package/defaults/pages/test/redirect/internal.md +13 -0
- package/defaults/pages/test/styleguide/index.html +588 -0
- package/defaults/pages/test/translation/index.md +50 -0
- package/defaults/pages/updates/index.md +4 -0
- package/defaults/sample-posts/2026-01-12-welcome-to-the-blog.md +26 -0
- package/defaults/sample-posts/2026-02-03-a-field-guide-to-good-defaults.md +50 -0
- package/defaults/sample-posts/2026-02-24-shipping-week-notes.md +37 -0
- package/defaults/sample-posts/2026-03-17-the-case-for-one-config.md +36 -0
- package/defaults/sample-posts/2026-04-09-write-release-notes-people-read.md +34 -0
- package/defaults/sample-posts/2026-05-21-dark-mode-done-properly.md +38 -0
- package/defaults/sample-posts/2026-06-02-the-boring-deploy-is-the-good-deploy.md +30 -0
- package/defaults/sample-posts/2026-06-11-naming-things-a-survival-guide.md +30 -0
- package/defaults/sample-posts/2026-06-19-your-pricing-page-is-a-product.md +28 -0
- package/defaults/sample-posts/2026-06-30-email-is-an-api-with-feelings.md +28 -0
- package/defaults/sample-posts/2026-07-08-ship-the-docs-with-the-diff.md +30 -0
- package/defaults/sample-team/avery-quinn.md +22 -0
- package/defaults/sample-team/jordan-reyes.md +25 -0
- package/defaults/sample-team/riley-nakamura.md +22 -0
- package/defaults/sample-team/sam-okafor.md +22 -0
- package/defaults/sample-updates/v1.0.0.md +28 -0
- package/defaults/sample-updates/v1.1.0.md +27 -0
- package/defaults/sample-updates/v1.2.0.md +27 -0
- package/defaults/sample-updates/v1.3.0.md +28 -0
- package/defaults/showcase/components.html +62 -0
- package/defaults/showcase/entry.html +76 -0
- package/defaults/showcase/frame.html +62 -0
- package/defaults/showcase/index.html +59 -0
- package/dist/assets.js +1349 -0
- package/dist/brand-tokens.js +195 -0
- package/dist/build.js +192 -0
- package/dist/cachebreak-html.js +71 -0
- package/dist/cli-run.js +25 -0
- package/dist/cli.js +47 -0
- package/dist/collections.js +183 -0
- package/dist/commands/audit.js +470 -0
- package/dist/commands/build.js +197 -0
- package/dist/commands/clean.js +18 -0
- package/dist/commands/customize.js +125 -0
- package/dist/commands/deploy.js +441 -0
- package/dist/commands/dev.js +1067 -0
- package/dist/commands/install.js +31 -0
- package/dist/commands/lib/deploy-precheck.js +43 -0
- package/dist/commands/lib/ensure-target.js +115 -0
- package/dist/commands/migrate.js +86 -0
- package/dist/commands/purge.js +31 -0
- package/dist/commands/test.js +237 -0
- package/dist/commands/translate.js +57 -0
- package/dist/commands/update.js +22 -0
- package/dist/commands/version.js +11 -0
- package/dist/config-sections.js +245 -0
- package/dist/consumer-scan.js +118 -0
- package/dist/consumer.js +49 -0
- package/dist/customize.js +328 -0
- package/dist/decisions.js +153 -0
- package/dist/dist-audit.js +417 -0
- package/dist/dynamic-pages.js +357 -0
- package/dist/engine.js +1402 -0
- package/dist/firebase-auth-helpers.js +198 -0
- package/dist/font-metrics.js +420 -0
- package/dist/frontmatter-liquid.js +253 -0
- package/dist/github-secrets.js +80 -0
- package/dist/hero-animations.js +170 -0
- package/dist/imagemin.js +261 -0
- package/dist/index.js +41 -0
- package/dist/inline-icons.js +65 -0
- package/dist/language-flags.js +55 -0
- package/dist/layers.js +80 -0
- package/dist/layouts.js +44 -0
- package/dist/limit-collections.js +199 -0
- package/dist/link-resolver.js +303 -0
- package/dist/markdown-images.js +85 -0
- package/dist/merge.js +20 -0
- package/dist/migrate/codemod.js +172 -0
- package/dist/migrate/config-convert.js +448 -0
- package/dist/migrate/consumer-assets.js +130 -0
- package/dist/migrate/index.js +223 -0
- package/dist/migrate/lint.js +108 -0
- package/dist/migrate/rules.js +1929 -0
- package/dist/minify-html.js +148 -0
- package/dist/mode-helpers.js +96 -0
- package/dist/omega-bin.js +7 -0
- package/dist/overrides.js +190 -0
- package/dist/path-prefix.js +167 -0
- package/dist/paths.js +37 -0
- package/dist/pricing.js +301 -0
- package/dist/purge.js +95 -0
- package/dist/redirect-page.js +45 -0
- package/dist/sample-content.js +210 -0
- package/dist/scaffold.js +123 -0
- package/dist/sections.js +810 -0
- package/dist/service-worker.js +200 -0
- package/dist/social-pages.js +142 -0
- package/dist/srcset.js +67 -0
- package/dist/static-assets.js +154 -0
- package/dist/target-shortlinks.js +123 -0
- package/dist/theme-vocabulary.js +76 -0
- package/dist/translate/collect-text-nodes.js +93 -0
- package/dist/translate/default-routes.js +152 -0
- package/dist/translate/generate-defaults.js +297 -0
- package/dist/translate/index.js +568 -0
- package/dist/translate/packaged-defaults.js +127 -0
- package/dist/translate/sitemap.js +108 -0
- package/dist/vendor/account/engine.js +182 -0
- package/dist/vendor/account/features.js +220 -0
- package/dist/vendor/account/index.js +53 -0
- package/dist/vendor/account/schema.js +272 -0
- package/dist/vendor/account/subscription.js +38 -0
- package/dist/vendor/config/company.js +31 -0
- package/dist/vendor/config/defaults.js +173 -0
- package/dist/vendor/config/demo.js +18 -0
- package/dist/vendor/config/desktop-artifacts.js +110 -0
- package/dist/vendor/config/edit.js +769 -0
- package/dist/vendor/config/env-delivery.js +145 -0
- package/dist/vendor/config/env-rules.js +93 -0
- package/dist/vendor/config/env-schema.js +1078 -0
- package/dist/vendor/config/env.js +445 -0
- package/dist/vendor/config/hooks.js +97 -0
- package/dist/vendor/config/index.js +237 -0
- package/dist/vendor/config/instances.js +208 -0
- package/dist/vendor/config/load.js +490 -0
- package/dist/vendor/config/merge.js +68 -0
- package/dist/vendor/config/order.js +139 -0
- package/dist/vendor/config/ports.js +374 -0
- package/dist/vendor/config/providers.js +32 -0
- package/dist/vendor/config/repo.js +142 -0
- package/dist/vendor/config/retired-keys.js +430 -0
- package/dist/vendor/config/schema.js +1610 -0
- package/dist/vendor/config/secrets.js +50 -0
- package/dist/vendor/config/seed.js +34 -0
- package/dist/vendor/config/site-global.js +205 -0
- package/dist/vendor/config/validate.js +554 -0
- package/dist/vendor/config/winback.js +61 -0
- package/dist/vendor/devkit/actions-secrets.js +145 -0
- package/dist/vendor/devkit/attach-log-file.js +262 -0
- package/dist/vendor/devkit/bare-requires.js +153 -0
- package/dist/vendor/devkit/bundle.js +292 -0
- package/dist/vendor/devkit/ci-workflows.js +520 -0
- package/dist/vendor/devkit/cli-router.js +156 -0
- package/dist/vendor/devkit/command-path.js +46 -0
- package/dist/vendor/devkit/defaults-engine.js +372 -0
- package/dist/vendor/devkit/deploy-precheck.js +54 -0
- package/dist/vendor/devkit/deploy-record.js +180 -0
- package/dist/vendor/devkit/deploy.js +286 -0
- package/dist/vendor/devkit/env-watch.js +132 -0
- package/dist/vendor/devkit/framework-deps.js +46 -0
- package/dist/vendor/devkit/icons.js +178 -0
- package/dist/vendor/devkit/license.js +155 -0
- package/dist/vendor/devkit/local-https.js +360 -0
- package/dist/vendor/devkit/local.js +1905 -0
- package/dist/vendor/devkit/logger.js +128 -0
- package/dist/vendor/devkit/merge-line-files.js +296 -0
- package/dist/vendor/devkit/omega-bin.js +345 -0
- package/dist/vendor/devkit/reads.js +274 -0
- package/dist/vendor/devkit/safe-install.js +18 -0
- package/dist/vendor/devkit/scaffold-guard.js +96 -0
- package/dist/vendor/devkit/stop-signals.js +28 -0
- package/dist/vendor/devkit/strip-dev-blocks-plugin.js +29 -0
- package/dist/vendor/devkit/strip-dev-blocks.js +32 -0
- package/dist/vendor/devkit/target-secrets.js +165 -0
- package/dist/vendor/devkit/test/scope.js +162 -0
- package/dist/vendor/devkit/translate/cache.js +84 -0
- package/dist/vendor/devkit/translate/engine.js +243 -0
- package/dist/vendor/devkit/translate/index.js +49 -0
- package/dist/vendor/devkit/translate/languages.js +134 -0
- package/dist/vendor/devkit/translate/providers.js +188 -0
- package/dist/vendor/devkit/update.js +569 -0
- package/dist/vendor/template-kit/data/language-flags.js +54 -0
- package/dist/vendor/template-kit/data/languages.js +195 -0
- package/dist/vendor/template-kit/data/social-urls.js +26 -0
- package/dist/vendor/template-kit/filters.js +227 -0
- package/dist/vendor/template-kit/index.js +28 -0
- package/dist/vendor/template-kit/jekyll-compat.js +268 -0
- package/dist/vendor/template-kit/register-liquid.js +0 -0
- package/dist/vendor/template-kit/tags/collections.js +191 -0
- package/dist/vendor/template-kit/tags/conditionals.js +101 -0
- package/dist/vendor/template-kit/tags/content.js +175 -0
- package/dist/vendor/template-kit/tags/index.js +33 -0
- package/dist/vendor/template-kit/tags/media.js +376 -0
- package/dist/vendor/template-kit/variable-resolver.js +163 -0
- package/docs/ads-system.md +109 -0
- package/docs/classy-v2/DIRECTION.md +92 -0
- package/docs/classy-v2/draft-1.html +1932 -0
- package/docs/classy-v2/draft-2.html +1956 -0
- package/docs/frontmatter.md +153 -0
- package/docs/index.md +254 -0
- package/docs/libs.md +298 -0
- package/docs/omega-sections-spec.md +159 -0
- package/docs/page-contract.md +112 -0
- package/docs/sections.md +845 -0
- package/docs/shared/agent-docs.md +89 -0
- package/docs/shared/analytics.md +612 -0
- package/docs/shared/brands.md +51 -0
- package/docs/shared/breaking-changes.md +497 -0
- package/docs/shared/config.md +1387 -0
- package/docs/shared/deploys.md +215 -0
- package/docs/shared/icons.md +201 -0
- package/docs/shared/local-dev.md +147 -0
- package/docs/shared/logging.md +202 -0
- package/docs/shared/monitoring.md +153 -0
- package/docs/shared/publishing.md +183 -0
- package/docs/shared/rulings.md +34 -0
- package/docs/shared/testing.md +147 -0
- package/docs/shared/theming.md +604 -0
- package/docs/shared/translation.md +291 -0
- package/docs/shared/updates.md +61 -0
- package/docs/template-kit.md +128 -0
- package/docs/test-framework.md +163 -0
- package/package.json +122 -0
- package/runtime/boot.js +149 -0
- package/runtime/icons.js +77 -0
- package/runtime/manager.js +36 -0
- package/scaffold/.github/workflows/build.yml +139 -0
- package/scaffold/.nvmrc +1 -0
- package/scaffold/AGENTS.md +55 -0
- package/scaffold/CLAUDE.md +1 -0
- package/scaffold/_.gitattributes +4 -0
- package/scaffold/_.gitignore +27 -0
- package/scaffold/config/omega.json5 +20 -0
- package/scaffold/src/assets/css/pages/.gitkeep +0 -0
- package/scaffold/src/assets/images/.gitkeep +0 -0
- package/scaffold/src/assets/js/pages/.gitkeep +0 -0
- package/scaffold/src/pages/.gitkeep +0 -0
- package/scaffold/src/pages/example.md.txt +94 -0
- package/scaffold/src/service-worker.js +16 -0
- package/sw/entry.js +19 -0
- package/sw/manager.js +281 -0
- package/themes/_template/README.md +115 -0
- package/themes/_template/_config.scss +60 -0
- package/themes/_template/_theme.js +14 -0
- package/themes/_template/_theme.scss +28 -0
- package/themes/_template/css/base/_root.scss +19 -0
- package/themes/_template/css/components/_components.scss +23 -0
- package/themes/base/README.md +5 -0
- package/themes/base/_components/data/org-chart/component.html +33 -0
- package/themes/base/_components/data/org-chart/component.json5 +33 -0
- package/themes/base/_components/data/org-chart/component.scss +183 -0
- package/themes/base/_components/heading/masthead/component.html +22 -0
- package/themes/base/_components/heading/masthead/component.json5 +30 -0
- package/themes/base/_components/heading/section-head/component.html +18 -0
- package/themes/base/_components/heading/section-head/component.json5 +27 -0
- package/themes/base/_components/pricing/features/component.html +29 -0
- package/themes/base/_components/pricing/features/component.json5 +30 -0
- package/themes/base/_hero/orbit/index.html +30 -0
- package/themes/base/_hero/orbit/script.js +33 -0
- package/themes/base/_hero/orbit/style.scss +133 -0
- package/themes/base/_includes/admin/sections/sidebar.html +3 -0
- package/themes/base/_includes/admin/sections/topbar.html +3 -0
- package/themes/base/_includes/backend/sections/sidebar.html +3 -0
- package/themes/base/_includes/backend/sections/topbar.html +3 -0
- package/themes/base/_includes/frontend/components/connection-card.html +49 -0
- package/themes/base/_includes/frontend/components/post-card.html +54 -0
- package/themes/base/_includes/frontend/sections/account-connections.html +42 -0
- package/themes/base/_includes/frontend/sections/account-section-header.html +11 -0
- package/themes/base/_includes/frontend/sections/footer.html +154 -0
- package/themes/base/_includes/frontend/sections/nav.html +276 -0
- package/themes/base/_includes/global/sections/account.html +72 -0
- package/themes/base/_includes/global/sections/app-sidebar.html +248 -0
- package/themes/base/_includes/global/sections/app-topbar.html +228 -0
- package/themes/base/_includes/global/sections/page-header.html +99 -0
- package/themes/base/_layouts/admin/core/minimal-viewport-locked.html +22 -0
- package/themes/base/_layouts/admin/core/minimal.html +23 -0
- package/themes/base/_layouts/backend/core/base.html +57 -0
- package/themes/base/_layouts/backend/core/minimal-viewport-locked.html +32 -0
- package/themes/base/_layouts/backend/core/minimal.html +32 -0
- package/themes/base/_layouts/backend/pages/dashboard/index.html +230 -0
- package/themes/base/_layouts/frontend/core/base.html +31 -0
- package/themes/base/_layouts/frontend/core/cover.html +40 -0
- package/themes/base/_layouts/frontend/core/minimal.html +47 -0
- package/themes/base/_layouts/frontend/pages/404.html +54 -0
- package/themes/base/_layouts/frontend/pages/about.html +144 -0
- package/themes/base/_layouts/frontend/pages/account/index.html +2104 -0
- package/themes/base/_layouts/frontend/pages/alternatives/alternative.html +319 -0
- package/themes/base/_layouts/frontend/pages/alternatives/index.html +186 -0
- package/themes/base/_layouts/frontend/pages/app.html +142 -0
- package/themes/base/_layouts/frontend/pages/auth/reset.html +53 -0
- package/themes/base/_layouts/frontend/pages/auth/signin.html +90 -0
- package/themes/base/_layouts/frontend/pages/auth/signup.html +141 -0
- package/themes/base/_layouts/frontend/pages/auth/token.html +44 -0
- package/themes/base/_layouts/frontend/pages/blog/categories/category.html +88 -0
- package/themes/base/_layouts/frontend/pages/blog/categories/index.html +88 -0
- package/themes/base/_layouts/frontend/pages/blog/index.html +255 -0
- package/themes/base/_layouts/frontend/pages/blog/post.html +261 -0
- package/themes/base/_layouts/frontend/pages/blog/tags/index.html +84 -0
- package/themes/base/_layouts/frontend/pages/blog/tags/tag.html +88 -0
- package/themes/base/_layouts/frontend/pages/collection/category.html +72 -0
- package/themes/base/_layouts/frontend/pages/collection/document.html +54 -0
- package/themes/base/_layouts/frontend/pages/collection/index.html +84 -0
- package/themes/base/_layouts/frontend/pages/connections/callback.html +55 -0
- package/themes/base/_layouts/frontend/pages/contact.html +214 -0
- package/themes/base/_layouts/frontend/pages/download.html +793 -0
- package/themes/base/_layouts/frontend/pages/extension/index.html +253 -0
- package/themes/base/_layouts/frontend/pages/extension/installed.html +198 -0
- package/themes/base/_layouts/frontend/pages/feedback.html +171 -0
- package/themes/base/_layouts/frontend/pages/index.html +66 -0
- package/themes/base/_layouts/frontend/pages/legal/document.html +78 -0
- package/themes/base/_layouts/frontend/pages/payment/checkout.html +459 -0
- package/themes/base/_layouts/frontend/pages/payment/confirmation.html +131 -0
- package/themes/base/_layouts/frontend/pages/portal/email-preferences.html +104 -0
- package/themes/base/_layouts/frontend/pages/pricing.html +596 -0
- package/themes/base/_layouts/frontend/pages/status.html +361 -0
- package/themes/base/_layouts/frontend/pages/team/index.html +210 -0
- package/themes/base/_layouts/frontend/pages/team/member.html +125 -0
- package/themes/base/_layouts/frontend/pages/updates/index.html +119 -0
- package/themes/base/_layouts/frontend/pages/updates/update.html +118 -0
- package/themes/base/_sections/about/hero/section.html +68 -0
- package/themes/base/_sections/about/hero/section.json5 +58 -0
- package/themes/base/_sections/about/letter/section.html +60 -0
- package/themes/base/_sections/about/letter/section.json5 +56 -0
- package/themes/base/_sections/about/photo-band/section.html +24 -0
- package/themes/base/_sections/about/photo-band/section.json5 +23 -0
- package/themes/base/_sections/about/principles/section.html +17 -0
- package/themes/base/_sections/about/principles/section.json5 +34 -0
- package/themes/base/_sections/about/timeline/section.html +20 -0
- package/themes/base/_sections/about/timeline/section.json5 +34 -0
- package/themes/base/_sections/marketing/bento/section.html +91 -0
- package/themes/base/_sections/marketing/bento/section.json5 +152 -0
- package/themes/base/_sections/marketing/cta/section.html +44 -0
- package/themes/base/_sections/marketing/cta/section.json5 +47 -0
- package/themes/base/_sections/marketing/faq/section.html +52 -0
- package/themes/base/_sections/marketing/faq/section.json5 +68 -0
- package/themes/base/_sections/marketing/hero/section.html +349 -0
- package/themes/base/_sections/marketing/hero/section.json5 +335 -0
- package/themes/base/_sections/marketing/newsletter-cta/section.html +38 -0
- package/themes/base/_sections/marketing/newsletter-cta/section.js +86 -0
- package/themes/base/_sections/marketing/newsletter-cta/section.json5 +29 -0
- package/themes/base/_sections/marketing/pricing-cards/section.html +115 -0
- package/themes/base/_sections/marketing/pricing-cards/section.json5 +72 -0
- package/themes/base/_sections/marketing/product-demo/section.html +62 -0
- package/themes/base/_sections/marketing/product-demo/section.js +44 -0
- package/themes/base/_sections/marketing/product-demo/section.json5 +52 -0
- package/themes/base/_sections/marketing/prose/section.html +22 -0
- package/themes/base/_sections/marketing/prose/section.json5 +41 -0
- package/themes/base/_sections/marketing/showcase/section.html +35 -0
- package/themes/base/_sections/marketing/showcase/section.json5 +52 -0
- package/themes/base/_sections/marketing/stats/section.html +32 -0
- package/themes/base/_sections/marketing/stats/section.json5 +44 -0
- package/themes/base/_sections/marketing/testimonials/section.html +54 -0
- package/themes/base/_sections/marketing/testimonials/section.json5 +70 -0
- package/themes/base/_sections/marketing/trusted-by/section.html +30 -0
- package/themes/base/_sections/marketing/trusted-by/section.json5 +38 -0
- package/themes/base/_sections/verts/unit/section.html +7 -0
- package/themes/base/_sections/verts/unit/section.js +22 -0
- package/themes/base/_sections/verts/unit/section.json5 +24 -0
- package/themes/base/_sections/verts/unit/section.scss +24 -0
- package/themes/base/_theme.js +13 -0
- package/themes/base/_theme.scss +4 -0
- package/themes/bootstrap/_theme.js +16 -0
- package/themes/bootstrap/_theme.scss +11 -0
- package/themes/bootstrap/js/index.esm.js +19 -0
- package/themes/bootstrap/js/index.umd.js +34 -0
- package/themes/bootstrap/js/src/alert.js +87 -0
- package/themes/bootstrap/js/src/base-component.js +86 -0
- package/themes/bootstrap/js/src/button.js +72 -0
- package/themes/bootstrap/js/src/carousel.js +474 -0
- package/themes/bootstrap/js/src/collapse.js +297 -0
- package/themes/bootstrap/js/src/dom/data.js +55 -0
- package/themes/bootstrap/js/src/dom/event-handler.js +317 -0
- package/themes/bootstrap/js/src/dom/manipulator.js +71 -0
- package/themes/bootstrap/js/src/dom/selector-engine.js +126 -0
- package/themes/bootstrap/js/src/dropdown.js +458 -0
- package/themes/bootstrap/js/src/modal.js +378 -0
- package/themes/bootstrap/js/src/offcanvas.js +282 -0
- package/themes/bootstrap/js/src/popover.js +97 -0
- package/themes/bootstrap/js/src/scrollspy.js +296 -0
- package/themes/bootstrap/js/src/tab.js +315 -0
- package/themes/bootstrap/js/src/toast.js +224 -0
- package/themes/bootstrap/js/src/tooltip.js +632 -0
- package/themes/bootstrap/js/src/util/backdrop.js +151 -0
- package/themes/bootstrap/js/src/util/component-functions.js +35 -0
- package/themes/bootstrap/js/src/util/config.js +65 -0
- package/themes/bootstrap/js/src/util/focustrap.js +115 -0
- package/themes/bootstrap/js/src/util/index.js +306 -0
- package/themes/bootstrap/js/src/util/sanitizer.js +117 -0
- package/themes/bootstrap/js/src/util/scrollbar.js +114 -0
- package/themes/bootstrap/js/src/util/swipe.js +146 -0
- package/themes/bootstrap/js/src/util/template-factory.js +160 -0
- package/themes/bootstrap/overrides/_accordion.scss +21 -0
- package/themes/bootstrap/overrides/_avatars.scss +51 -0
- package/themes/bootstrap/overrides/_blog-images.scss +45 -0
- package/themes/bootstrap/overrides/_buttons-adaptive.scss +141 -0
- package/themes/bootstrap/overrides/_color-shades.scss +25 -0
- package/themes/bootstrap/overrides/_cursor-utilities.scss +13 -0
- package/themes/bootstrap/overrides/_index.scss +30 -0
- package/themes/bootstrap/overrides/_links.scss +26 -0
- package/themes/bootstrap/overrides/_soft-colors.scss +99 -0
- package/themes/bootstrap/overrides/_spacing-extended.scss +74 -0
- package/themes/bootstrap/overrides/_spinners.scss +22 -0
- package/themes/bootstrap/overrides/_typography-utilities.scss +40 -0
- package/themes/bootstrap/scss/_accordion.scss +153 -0
- package/themes/bootstrap/scss/_alert.scss +68 -0
- package/themes/bootstrap/scss/_badge.scss +38 -0
- package/themes/bootstrap/scss/_breadcrumb.scss +40 -0
- package/themes/bootstrap/scss/_button-group.scss +147 -0
- package/themes/bootstrap/scss/_buttons.scss +216 -0
- package/themes/bootstrap/scss/_card.scss +238 -0
- package/themes/bootstrap/scss/_carousel.scss +226 -0
- package/themes/bootstrap/scss/_close.scss +66 -0
- package/themes/bootstrap/scss/_containers.scss +41 -0
- package/themes/bootstrap/scss/_dropdown.scss +250 -0
- package/themes/bootstrap/scss/_forms.scss +9 -0
- package/themes/bootstrap/scss/_functions.scss +302 -0
- package/themes/bootstrap/scss/_grid.scss +39 -0
- package/themes/bootstrap/scss/_helpers.scss +12 -0
- package/themes/bootstrap/scss/_images.scss +42 -0
- package/themes/bootstrap/scss/_list-group.scss +199 -0
- package/themes/bootstrap/scss/_maps.scss +174 -0
- package/themes/bootstrap/scss/_mixins.scss +42 -0
- package/themes/bootstrap/scss/_modal.scss +240 -0
- package/themes/bootstrap/scss/_nav.scss +197 -0
- package/themes/bootstrap/scss/_navbar.scss +289 -0
- package/themes/bootstrap/scss/_offcanvas.scss +147 -0
- package/themes/bootstrap/scss/_pagination.scss +109 -0
- package/themes/bootstrap/scss/_placeholders.scss +51 -0
- package/themes/bootstrap/scss/_popover.scss +196 -0
- package/themes/bootstrap/scss/_progress.scss +68 -0
- package/themes/bootstrap/scss/_reboot.scss +611 -0
- package/themes/bootstrap/scss/_root.scss +187 -0
- package/themes/bootstrap/scss/_spinners.scss +85 -0
- package/themes/bootstrap/scss/_tables.scss +171 -0
- package/themes/bootstrap/scss/_toasts.scss +73 -0
- package/themes/bootstrap/scss/_tooltip.scss +119 -0
- package/themes/bootstrap/scss/_transitions.scss +27 -0
- package/themes/bootstrap/scss/_type.scss +106 -0
- package/themes/bootstrap/scss/_utilities.scss +806 -0
- package/themes/bootstrap/scss/_variables-dark.scss +102 -0
- package/themes/bootstrap/scss/_variables.scss +1753 -0
- package/themes/bootstrap/scss/bootstrap-grid.scss +62 -0
- package/themes/bootstrap/scss/bootstrap-reboot.scss +10 -0
- package/themes/bootstrap/scss/bootstrap-utilities.scss +19 -0
- package/themes/bootstrap/scss/bootstrap.scss +52 -0
- package/themes/bootstrap/scss/forms/_floating-labels.scss +97 -0
- package/themes/bootstrap/scss/forms/_form-check.scss +189 -0
- package/themes/bootstrap/scss/forms/_form-control.scss +214 -0
- package/themes/bootstrap/scss/forms/_form-range.scss +91 -0
- package/themes/bootstrap/scss/forms/_form-select.scss +80 -0
- package/themes/bootstrap/scss/forms/_form-text.scss +11 -0
- package/themes/bootstrap/scss/forms/_input-group.scss +132 -0
- package/themes/bootstrap/scss/forms/_labels.scss +36 -0
- package/themes/bootstrap/scss/forms/_validation.scss +12 -0
- package/themes/bootstrap/scss/helpers/_clearfix.scss +3 -0
- package/themes/bootstrap/scss/helpers/_color-bg.scss +7 -0
- package/themes/bootstrap/scss/helpers/_colored-links.scss +30 -0
- package/themes/bootstrap/scss/helpers/_focus-ring.scss +5 -0
- package/themes/bootstrap/scss/helpers/_icon-link.scss +25 -0
- package/themes/bootstrap/scss/helpers/_position.scss +36 -0
- package/themes/bootstrap/scss/helpers/_ratio.scss +26 -0
- package/themes/bootstrap/scss/helpers/_stacks.scss +15 -0
- package/themes/bootstrap/scss/helpers/_stretched-link.scss +15 -0
- package/themes/bootstrap/scss/helpers/_text-truncation.scss +7 -0
- package/themes/bootstrap/scss/helpers/_visually-hidden.scss +8 -0
- package/themes/bootstrap/scss/helpers/_vr.scss +8 -0
- package/themes/bootstrap/scss/mixins/_alert.scss +18 -0
- package/themes/bootstrap/scss/mixins/_backdrop.scss +14 -0
- package/themes/bootstrap/scss/mixins/_banner.scss +7 -0
- package/themes/bootstrap/scss/mixins/_border-radius.scss +78 -0
- package/themes/bootstrap/scss/mixins/_box-shadow.scss +18 -0
- package/themes/bootstrap/scss/mixins/_breakpoints.scss +127 -0
- package/themes/bootstrap/scss/mixins/_buttons.scss +70 -0
- package/themes/bootstrap/scss/mixins/_caret.scss +69 -0
- package/themes/bootstrap/scss/mixins/_clearfix.scss +9 -0
- package/themes/bootstrap/scss/mixins/_color-mode.scss +21 -0
- package/themes/bootstrap/scss/mixins/_color-scheme.scss +7 -0
- package/themes/bootstrap/scss/mixins/_container.scss +11 -0
- package/themes/bootstrap/scss/mixins/_deprecate.scss +10 -0
- package/themes/bootstrap/scss/mixins/_forms.scss +163 -0
- package/themes/bootstrap/scss/mixins/_gradients.scss +47 -0
- package/themes/bootstrap/scss/mixins/_grid.scss +151 -0
- package/themes/bootstrap/scss/mixins/_image.scss +16 -0
- package/themes/bootstrap/scss/mixins/_list-group.scss +26 -0
- package/themes/bootstrap/scss/mixins/_lists.scss +7 -0
- package/themes/bootstrap/scss/mixins/_pagination.scss +10 -0
- package/themes/bootstrap/scss/mixins/_reset-text.scss +17 -0
- package/themes/bootstrap/scss/mixins/_resize.scss +6 -0
- package/themes/bootstrap/scss/mixins/_table-variants.scss +24 -0
- package/themes/bootstrap/scss/mixins/_text-truncate.scss +8 -0
- package/themes/bootstrap/scss/mixins/_transition.scss +26 -0
- package/themes/bootstrap/scss/mixins/_utilities.scss +97 -0
- package/themes/bootstrap/scss/mixins/_visually-hidden.scss +38 -0
- package/themes/bootstrap/scss/tests/jasmine.js +16 -0
- package/themes/bootstrap/scss/tests/mixins/_auto-import-of-variables-dark.test.scss +7 -0
- package/themes/bootstrap/scss/tests/mixins/_color-modes.test.scss +69 -0
- package/themes/bootstrap/scss/tests/mixins/_media-query-color-mode-full.test.scss +8 -0
- package/themes/bootstrap/scss/tests/mixins/_utilities.test.scss +393 -0
- package/themes/bootstrap/scss/tests/sass-true/register.js +14 -0
- package/themes/bootstrap/scss/tests/sass-true/runner.js +17 -0
- package/themes/bootstrap/scss/tests/utilities/_api.test.scss +75 -0
- package/themes/bootstrap/scss/utilities/_api.scss +47 -0
- package/themes/bootstrap/scss/vendor/_rfs.scss +348 -0
- package/themes/classy/README.md +89 -0
- package/themes/classy/_config.scss +245 -0
- package/themes/classy/_theme.js +31 -0
- package/themes/classy/_theme.scss +57 -0
- package/themes/classy/css/app/_panels.scss +183 -0
- package/themes/classy/css/base/_fonts.scss +79 -0
- package/themes/classy/css/base/_no-raise.scss +49 -0
- package/themes/classy/css/base/_root.scss +113 -0
- package/themes/classy/css/base/_typography.scss +154 -0
- package/themes/classy/css/base/_utilities.scss +284 -0
- package/themes/classy/css/components/_alerts.scss +51 -0
- package/themes/classy/css/components/_badges.scss +46 -0
- package/themes/classy/css/components/_buttons.scss +175 -0
- package/themes/classy/css/components/_cards.scss +41 -0
- package/themes/classy/css/components/_dropdowns.scss +48 -0
- package/themes/classy/css/components/_forms.scss +171 -0
- package/themes/classy/css/components/_receipt.scss +45 -0
- package/themes/classy/css/layout/_footer.scss +298 -0
- package/themes/classy/css/layout/_general.scss +209 -0
- package/themes/classy/css/layout/_nav.scss +154 -0
- package/themes/classy/css/layout/_shell.scss +458 -0
- package/themes/classy/css/marketing/_bento.scss +238 -0
- package/themes/classy/css/marketing/_content.scss +1328 -0
- package/themes/classy/css/marketing/_downloads.scss +246 -0
- package/themes/classy/css/marketing/_hero.scss +311 -0
- package/themes/classy/css/marketing/_sections.scss +649 -0
- package/themes/classy/css/pages/404/index.scss +67 -0
- package/themes/classy/css/pages/_auth.scss +235 -0
- package/themes/classy/css/pages/admin/firebase/index.scss +22 -0
- package/themes/classy/css/pages/blog/[slug].scss +56 -0
- package/themes/classy/css/pages/cookies.scss +2 -0
- package/themes/classy/css/pages/dashboard/account/index.scss +31 -0
- package/themes/classy/css/pages/feedback/index.scss +39 -0
- package/themes/classy/css/pages/legal/_document.scss +192 -0
- package/themes/classy/css/pages/payment/checkout/index.scss +585 -0
- package/themes/classy/css/pages/payment/confirmation/index.scss +9 -0
- package/themes/classy/css/pages/privacy.scss +2 -0
- package/themes/classy/css/pages/status/index.scss +164 -0
- package/themes/classy/css/pages/terms.scss +2 -0
- package/themes/classy/css/pages/updates/index.scss +128 -0
- package/themes/classy/fonts/inter-italic-latin-ext.woff2 +0 -0
- package/themes/classy/fonts/inter-italic-latin.woff2 +0 -0
- package/themes/classy/fonts/inter-normal-latin-ext.woff2 +0 -0
- package/themes/classy/fonts/inter-normal-latin.woff2 +0 -0
- package/themes/classy/fonts/newsreader-italic-latin-ext.woff2 +0 -0
- package/themes/classy/fonts/newsreader-italic-latin.woff2 +0 -0
- package/themes/classy/fonts/newsreader-normal-latin-ext.woff2 +0 -0
- package/themes/classy/fonts/newsreader-normal-latin.woff2 +0 -0
- package/themes/classy/js/hero-demo-form.js +56 -0
- package/themes/neobrutalism/README.md +118 -0
- package/themes/neobrutalism/_config.scss +139 -0
- package/themes/neobrutalism/_layouts/frontend/core/base.html +30 -0
- package/themes/neobrutalism/_layouts/frontend/pages/index.html +275 -0
- package/themes/neobrutalism/_theme.js +24 -0
- package/themes/neobrutalism/_theme.scss +61 -0
- package/themes/neobrutalism/css/base/_mixins.scss +46 -0
- package/themes/neobrutalism/css/base/_root.scss +169 -0
- package/themes/neobrutalism/css/base/_typography.scss +61 -0
- package/themes/neobrutalism/css/base/_utilities.scss +89 -0
- package/themes/neobrutalism/css/components/_billing-toggle.scss +74 -0
- package/themes/neobrutalism/css/components/_buttons.scss +148 -0
- package/themes/neobrutalism/css/components/_cards.scss +47 -0
- package/themes/neobrutalism/css/components/_forms.scss +88 -0
- package/themes/neobrutalism/css/components/_marketing.scss +244 -0
- package/themes/neobrutalism/css/layout/_general.scss +213 -0
- package/themes/neobrutalism/css/layout/_navigation.scss +171 -0
- package/themes/neobrutalism/css/pages/index.scss +203 -0
- package/themes/neobrutalism/css/pages/pricing/index.scss +58 -0
- package/themes/neobrutalism/css/pages/test/libraries/layers/index.scss +7 -0
- package/themes/neobrutalism/js/pages/test/libraries/layers/index.js +9 -0
- package/themes/newsflash/README.md +117 -0
- package/themes/newsflash/_components/heading/lede/component.html +14 -0
- package/themes/newsflash/_components/heading/lede/component.json5 +23 -0
- package/themes/newsflash/_components/heading/rule-head/component.html +17 -0
- package/themes/newsflash/_components/heading/rule-head/component.json5 +23 -0
- package/themes/newsflash/_components/news/byline/component.html +12 -0
- package/themes/newsflash/_components/news/byline/component.json5 +18 -0
- package/themes/newsflash/_components/news/story-card/component.html +25 -0
- package/themes/newsflash/_components/news/story-card/component.json5 +21 -0
- package/themes/newsflash/_config.scss +147 -0
- package/themes/newsflash/_layouts/frontend/core/base.html +51 -0
- package/themes/newsflash/_layouts/frontend/pages/blog/index.html +299 -0
- package/themes/newsflash/_layouts/frontend/pages/blog/post.html +235 -0
- package/themes/newsflash/_layouts/frontend/pages/index.html +328 -0
- package/themes/newsflash/_sections/marketing/desks/section.html +39 -0
- package/themes/newsflash/_sections/marketing/desks/section.json5 +49 -0
- package/themes/newsflash/_sections/marketing/newsletter-cta/section.html +103 -0
- package/themes/newsflash/_sections/marketing/newsletter-cta/section.json5 +50 -0
- package/themes/newsflash/_sections/marketing/rundown/section.html +28 -0
- package/themes/newsflash/_sections/marketing/rundown/section.json5 +40 -0
- package/themes/newsflash/_theme.js +24 -0
- package/themes/newsflash/_theme.scss +70 -0
- package/themes/newsflash/css/base/_fonts.scss +80 -0
- package/themes/newsflash/css/base/_mixins.scss +51 -0
- package/themes/newsflash/css/base/_root.scss +257 -0
- package/themes/newsflash/css/base/_typography.scss +49 -0
- package/themes/newsflash/css/base/_utilities.scss +122 -0
- package/themes/newsflash/css/components/_badges.scss +65 -0
- package/themes/newsflash/css/components/_billing-toggle.scss +61 -0
- package/themes/newsflash/css/components/_buttons.scss +139 -0
- package/themes/newsflash/css/components/_cards.scss +47 -0
- package/themes/newsflash/css/components/_editorial.scss +173 -0
- package/themes/newsflash/css/components/_forms.scss +75 -0
- package/themes/newsflash/css/components/_infinite-scroll.scss +102 -0
- package/themes/newsflash/css/components/_panels.scss +156 -0
- package/themes/newsflash/css/components/_ticker.scss +67 -0
- package/themes/newsflash/css/layout/_general.scss +330 -0
- package/themes/newsflash/css/layout/_navigation.scss +168 -0
- package/themes/newsflash/css/pages/404/index.scss +79 -0
- package/themes/newsflash/css/pages/blog/[slug].scss +186 -0
- package/themes/newsflash/css/pages/blog/index.scss +17 -0
- package/themes/newsflash/css/pages/index.scss +157 -0
- package/themes/newsflash/css/pages/pricing/index.scss +55 -0
- package/themes/newsflash/css/pages/test/libraries/layers/index.scss +7 -0
- package/themes/newsflash/fonts/fraunces-italic-latin-ext.woff2 +0 -0
- package/themes/newsflash/fonts/fraunces-italic-latin.woff2 +0 -0
- package/themes/newsflash/fonts/fraunces-normal-latin-ext.woff2 +0 -0
- package/themes/newsflash/fonts/fraunces-normal-latin.woff2 +0 -0
- package/themes/newsflash/fonts/schibsted-grotesk-italic-latin-ext.woff2 +0 -0
- package/themes/newsflash/fonts/schibsted-grotesk-italic-latin.woff2 +0 -0
- package/themes/newsflash/fonts/schibsted-grotesk-normal-latin-ext.woff2 +0 -0
- package/themes/newsflash/fonts/schibsted-grotesk-normal-latin.woff2 +0 -0
- package/themes/newsflash/js/pages/blog/[slug].js +29 -0
- package/themes/newsflash/js/pages/test/libraries/layers/index.js +9 -0
- package/translations/es/pages/404.json +86 -0
- package/translations/es/pages/account.json +36 -0
- package/translations/es/pages/app.json +44 -0
- package/translations/es/pages/cancel.json +36 -0
- package/translations/es/pages/change-password.json +36 -0
- package/translations/es/pages/connections/callback.json +41 -0
- package/translations/es/pages/dashboard/account.json +475 -0
- package/translations/es/pages/dashboard.json +36 -0
- package/translations/es/pages/forgot.json +36 -0
- package/translations/es/pages/join.json +36 -0
- package/translations/es/pages/login.json +36 -0
- package/translations/es/pages/payment/checkout.json +83 -0
- package/translations/es/pages/payment/confirmation.json +62 -0
- package/translations/es/pages/portal/email-preferences.json +46 -0
- package/translations/es/pages/recover.json +36 -0
- package/translations/es/pages/refund.json +36 -0
- package/translations/es/pages/register.json +36 -0
- package/translations/es/pages/reset-password.json +36 -0
- package/translations/es/pages/reset.json +42 -0
- package/translations/es/pages/signin.json +45 -0
- package/translations/es/pages/signup.json +60 -0
- package/translations/es/pages/token.json +39 -0
- package/translations/fa/pages/404.json +86 -0
- package/translations/fa/pages/account.json +36 -0
- package/translations/fa/pages/app.json +44 -0
- package/translations/fa/pages/cancel.json +36 -0
- package/translations/fa/pages/change-password.json +36 -0
- package/translations/fa/pages/connections/callback.json +41 -0
- package/translations/fa/pages/dashboard/account.json +475 -0
- package/translations/fa/pages/dashboard.json +36 -0
- package/translations/fa/pages/forgot.json +36 -0
- package/translations/fa/pages/join.json +36 -0
- package/translations/fa/pages/login.json +36 -0
- package/translations/fa/pages/payment/checkout.json +83 -0
- package/translations/fa/pages/payment/confirmation.json +62 -0
- package/translations/fa/pages/portal/email-preferences.json +46 -0
- package/translations/fa/pages/recover.json +36 -0
- package/translations/fa/pages/refund.json +36 -0
- package/translations/fa/pages/register.json +36 -0
- package/translations/fa/pages/reset-password.json +36 -0
- package/translations/fa/pages/reset.json +42 -0
- package/translations/fa/pages/signin.json +45 -0
- package/translations/fa/pages/signup.json +60 -0
- package/translations/fa/pages/token.json +39 -0
package/dist/engine.js
ADDED
|
@@ -0,0 +1,1402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Eleventy engine core of @omega.js/web: layered themes (virtual templates
|
|
3
|
+
* / symlink farm), template-kit registration on Eleventy's own LiquidJS
|
|
4
|
+
* instance, frontmatter Liquid rendering, the `resolved` data alias
|
|
5
|
+
* (page.resolved equivalent — native data cascade), Jekyll conventions (dated
|
|
6
|
+
* post filenames, permalink normalization), blog collections + taxonomy, and
|
|
7
|
+
* default pages as virtual templates suppressed by same-URL consumer files.
|
|
8
|
+
* Promoted from the winning bake-off spike (_attic/spikes/bakeoff-shared/DECISION.md).
|
|
9
|
+
*/
|
|
10
|
+
const fs = require('node:fs');
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
const JSON5 = require('json5');
|
|
13
|
+
const yaml = require('js-yaml');
|
|
14
|
+
const markdownIt = require('markdown-it');
|
|
15
|
+
const { registerLiquid } = require('./vendor/template-kit/register-liquid.js');
|
|
16
|
+
const { CACHE_TIMESTAMP } = require('./vendor/template-kit/filters.js');
|
|
17
|
+
const { slugify } = require('./vendor/template-kit/jekyll-compat.js');
|
|
18
|
+
const { toSiteGlobal } = require('./vendor/config/site-global.js');
|
|
19
|
+
const { resolveWinbackOffer } = require('./vendor/config/winback.js');
|
|
20
|
+
const Logger = require('./vendor/devkit/logger.js');
|
|
21
|
+
const reads = require('./vendor/devkit/reads.js');
|
|
22
|
+
const { KEYLESS_STAMP } = require('./vendor/devkit/license.js');
|
|
23
|
+
const { createFrontmatterResolver } = require('./frontmatter-liquid.js');
|
|
24
|
+
const { collectLayered, resolveThemeLayers } = require('./layers.js');
|
|
25
|
+
const { applyMarkdownImages } = require('./markdown-images.js');
|
|
26
|
+
const { permalinkOf } = require('./consumer-scan.js');
|
|
27
|
+
const { createDecisions } = require('./decisions.js');
|
|
28
|
+
const { registerVirtualLayouts, composeSymlinkFarm } = require('./layouts.js');
|
|
29
|
+
const { registerSectionTags, buildSectionLibrary } = require('./sections.js');
|
|
30
|
+
const { watchHeroAnimations, HERO_DIR } = require('./hero-animations.js');
|
|
31
|
+
const { registerCollections, BUILT_IN_COLLECTIONS } = require('./collections.js');
|
|
32
|
+
const { applyCollectionLimits } = require('./limit-collections.js');
|
|
33
|
+
const { readCollections, collectionPages, applyDocumentData } = require('./dynamic-pages.js');
|
|
34
|
+
const { readSocials, socialPages } = require('./social-pages.js');
|
|
35
|
+
const { readTargetShortlinks, targetShortlinkPages } = require('./target-shortlinks.js');
|
|
36
|
+
const { resolvePageAsset } = require('./assets.js');
|
|
37
|
+
const { resolvePathPrefix, prefixHtml } = require('./path-prefix.js');
|
|
38
|
+
const { SAMPLE_SETS, resolveAnchor, generateSampleSet } = require('./sample-content.js');
|
|
39
|
+
const { composePricing } = require('./pricing.js');
|
|
40
|
+
const { composeBrandTokens } = require('./brand-tokens.js');
|
|
41
|
+
const { resolveFontAwesomeRoots, createIconLoader } = require('./vendor/devkit/icons.js');
|
|
42
|
+
const { inlineIcons } = require('./inline-icons.js');
|
|
43
|
+
const { getEnvironment } = require('./mode-helpers.js');
|
|
44
|
+
const { ogLocale } = require('./vendor/devkit/translate/index.js');
|
|
45
|
+
const { PATHS } = require('./paths.js');
|
|
46
|
+
const {
|
|
47
|
+
CONFIG_SECTIONS, DEAD_SITE_SECTIONS, SITE_FACT_KEYS, RANDOM_ID_ASSIGN_IDIOM,
|
|
48
|
+
templateReads, randomIdReads, assignsRandomId,
|
|
49
|
+
} = require('./config-sections.js');
|
|
50
|
+
|
|
51
|
+
const logger = new Logger('engine');
|
|
52
|
+
|
|
53
|
+
// Data-cascade keys that are engine machinery, not page/layout data — everything
|
|
54
|
+
// else IS the resolved page data (the cascade already deep-merged layout
|
|
55
|
+
// defaults under page frontmatter, which is exactly what inject-properties.rb
|
|
56
|
+
// computed as page.resolved).
|
|
57
|
+
const RESOLVED_OMIT = new Set([
|
|
58
|
+
'collections', 'content', 'page', 'eleventy', 'pkg', 'eleventyComputed',
|
|
59
|
+
'resolved', 'permalink', 'layout', 'tags', 'pagination', 'site', 'assetManifest',
|
|
60
|
+
'paginator', 'pageAssets', 'layoutAssets', 'jekyll', 'sectionLibrary',
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
// Build-fact keys that do NOT seed `resolved` (bulk/runtime values templates
|
|
64
|
+
// read via site.* directly — mirrors inject-properties.rb's config exclusions;
|
|
65
|
+
// seeding the site collection arrays would make every page's resolved walk all
|
|
66
|
+
// 1,030 post docs). Every SITE COLLECTION joins them per build — the built-ins
|
|
67
|
+
// and the brand's own alike (SITE_COLLECTIONS below, #593).
|
|
68
|
+
const RESOLVED_SITE_EXCLUDE = new Set(['data', 'omega', 'time']);
|
|
69
|
+
|
|
70
|
+
// Consumer PAGE frontmatter is meta-only (Ian's rule, 2026-07-19: content
|
|
71
|
+
// lives in {% section %} calls — and nothing may even TRY to consume it from
|
|
72
|
+
// frontmatter). Softened same day (Ian: no build-fail): content keys in a
|
|
73
|
+
// page's own frontmatter are STRIPPED from the data cascade with a warning
|
|
74
|
+
// before resolution — sections/components can never see them, and the build
|
|
75
|
+
// proceeds. Plumbing keys (layout, permalink,
|
|
76
|
+
// tags, pagination — the RESOLVED_OMIT set) are filtered before this check;
|
|
77
|
+
// collections (_posts/_team/…) are content ENTRIES whose frontmatter IS the
|
|
78
|
+
// document, and layouts are theme voice that never renders standalone —
|
|
79
|
+
// neither passes through the guard.
|
|
80
|
+
// `config` is the page's omega.json5 override block (#607) — every config
|
|
81
|
+
// section a page restates lives under it, and nothing else may: what is in
|
|
82
|
+
// the config file goes under `config:`, what is not may not. Every other key
|
|
83
|
+
// here is PAGE machinery, which is why it has no config home: `meta` (the
|
|
84
|
+
// head walk, the only meta there is) and `schema` (JSON-LD SEO) are the
|
|
85
|
+
// page's own SEO surface, and `redirect` (the modules/utilities/redirect
|
|
86
|
+
// layout's target — docs/web/index.md) is layout MACHINERY a page configures
|
|
87
|
+
// the same way: shipped contracts that worked only from defaults/ and
|
|
88
|
+
// _layouts/ until #247 — a consumer page lost them silently.
|
|
89
|
+
// `sitemap` is NOT here: #564 folded `sitemap.include` into `meta.index`, so
|
|
90
|
+
// there is no longer a way to be indexable and out of the sitemap.
|
|
91
|
+
const PAGE_FRONTMATTER_ALLOW = new Set([
|
|
92
|
+
'config', 'meta',
|
|
93
|
+
'schema', 'redirect', 'templateEngineOverride', 'eleventyExcludeFromCollections',
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
// Deep merge shared with the section tag's defaults ← data ← args chain —
|
|
97
|
+
// one semantics for both override lanes (see src/merge.js).
|
|
98
|
+
const { deepMerge } = require('./merge.js');
|
|
99
|
+
|
|
100
|
+
// Dev surfaces never ship (#554, Ian 2026-08-24): every page the framework
|
|
101
|
+
// publishes under /test — the index, the styleguide, the library harnesses,
|
|
102
|
+
// the translation page — is a DEVELOPMENT surface, and so is a consumer page
|
|
103
|
+
// that claims one of those URLs. A production build emits none of them, the
|
|
104
|
+
// same omission the showcase gallery already rides. The boundary is a path
|
|
105
|
+
// SEGMENT, not a bare prefix: /testimonials is brand content.
|
|
106
|
+
const DEV_ONLY_URL_RE = /^\/test(\/|\.|$)/;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The first dead config read in a CONFIG VALUE
|
|
110
|
+
* ([#671](https://github.com/Omega-JS-Stack/omega/issues/671)). The per-template
|
|
111
|
+
* census cannot see this one: `targets.web.tagline: "Agency - {{ site.brand.name }}"`
|
|
112
|
+
* lives in omega.json5, renders through the same Liquid, and has rendered the
|
|
113
|
+
* brand name EMPTY since #611 on every page that falls through to the default.
|
|
114
|
+
* Same census, same section list — the address is a config key path instead of
|
|
115
|
+
* a file and line.
|
|
116
|
+
* @param {*} node - a config value (walked recursively)
|
|
117
|
+
* @param {string[]} [trail] - the key path to this node
|
|
118
|
+
* @returns {{ path: string, read: object }|null}
|
|
119
|
+
*/
|
|
120
|
+
function deadConfigValueRead(node, trail = []) {
|
|
121
|
+
if (typeof node === 'string') {
|
|
122
|
+
const read = templateReads(node).find((entry) => entry.root === 'site' && DEAD_SITE_SECTIONS.has(entry.key));
|
|
123
|
+
return read ? { path: trail.join('.'), read } : null;
|
|
124
|
+
}
|
|
125
|
+
if (!node || typeof node !== 'object') return null;
|
|
126
|
+
for (const [key, value] of Object.entries(node)) {
|
|
127
|
+
const found = deadConfigValueRead(value, [...trail, key]);
|
|
128
|
+
if (found) return found;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Configure an Eleventy instance as an OMEGA web engine.
|
|
135
|
+
* @param {object} eleventyConfig
|
|
136
|
+
* @param {object} options
|
|
137
|
+
* @param {string} options.consumerDir - the consumer site — Eleventy input dir
|
|
138
|
+
* @param {object} options.siteData - raw site data (site-data.json shape / resolved omega config)
|
|
139
|
+
* @param {string} [options.themesDir] - directory containing theme layer dirs (default: packaged themes)
|
|
140
|
+
* @param {string} [options.coreDir] - the framework core layer (default: packaged core)
|
|
141
|
+
* @param {string} [options.defaultsDir] - framework default pages dir (default: packaged defaults)
|
|
142
|
+
* @param {string} [options.activeTheme] - theme id (default: siteData.theme.id)
|
|
143
|
+
* @param {string} [options.layoutMode] - 'virtual' (default) or 'farm'
|
|
144
|
+
* @param {string} [options.farmDir] - symlink-farm target (farm mode)
|
|
145
|
+
* @param {object} [options.assetManifest] - js/css manifest from the asset build
|
|
146
|
+
* @param {string} [options.version] - the website target's own package version (site.omega.version → the Configuration block)
|
|
147
|
+
* @param {string} [options.pathPrefix] - the base path the built site is served under (#355) — default the domain root
|
|
148
|
+
* @param {string} [options.sampleAnchor] - YYYY-MM-DD rolling-date anchor for sample content (default: OMEGA_SAMPLE_ANCHOR env, then today)
|
|
149
|
+
* @param {object} [options.license] - the deploy-time license stamp (#320) → site.license (default: the keyless stamp)
|
|
150
|
+
* @returns {object} internals exposed for tests ({ site, layers, frontmatter })
|
|
151
|
+
*/
|
|
152
|
+
function configureOmega(eleventyConfig, options) {
|
|
153
|
+
// The capture scope (#200): every config-time read below goes through
|
|
154
|
+
// @omega.js/devkit/reads, and each one records the dir it touched. `omega dev` arms a
|
|
155
|
+
// handler before this runs and registers the recorded union as its
|
|
156
|
+
// config-reset watch targets when the scope closes on the way out — so a
|
|
157
|
+
// capture added here can never drift out of the watch set. Other callers
|
|
158
|
+
// (`omega build`, tests) open a scope nobody consumes: recording is cheap.
|
|
159
|
+
reads.openScope({ consumerDir: options.consumerDir });
|
|
160
|
+
try {
|
|
161
|
+
return buildConfig(eleventyConfig, options);
|
|
162
|
+
} finally {
|
|
163
|
+
// Config-time reading is over: hand the recorded union to whoever armed a
|
|
164
|
+
// handler (the dev loop's watch registration — src/commands/dev.js). In a
|
|
165
|
+
// FINALLY because a config build that throws (a bad section.json5, a
|
|
166
|
+
// broken omega.json5) must still register what it read: those very dirs
|
|
167
|
+
// are where the fix lands, and an unregistered union means the fix needs a
|
|
168
|
+
// restart to be seen.
|
|
169
|
+
reads.closeScope();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The config build itself — everything between opening and closing the capture
|
|
175
|
+
* scope (configureOmega above owns that lifecycle).
|
|
176
|
+
* @param {object} eleventyConfig
|
|
177
|
+
* @param {object} options - configureOmega's options
|
|
178
|
+
* @returns {object} internals exposed for tests ({ site, layers, frontmatter })
|
|
179
|
+
*/
|
|
180
|
+
function buildConfig(eleventyConfig, options) {
|
|
181
|
+
// The environment (#717): read ONCE, from the one surface every OMEGA
|
|
182
|
+
// framework answers with. `options.environment` is still the deliberate
|
|
183
|
+
// override the verbs thread (`omega build` → production) — it just arrives
|
|
184
|
+
// through getEnvironment() now instead of being compared as a loose string.
|
|
185
|
+
const environment = getEnvironment.call(options);
|
|
186
|
+
const themesDir = options.themesDir || PATHS.themes;
|
|
187
|
+
const coreDir = options.coreDir || PATHS.core;
|
|
188
|
+
const defaultsDir = options.defaultsDir || PATHS.defaults;
|
|
189
|
+
// The packaged defaults tree is a config-time input WHOLE (#136): default
|
|
190
|
+
// pages, the showcase and the sample corpora are all read out of it below,
|
|
191
|
+
// and probing the root records the tree as ONE dependency — nothing under it
|
|
192
|
+
// rides the incremental path, so a defaults reader added later is watched
|
|
193
|
+
// whether or not it reads through a dir this build touched.
|
|
194
|
+
reads.dirExists(defaultsDir);
|
|
195
|
+
// The two namespaces (#607): `config` is the brand's omega.json5 for this
|
|
196
|
+
// target, which every template reads through `resolved.config.*` (with the
|
|
197
|
+
// page's own `config:` block merged on top); `site` is BUILD FACTS — what
|
|
198
|
+
// the build knows and the config cannot say. The curated targets view is
|
|
199
|
+
// the one thing toSiteGlobal derives that IS a build fact, so it moves
|
|
200
|
+
// across; src/config-sections.js names both sets.
|
|
201
|
+
const config = toSiteGlobal(options.siteData);
|
|
202
|
+
const site = { targets: config.targets };
|
|
203
|
+
delete config.targets;
|
|
204
|
+
|
|
205
|
+
// The config-VALUE half of the #611 read guard (#671), before anything reads
|
|
206
|
+
// one: a dead read here is ONE key in omega.json5 and it silently drains the
|
|
207
|
+
// brand name out of every page's <title>. The curated targets view is gone
|
|
208
|
+
// by now — it is a build fact, not a config value.
|
|
209
|
+
const deadValue = deadConfigValueRead(config);
|
|
210
|
+
if (deadValue) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`[@omega.js/web:engine] config \`${deadValue.path}\` reads \`${deadValue.read.expression}\` — the brand config left `
|
|
213
|
+
+ `the \`site\` global, which is BUILD FACTS only (${SITE_FACT_KEYS.join(', ')}, plus the collections `
|
|
214
|
+
+ `targets.web.collections declares). Spell it `
|
|
215
|
+
+ `\`${deadValue.read.expression.replace(/^site\./, 'resolved.config.')}\` in omega.json5. `
|
|
216
|
+
+ 'Run `omega migrate` to rewrite it (docs/web/index.md).',
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const activeTheme = options.activeTheme || (config.theme && config.theme.id) || 'classy';
|
|
221
|
+
const layoutMode = options.layoutMode || 'virtual';
|
|
222
|
+
|
|
223
|
+
// Reflect the active theme into the config templates render against
|
|
224
|
+
config.theme = { ...(config.theme || {}), id: activeTheme };
|
|
225
|
+
|
|
226
|
+
// ---- The brand's own collections (#207): validated HERE, before anything
|
|
227
|
+
// reads them, so a bad entry fails the config build instead of quietly
|
|
228
|
+
// generating nothing. Built-ins plus these are the whole collection roster —
|
|
229
|
+
// directory tagging, permalinks, dev sampling and the generated pages below
|
|
230
|
+
// all read this one list.
|
|
231
|
+
const dynamicCollections = readCollections(config.collections);
|
|
232
|
+
const allCollections = [...BUILT_IN_COLLECTIONS, ...dynamicCollections];
|
|
233
|
+
|
|
234
|
+
// ---- Runtime composition: omega.json5 keeps ONE home per shared section
|
|
235
|
+
// (cloud, payment at the top level); the chrome + @omega.js/client contract
|
|
236
|
+
// reads them through resolved.config.client — the client settings blob (#1:
|
|
237
|
+
// renamed from the legacy `web_manager`; WebManager is not an OMEGA concept).
|
|
238
|
+
// Pricing loops over config.client.payment.products and the Configuration
|
|
239
|
+
// spread feeds the client. Compose here — same bridge pattern as extension's
|
|
240
|
+
// package.js mapping analytics.providers → the client's flat shape.
|
|
241
|
+
config.client = config.client || {};
|
|
242
|
+
if (config.cloud && config.cloud.config) {
|
|
243
|
+
config.client.firebase = config.client.firebase || {};
|
|
244
|
+
config.client.firebase.app = config.client.firebase.app || {};
|
|
245
|
+
config.client.firebase.app.config = config.cloud.config;
|
|
246
|
+
}
|
|
247
|
+
if (config.cloud && config.cloud.messaging && config.cloud.messaging.vapidKey) {
|
|
248
|
+
config.client.firebase = config.client.firebase || {};
|
|
249
|
+
config.client.firebase.messaging = config.client.firebase.messaging || {};
|
|
250
|
+
config.client.firebase.messaging.config = config.client.firebase.messaging.config || {};
|
|
251
|
+
config.client.firebase.messaging.config.vapidKey = config.cloud.messaging.vapidKey;
|
|
252
|
+
}
|
|
253
|
+
// The cancel-flow save offer (#268) is RESOLVED here, not in the browser: the
|
|
254
|
+
// framework default (50% off the next cycle) has ONE home in @omega.js/config,
|
|
255
|
+
// and the backend's apply route resolves the same section through the same
|
|
256
|
+
// function — so the dialog the customer reads and the coupon the provider
|
|
257
|
+
// creates can never name different numbers. The spread leaves config.payment
|
|
258
|
+
// itself alone; templates and the pricing composer read the brand's section.
|
|
259
|
+
if (config.payment) config.client.payment = { ...config.payment, winback: resolveWinbackOffer(config.payment) };
|
|
260
|
+
|
|
261
|
+
// The FEATURES CATALOG rides the same bridge (#647): the account page's usage
|
|
262
|
+
// bars and plan bullets read names, icons and definitions from it, and a
|
|
263
|
+
// feature is DEFINED in exactly one place for the server and the browser both.
|
|
264
|
+
if (config.features) config.client.features = config.features;
|
|
265
|
+
|
|
266
|
+
// Pricing view-model (C2): payment.products is the ONLY plan source — the
|
|
267
|
+
// seed surfaces as resolved.pricing (cascade still lets consumer frontmatter
|
|
268
|
+
// override presentation). A composed VIEW of the config, not config itself,
|
|
269
|
+
// so it rides the build-fact global. null = honest empty state.
|
|
270
|
+
site.pricing = composePricing(config.payment, config.features);
|
|
271
|
+
|
|
272
|
+
// Brand accent ramp (C3/D6): brand.color → the --omega-accent-* family,
|
|
273
|
+
// emitted by head.html after the CSS bundles. null (no/invalid color) =
|
|
274
|
+
// the token sheet's neutral placeholder stands.
|
|
275
|
+
site.brandTokens = composeBrandTokens(config.brand?.color);
|
|
276
|
+
|
|
277
|
+
// The license verdict ([#320](https://github.com/Omega-JS-Stack/omega/issues/320)):
|
|
278
|
+
// a BUILD fact — the production build's one deploy-time check, whose
|
|
279
|
+
// `attribution` decides the footer's "Powered by omegajs.dev" block. Nothing
|
|
280
|
+
// handed in (dev, a test, any lane that runs no check) is the keyless stamp,
|
|
281
|
+
// which is the state every build ships in today.
|
|
282
|
+
site.license = options.license || KEYLESS_STAMP;
|
|
283
|
+
|
|
284
|
+
// ---- Theme layer chain: active theme → base → core
|
|
285
|
+
// (consumer-local themes/<id> beats the packaged theme — C3 tier 2)
|
|
286
|
+
const themeLayers = resolveThemeLayers({ activeTheme, consumerDir: options.consumerDir, themesDir });
|
|
287
|
+
const layers = [...themeLayers, coreDir];
|
|
288
|
+
|
|
289
|
+
// ---- LiquidJS: Jekyll include syntax + layered include roots.
|
|
290
|
+
// timezoneOffset 0: filename dates are UTC midnights; rendering them in UTC
|
|
291
|
+
// matches CI-built Jekyll output (Actions runners are UTC).
|
|
292
|
+
// Consumer _includes first (somiibo's index includes
|
|
293
|
+
// frontend/components/hero-demo.html from its own _includes), then layers.
|
|
294
|
+
// EXISTING dirs only — LiquidJS probes every root per include lookup, and
|
|
295
|
+
// a nonexistent root costs ~1s over the corpus (measured: 3.60→4.65s).
|
|
296
|
+
const includeRoots = [path.join(options.consumerDir, '_includes'), ...layers.map((layer) => path.join(layer, '_includes'))]
|
|
297
|
+
.filter((dir) => reads.dirExists(dir));
|
|
298
|
+
eleventyConfig.setLiquidOptions({
|
|
299
|
+
jekyllInclude: true,
|
|
300
|
+
root: includeRoots,
|
|
301
|
+
timezoneOffset: 0,
|
|
302
|
+
// Parsed-template cache for includes: without it LiquidJS re-reads and
|
|
303
|
+
// re-parses every {% include %} on every render — the real core chrome
|
|
304
|
+
// (head/body/foot ≈ 885 lines) made that the corpus bottleneck
|
|
305
|
+
// (40.6s → measured again with cache below). Keyed by resolved file
|
|
306
|
+
// path, so theme switches (different winning path) stay correct.
|
|
307
|
+
cache: true,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// ---- Jekyll site-data emulation.
|
|
311
|
+
// site.data._includes.<path> mirrors UJM's json-in-_includes data system
|
|
312
|
+
// (admin sidebar/topbar read site.data._includes.admin.sections.sidebar):
|
|
313
|
+
// every .json under a layered include root lands at its path, higher
|
|
314
|
+
// layers win.
|
|
315
|
+
const dataIncludes = {};
|
|
316
|
+
for (const root of [...includeRoots].reverse()) {
|
|
317
|
+
for (const entry of reads.readdir(root, { recursive: true, withFileTypes: true })) {
|
|
318
|
+
if (!entry.isFile() || !entry.name.endsWith('.json')) continue;
|
|
319
|
+
const rel = path.relative(root, path.join(entry.parentPath, entry.name));
|
|
320
|
+
const segments = rel.replace(/\.json$/, '').split(path.sep);
|
|
321
|
+
let node = dataIncludes;
|
|
322
|
+
for (const segment of segments.slice(0, -1)) node = node[segment] = node[segment] || {};
|
|
323
|
+
try {
|
|
324
|
+
// JSON5 — the packaged section files (nav/footer/sidebar) use
|
|
325
|
+
// unquoted keys, comments, and trailing commas
|
|
326
|
+
node[segments[segments.length - 1]] = JSON5.parse(reads.read(path.join(root, rel)));
|
|
327
|
+
} catch { /* malformed data file — leave the slot empty */ }
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
site.data = { ...(config.data || {}), _includes: dataIncludes };
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
// ---- template-kit on Eleventy's own Liquid instance
|
|
334
|
+
// This instance backs template-kit's `markdown` filter; Eleventy keeps its
|
|
335
|
+
// own for .md templates. BOTH get the optimized image renderer (#193) so a
|
|
336
|
+
// markdown image is the same markup wherever the markdown is rendered, and
|
|
337
|
+
// BOTH run the typographer (#547): Kramdown smartened quotes, apostrophes,
|
|
338
|
+
// dashes and ellipses at build time, so a brand converted off Jekyll drifts
|
|
339
|
+
// into straight punctuation on its very next build without it (HARD RULE 5 —
|
|
340
|
+
// preserve semantics). A hard default, no consumer knob.
|
|
341
|
+
const md = markdownIt({ html: true, typographer: true });
|
|
342
|
+
applyMarkdownImages(md);
|
|
343
|
+
eleventyConfig.amendLibrary('md', (mdLib) => {
|
|
344
|
+
mdLib.set({ typographer: true });
|
|
345
|
+
applyMarkdownImages(mdLib);
|
|
346
|
+
});
|
|
347
|
+
const collectionsHolder = new Map();
|
|
348
|
+
|
|
349
|
+
// site.posts / site.team / site.updates / site.alternatives are Jekyll's
|
|
350
|
+
// site collections, flattened to Jekyll doc shape (post.url, post.date,
|
|
351
|
+
// post.post.title). Real arrays SYNCED when the holder fills — mutated in
|
|
352
|
+
// place, never reassigned: the site object is captured into Eleventy's
|
|
353
|
+
// data cascade at data-init (before collections compute), so replacing the
|
|
354
|
+
// array (or lazy getters) after that is invisible to templates.
|
|
355
|
+
//
|
|
356
|
+
// A BRAND's own collection (#207) publishes the same way (#593): a UJM
|
|
357
|
+
// consumer's `{% for product in site.products %}` is how a declared
|
|
358
|
+
// collection is read, and reaching templates only as `collections.products`
|
|
359
|
+
// (frontmatter under `.data`) rendered that loop empty on a green build —
|
|
360
|
+
// no warning, no output (HARD RULE 5, preserve semantics).
|
|
361
|
+
const SITE_COLLECTIONS = allCollections.map((collection) => collection.name);
|
|
362
|
+
for (const name of SITE_COLLECTIONS) site[name] = [];
|
|
363
|
+
// A collection array is a build fact templates read off `site` directly,
|
|
364
|
+
// never a `resolved` key — the brand's own on exactly the built-ins' terms.
|
|
365
|
+
const resolvedSiteExclude = new Set([...RESOLVED_SITE_EXCLUDE, ...SITE_COLLECTIONS]);
|
|
366
|
+
const holderSet = collectionsHolder.set.bind(collectionsHolder);
|
|
367
|
+
collectionsHolder.set = (name, docs) => {
|
|
368
|
+
if (SITE_COLLECTIONS.includes(name)) {
|
|
369
|
+
site[name].length = 0;
|
|
370
|
+
// ONE flattener for both lanes (#711): `jekyllDoc` gives a site doc the
|
|
371
|
+
// same lazy `content` the paginator's docs carry, so a layout reading
|
|
372
|
+
// `post.content` off site.posts counts the post's real body instead of
|
|
373
|
+
// an empty string (the newsflash homepage printed "1 min read" for every
|
|
374
|
+
// cover story and tile). id rides along inside it (Jekyll doc parity —
|
|
375
|
+
// layouts pass member.id/post.id to the omega_member/omega_post tags).
|
|
376
|
+
site[name].push(...docs.map((doc) => jekyllDoc(doc)));
|
|
377
|
+
}
|
|
378
|
+
return holderSet(name, docs);
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
// Icon roots (C4 cp108/cp111): the curated core set wins, then the
|
|
382
|
+
// brand's own Font Awesome set when one is supplied (Pro npm install or
|
|
383
|
+
// OMEGA_FONTAWESOME_ROOT download dir), with the free npm set (resolved,
|
|
384
|
+
// never vendored) as the always-present floor; the richest metadata
|
|
385
|
+
// resolves legacy aliases (search → magnifying-glass).
|
|
386
|
+
const fa = resolveFontAwesomeRoots();
|
|
387
|
+
|
|
388
|
+
// The resolved library with meta — the showcase/docs data source (spec §9).
|
|
389
|
+
// Registered unconditionally (deterministic data model, ~20 json5 reads);
|
|
390
|
+
// only the PAGES consuming it are development-gated below.
|
|
391
|
+
eleventyConfig.addGlobalData('sectionLibrary', buildSectionLibrary({
|
|
392
|
+
baseDirs: [options.consumerDir, ...themeLayers],
|
|
393
|
+
consumerDir: options.consumerDir,
|
|
394
|
+
}));
|
|
395
|
+
|
|
396
|
+
// The hero animation folders (#441): probed through the captured-read helper
|
|
397
|
+
// so a `_hero/` dir appearing mid-session resets the config, exactly like the
|
|
398
|
+
// section walk's own probes. The tag registered below reads them at render
|
|
399
|
+
// time, which is why the probe has to be its own line here.
|
|
400
|
+
watchHeroAnimations([options.consumerDir, ...themeLayers]);
|
|
401
|
+
|
|
402
|
+
eleventyConfig.amendLibrary('liquid', (engine) => {
|
|
403
|
+
// The section/component library tags resolve through the same precedence
|
|
404
|
+
// as every other layer: consumer-local _sections/_components → active
|
|
405
|
+
// theme → base (docs/web/omega-sections-spec.md).
|
|
406
|
+
// …and with them `{% hero_animation %}` (#441): the hero's custom demo
|
|
407
|
+
// branch calls it, and section markup is context-free by contract, so a TAG
|
|
408
|
+
// is how the folder's markup reaches the section at all.
|
|
409
|
+
registerSectionTags(engine, { baseDirs: [options.consumerDir, ...themeLayers] });
|
|
410
|
+
registerLiquid(engine, {
|
|
411
|
+
// template-kit's `site` slot is the CONFIG it reads (url, baseurl,
|
|
412
|
+
// icons.style, translation) — `relative_url`/`absolute_url` and every
|
|
413
|
+
// tag's ctx.site.config. The build-fact global is not its business.
|
|
414
|
+
site: config,
|
|
415
|
+
getCollection: (name) => collectionsHolder.get(name) || [],
|
|
416
|
+
getCollectionNames: () => [...collectionsHolder.keys()],
|
|
417
|
+
fileExists: (file) => fs.existsSync(path.join(options.consumerDir, file)),
|
|
418
|
+
markdown: (content) => md.render(content),
|
|
419
|
+
logos: { dir: path.join(coreDir, 'logos') },
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// ONE slugifier (#488): Eleventy ships a universal `slugify` filter of its
|
|
424
|
+
// own, and it OUTRANKS the template-kit one amendLibrary registers (the
|
|
425
|
+
// Liquid engine applies universal filters after the amendments) — so a term
|
|
426
|
+
// containing `&` was linked at `a-and-r` while the taxonomy page it pointed
|
|
427
|
+
// at was generated at `a-r` (src/collections.js slugifies with template-kit).
|
|
428
|
+
// Every term with an `&` shipped a guaranteed dead link. template-kit's is
|
|
429
|
+
// the authority — Jekyll-parity, so a migrating brand's taxonomy URLs never
|
|
430
|
+
// move — and a PLUGIN is where it has to be claimed: this config callback
|
|
431
|
+
// runs BEFORE Eleventy's own defaults, plugins run after.
|
|
432
|
+
eleventyConfig.addPlugin((config) => config.addFilter('slugify', slugify));
|
|
433
|
+
|
|
434
|
+
// ---- Layered layouts (zero copying): virtual templates or symlink farm.
|
|
435
|
+
// Layer order: consumer-local _layouts (sweet-saucy ships src/_layouts/
|
|
436
|
+
// recipe.html) → active theme → base → core (blueprint/root/modules —
|
|
437
|
+
// the theme-agnostic framework layouts live in the core layer).
|
|
438
|
+
// The farm must live OUTSIDE the input dir — inside it, Eleventy processes
|
|
439
|
+
// the symlinked layouts as content templates (and `../`-relative inputs
|
|
440
|
+
// defeat ignore globs).
|
|
441
|
+
const layoutMap = collectLayered([
|
|
442
|
+
path.join(options.consumerDir, '_layouts'),
|
|
443
|
+
...themeLayers.map((layer) => path.join(layer, '_layouts')),
|
|
444
|
+
path.join(coreDir, '_layouts'),
|
|
445
|
+
]);
|
|
446
|
+
// Consumer _layouts live INSIDE the input dir — without an ignore, Eleventy
|
|
447
|
+
// would process them as content templates (each writing to /index.html).
|
|
448
|
+
// Eleventy matches ignores against CWD-RELATIVE paths (`../corpus/...` for
|
|
449
|
+
// `../`-relative input dirs — the known farm gotcha), so the glob must be
|
|
450
|
+
// built the same way; the bare `**/` form covers cwd-contained inputs.
|
|
451
|
+
eleventyConfig.ignores.add('**/_layouts/**');
|
|
452
|
+
eleventyConfig.ignores.add(path.join(path.relative(process.cwd(), options.consumerDir), '_layouts', '**'));
|
|
453
|
+
// Consumer-local section/component/hero-animation folders are template
|
|
454
|
+
// machinery too — without the ignore, Eleventy would emit every section.html
|
|
455
|
+
// (and every `_hero/<name>/index.html`, #441) as content.
|
|
456
|
+
for (const machineryDir of ['_sections', '_components', HERO_DIR]) {
|
|
457
|
+
eleventyConfig.ignores.add(`**/${machineryDir}/**`);
|
|
458
|
+
eleventyConfig.ignores.add(path.join(path.relative(process.cwd(), options.consumerDir), machineryDir, '**'));
|
|
459
|
+
}
|
|
460
|
+
// A tier-2 theme lives INSIDE the input dir (`<src>/themes/<id>/`), and
|
|
461
|
+
// everything under it is LAYER SOURCE — never content. The families above
|
|
462
|
+
// covered a consumer's own machinery dirs but not a theme's copies of them,
|
|
463
|
+
// so a consumer-local theme shipping an include override failed the build on
|
|
464
|
+
// a duplicate permalink, and an INACTIVE local theme did it without even
|
|
465
|
+
// being in the chain ([#773](https://github.com/Omega-JS-Stack/omega/issues/773)).
|
|
466
|
+
// The whole namespace is the rule, because that is what it means: pages live
|
|
467
|
+
// in `pages/`, and `resolveThemeLayers` probes exactly this directory.
|
|
468
|
+
eleventyConfig.ignores.add(path.join(path.relative(process.cwd(), options.consumerDir), 'themes', '**'));
|
|
469
|
+
// The cwd-relative twin, kept to the machinery families rather than the whole
|
|
470
|
+
// namespace: unanchored, `**/themes/**` would also swallow a brand's own page
|
|
471
|
+
// at `pages/themes/…`, and no page is ever called `_includes`.
|
|
472
|
+
for (const machineryDir of ['_includes', '_layouts', '_sections', '_components', HERO_DIR]) {
|
|
473
|
+
eleventyConfig.ignores.add(`**/themes/*/${machineryDir}/**`);
|
|
474
|
+
}
|
|
475
|
+
if (layoutMode === 'farm') {
|
|
476
|
+
composeSymlinkFarm(layoutMap, options.farmDir);
|
|
477
|
+
eleventyConfig.setIncludesDirectory(path.relative(options.consumerDir, options.farmDir));
|
|
478
|
+
} else {
|
|
479
|
+
registerVirtualLayouts(eleventyConfig, layoutMap);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// The layout CHAIN behind a page, for the layout-keyed asset lane (#624).
|
|
483
|
+
// Eleventy merges a layout's frontmatter into the cascade but `data.layout`
|
|
484
|
+
// stays the page's OWN declaration, so the chain is walked here from the same
|
|
485
|
+
// layered map the render resolves through: `blueprint/careers` →
|
|
486
|
+
// `modules/utilities/redirect` → `frontend/core/cover`, and every layout in
|
|
487
|
+
// it contributes its assets.
|
|
488
|
+
// The one frontmatter fence, read by both hand-parsers below (the layout
|
|
489
|
+
// chain here, the page's own frontmatter further down).
|
|
490
|
+
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
491
|
+
const layoutFiles = new Map();
|
|
492
|
+
for (const [rel, abs] of layoutMap) layoutFiles.set(rel.replace(/\.[^.]+$/, '').split(path.sep).join('/'), abs);
|
|
493
|
+
const layoutParent = new Map();
|
|
494
|
+
const parentOf = (name) => {
|
|
495
|
+
if (layoutParent.has(name)) return layoutParent.get(name);
|
|
496
|
+
let parent = null;
|
|
497
|
+
const file = layoutFiles.get(name);
|
|
498
|
+
if (file) {
|
|
499
|
+
const match = (reads.read(file) || '').match(FRONTMATTER_RE);
|
|
500
|
+
try {
|
|
501
|
+
const own = match ? yaml.load(match[1]) : null;
|
|
502
|
+
if (own && typeof own.layout === 'string') parent = own.layout;
|
|
503
|
+
} catch { /* exotic frontmatter — the chain simply ends here */ }
|
|
504
|
+
}
|
|
505
|
+
layoutParent.set(name, parent);
|
|
506
|
+
return parent;
|
|
507
|
+
};
|
|
508
|
+
// Outermost FIRST (the root layout, then each layout that renders inside it),
|
|
509
|
+
// so assets load general → specific exactly as the layer chain does.
|
|
510
|
+
const layoutChain = (layout) => {
|
|
511
|
+
const chain = [];
|
|
512
|
+
let name = typeof layout === 'string' ? layout : null;
|
|
513
|
+
while (name && !chain.includes(name)) {
|
|
514
|
+
chain.unshift(name);
|
|
515
|
+
name = parentOf(name);
|
|
516
|
+
}
|
|
517
|
+
return chain;
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// ---- Frontmatter Liquid + collection tagging
|
|
521
|
+
const frontmatter = createFrontmatterResolver({ site });
|
|
522
|
+
// The page's pagination ALIAS (#544): `pagination.alias` is a declaration in
|
|
523
|
+
// the very cascade being resolved, so a frontmatter value naming the alias is
|
|
524
|
+
// per-page BY DEFINITION — it defers to the `resolved` pass exactly like a
|
|
525
|
+
// `resolved.`/`page.` ref, where the alias binding exists. Without this the
|
|
526
|
+
// value rendered empty at cache time and cached it, and every generated page
|
|
527
|
+
// shipped the same nameless title (280 of them, found porting trusteroo).
|
|
528
|
+
const aliasRoots = (data) => {
|
|
529
|
+
const alias = data.pagination && data.pagination.alias;
|
|
530
|
+
return alias ? new Set([alias]) : undefined;
|
|
531
|
+
};
|
|
532
|
+
// The alias BINDING, for the two passes that render with a per-page scope.
|
|
533
|
+
const aliasScope = (data) => {
|
|
534
|
+
const alias = data.pagination && data.pagination.alias;
|
|
535
|
+
return alias ? { [alias]: data[alias] } : undefined;
|
|
536
|
+
};
|
|
537
|
+
// The template's OWN frontmatter, re-parsed from the source file — no
|
|
538
|
+
// Eleventy hook sees template data apart from the cascade (preprocessors
|
|
539
|
+
// already get the merged tree). Two consumers: the meta-only PAGE guard
|
|
540
|
+
// below, and the collections parity repair in `resolved` — Eleventy's
|
|
541
|
+
// cascade merge breaks the inject-properties.rb contract for content
|
|
542
|
+
// ENTRIES (it CONCATS a doc array onto a layout-default array, and a
|
|
543
|
+
// layout-default object beats a doc scalar), so `resolved` re-applies a
|
|
544
|
+
// doc's own keys with OUR deepMerge. Values are raw (un-liquified) —
|
|
545
|
+
// resolved's renderData pass liquifies them with the same resolver.
|
|
546
|
+
// Virtual templates (blueprints, sample content) have no file to parse
|
|
547
|
+
// and keep pure cascade behavior; ANY read/parse failure degrades the
|
|
548
|
+
// same way.
|
|
549
|
+
const pageOwnData = new Map();
|
|
550
|
+
const pageOwnSource = new Map();
|
|
551
|
+
const readOwnSource = (inputPath) => {
|
|
552
|
+
if (pageOwnSource.has(inputPath)) return pageOwnSource.get(inputPath);
|
|
553
|
+
let source = null;
|
|
554
|
+
try {
|
|
555
|
+
source = fs.readFileSync(path.resolve(inputPath), 'utf8');
|
|
556
|
+
} catch { /* virtual template — there is no file behind it */ }
|
|
557
|
+
pageOwnSource.set(inputPath, source);
|
|
558
|
+
return source;
|
|
559
|
+
};
|
|
560
|
+
const readOwnFrontmatter = (inputPath) => {
|
|
561
|
+
if (pageOwnData.has(inputPath)) return pageOwnData.get(inputPath);
|
|
562
|
+
let own = null;
|
|
563
|
+
try {
|
|
564
|
+
const match = (readOwnSource(inputPath) || '').match(FRONTMATTER_RE);
|
|
565
|
+
const parsed = match ? yaml.load(match[1]) : null;
|
|
566
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
567
|
+
own = {};
|
|
568
|
+
for (const key of Object.keys(parsed)) {
|
|
569
|
+
if (!RESOLVED_OMIT.has(key)) own[key] = parsed[key];
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
} catch { /* virtual template or exotic frontmatter — cascade behavior stands */ }
|
|
573
|
+
pageOwnData.set(inputPath, own);
|
|
574
|
+
return own;
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// The template's own SIDECAR data file (`<template>.11tydata.*`), re-read
|
|
578
|
+
// for the same reason (#269): page frontmatter is meta-only, so the sidecar
|
|
579
|
+
// is the page-level lane for a SHELL layout's band data — and Eleventy's
|
|
580
|
+
// cascade CONCATS a sidecar array onto the layout default, so a page could
|
|
581
|
+
// only append to a band, never replace it. `resolved` re-applies the sidecar
|
|
582
|
+
// with OUR deepMerge, the arrays-replace rule every other override lane
|
|
583
|
+
// obeys; object/string keys land exactly where the cascade already put them.
|
|
584
|
+
// EVERY sidecar form Eleventy reads synchronously rides the lane (#543):
|
|
585
|
+
// .js/.cjs first, .json last, the order Eleventy itself merges them in, so
|
|
586
|
+
// one contract holds no matter which extension a page reached for (found
|
|
587
|
+
// porting trusteroo: the identical data as .11tydata.js still concatenated a
|
|
588
|
+
// 6-item faqs.items onto the layout default). `.mjs` is async-only and stays
|
|
589
|
+
// on pure cascade behavior, as a missing file — the common case — does.
|
|
590
|
+
const SIDECAR_EXTENSIONS = ['.js', '.cjs', '.json'];
|
|
591
|
+
const pageSidecarData = new Map();
|
|
592
|
+
const sidecarPath = (inputPath, extension) => {
|
|
593
|
+
const file = path.resolve(inputPath);
|
|
594
|
+
return path.join(path.dirname(file), `${path.basename(file, path.extname(file))}.11tydata${extension}`);
|
|
595
|
+
};
|
|
596
|
+
// A module sidecar is `require`d, so its exports outlive a config reset —
|
|
597
|
+
// busting the entry keeps a dev edit from serving the boot-time value.
|
|
598
|
+
const readSidecarFile = (file) => {
|
|
599
|
+
if (!file.endsWith('.json')) {
|
|
600
|
+
delete require.cache[require.resolve(file)];
|
|
601
|
+
const loaded = require(file);
|
|
602
|
+
return typeof loaded === 'function' ? loaded() : loaded;
|
|
603
|
+
}
|
|
604
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
605
|
+
};
|
|
606
|
+
const readSidecarData = (inputPath) => {
|
|
607
|
+
if (pageSidecarData.has(inputPath)) return pageSidecarData.get(inputPath);
|
|
608
|
+
let sidecar = null;
|
|
609
|
+
for (const extension of SIDECAR_EXTENSIONS) {
|
|
610
|
+
try {
|
|
611
|
+
const parsed = readSidecarFile(sidecarPath(inputPath, extension));
|
|
612
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
613
|
+
sidecar = sidecar || {};
|
|
614
|
+
for (const key of Object.keys(parsed)) {
|
|
615
|
+
if (!RESOLVED_OMIT.has(key)) sidecar[key] = deepMerge(sidecar[key], parsed[key]);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
} catch { /* no sidecar at this extension, or a malformed one — cascade behavior stands */ }
|
|
619
|
+
}
|
|
620
|
+
pageSidecarData.set(inputPath, sidecar);
|
|
621
|
+
return sidecar;
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
// Is a frontmatter key a CONFIG section (#607)? Every section the schema
|
|
625
|
+
// declares for a web build, plus whatever this brand's own omega.json5
|
|
626
|
+
// carries — a brand key with no schema rule yet is still config, and
|
|
627
|
+
// restating it bare would reach nothing at all. The SAME question decides
|
|
628
|
+
// both directions of the namespace rule (Ian 2026-08-26): a section spelled
|
|
629
|
+
// bare is an error, and a key under `config:` that is not one is an error.
|
|
630
|
+
const isConfigSection = (key) => CONFIG_SECTIONS.has(key) || Object.hasOwn(config, key);
|
|
631
|
+
|
|
632
|
+
// The READ half of the same seam (#611). A config key restated bare in
|
|
633
|
+
// frontmatter throws above; a config key READ off the `site` global does not
|
|
634
|
+
// fail at all — it renders an empty string, silently, on every page carrying
|
|
635
|
+
// one. So the page's own source is censused and the first sighting stops the
|
|
636
|
+
// build with the file and the expression. The census runs over the WHOLE
|
|
637
|
+
// file: `{{ site.brand.name }}` inside a frontmatter value is the same dead
|
|
638
|
+
// read as one in the body, and the playground's own pages hid theirs there.
|
|
639
|
+
//
|
|
640
|
+
// The section list is `CONFIG_SECTIONS` EXACTLY — never `isConfigSection`'s
|
|
641
|
+
// `hasOwn(config, key)` widening, which is right for the frontmatter lane and
|
|
642
|
+
// wrong here (the blind-verifier walk, 2026-08-25). `toSiteGlobal` always
|
|
643
|
+
// derives a top-level `url`, and a brand may override `omega`/`characters` in
|
|
644
|
+
// its own omega.json5; under the widening every one of those became "config",
|
|
645
|
+
// so `{{ site.url }}` — the canonical idiom `omega migrate` rule 7 itself
|
|
646
|
+
// writes — failed the build with no way to comply. A read guard's false
|
|
647
|
+
// positive is a build a brand cannot fix, so it answers to the schema alone.
|
|
648
|
+
//
|
|
649
|
+
// What COUNTS as a read (Liquid context, no fenced code, no `{% raw %}`) is
|
|
650
|
+
// config-sections.js's `templateReads` — the same census `omega migrate`'s
|
|
651
|
+
// rule 24 rewrites from, so the guard can never fail on something the verb
|
|
652
|
+
// would not have fixed.
|
|
653
|
+
// `meta` IS flagged here even though it is not config at all: a page spells
|
|
654
|
+
// `meta:` bare, but `site.meta` is as dead as any config read (the walk
|
|
655
|
+
// lives at `resolved.meta`), and rule 24 rewrites it — so the guard may
|
|
656
|
+
// demand it. DEAD_SITE_SECTIONS is that list, config sections plus `meta`.
|
|
657
|
+
const configReadsIn = (source) => templateReads(source)
|
|
658
|
+
.filter((read) => read.root === 'site' && DEAD_SITE_SECTIONS.has(read.key));
|
|
659
|
+
|
|
660
|
+
eleventyConfig.addPreprocessor('omega-frontmatter', 'md,html,liquid', (data) => {
|
|
661
|
+
const inputPath = data.page.inputPath;
|
|
662
|
+
const ownSource = readOwnSource(inputPath) || '';
|
|
663
|
+
|
|
664
|
+
// The OTHER dead read the census owns (#595): UJM's per-render `random_id`
|
|
665
|
+
// global, which OMEGA does not have — the read renders empty and every id
|
|
666
|
+
// it scopes collides. A WARNING, not a throw, unlike the config reads
|
|
667
|
+
// above: `random_id` is an ordinary variable name, so a layout or an
|
|
668
|
+
// include may legitimately assign it for this page, and a read guard's
|
|
669
|
+
// false positive is a build a brand cannot fix.
|
|
670
|
+
if (!assignsRandomId(ownSource)) {
|
|
671
|
+
const leftover = randomIdReads(ownSource);
|
|
672
|
+
if (leftover.length) {
|
|
673
|
+
logger.warn(
|
|
674
|
+
`${inputPath}:${leftover[0].line}: reads a bare \`random_id\` — UJM's per-render global is gone, so it renders `
|
|
675
|
+
+ `EMPTY and every id built from it collides. Assign it first: \`${RANDOM_ID_ASSIGN_IDIOM}\`. `
|
|
676
|
+
+ 'Run `omega migrate` to write it (docs/web/index.md).',
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// The dead-read guard (#611), over every template with a FILE behind it —
|
|
682
|
+
// a page, a collection document, a consumer's own anything. Virtual
|
|
683
|
+
// templates (blueprints, the showcase, sample content) have no source to
|
|
684
|
+
// read and the static lane already covers those surfaces.
|
|
685
|
+
const deadReads = configReadsIn(ownSource);
|
|
686
|
+
if (deadReads.length) {
|
|
687
|
+
const first = deadReads[0];
|
|
688
|
+
throw new Error(
|
|
689
|
+
`[@omega.js/web:engine] ${inputPath}:${first.line}: reads \`${first.expression}\` — the brand config left the `
|
|
690
|
+
+ `\`site\` global, which is BUILD FACTS only (${SITE_FACT_KEYS.join(', ')}, plus the collections `
|
|
691
|
+
+ `targets.web.collections declares). Spell it `
|
|
692
|
+
+ `\`${first.expression.replace(/^site\./, 'resolved.config.')}\``
|
|
693
|
+
+ `${deadReads.length > 1 ? ` (and ${deadReads.length - 1} more in this file)` : ''}. `
|
|
694
|
+
+ 'Run `omega migrate` to rewrite them (docs/web/index.md).',
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Directory → collection tag, the Jekyll convention: a document under
|
|
699
|
+
// `_<collection>/` belongs to that collection, the brand's own included.
|
|
700
|
+
const collection = allCollections.find((entry) => inputPath.includes(`/${entry.dir}/`));
|
|
701
|
+
if (collection) data.tags = [collection.name];
|
|
702
|
+
|
|
703
|
+
// A BRAND collection's documents (#317): the built-ins ship their own
|
|
704
|
+
// blueprint layouts and meta, a declared collection's documents get theirs
|
|
705
|
+
// here — the collection block their layout renders from, and per-document
|
|
706
|
+
// meta. A document that falls back to the site's title and description is
|
|
707
|
+
// the indexable duplication #312 fixed one layer up.
|
|
708
|
+
if (collection && dynamicCollections.includes(collection)) {
|
|
709
|
+
applyDocumentData(collection, data, config.brand && config.brand.name);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// The OTHER half of the namespace rule (#607, Ian 2026-08-26): what is in
|
|
713
|
+
// the config file goes under `config:`, and what is NOT in the config file
|
|
714
|
+
// may not. A key under `config:` that no omega.json5 section answers to
|
|
715
|
+
// merges into `resolved.config` and is read by nothing — a typo (`them:`),
|
|
716
|
+
// or page data written into the config lane, both silent. It fails here,
|
|
717
|
+
// for every template that carries frontmatter: a collection document's
|
|
718
|
+
// `config:` block is the same block a page's is.
|
|
719
|
+
const ownFrontmatter = readOwnFrontmatter(inputPath) || {};
|
|
720
|
+
if ('config' in ownFrontmatter) {
|
|
721
|
+
const ownConfig = ownFrontmatter.config;
|
|
722
|
+
|
|
723
|
+
// Before the membership check: a `config:` that is not a MAP of sections
|
|
724
|
+
// at all. `resolved` deep-merges the page block OVER the seeded config,
|
|
725
|
+
// and a null (the empty key `config:` with nothing under it), an array or
|
|
726
|
+
// a scalar REPLACES it — so resolved.config stops being the brand config
|
|
727
|
+
// for that page and every read under it renders empty, silently. The
|
|
728
|
+
// shape is the brand's own typo, so it fails here naming what arrived.
|
|
729
|
+
if (!ownConfig || typeof ownConfig !== 'object' || Array.isArray(ownConfig)) {
|
|
730
|
+
const shape = ownConfig === null || ownConfig === undefined
|
|
731
|
+
? 'an EMPTY key (null)'
|
|
732
|
+
: (Array.isArray(ownConfig) ? 'an array' : `a ${typeof ownConfig}`);
|
|
733
|
+
throw new Error(
|
|
734
|
+
`[@omega.js/web:engine] ${inputPath}: \`config:\` is ${shape} — it must be a MAP of omega.json5 `
|
|
735
|
+
+ 'sections (config:\n theme:\n …). Any other shape REPLACES the whole merged config for this page, '
|
|
736
|
+
+ 'so every `resolved.config.*` read on it renders empty. Delete the key, or put the sections under it '
|
|
737
|
+
+ '(docs/web/frontmatter.md).',
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const strays = Object.keys(ownConfig).filter((key) => !isConfigSection(key));
|
|
742
|
+
if (strays.length) {
|
|
743
|
+
throw new Error(
|
|
744
|
+
`[@omega.js/web:engine] ${inputPath}: \`config:\` carries `
|
|
745
|
+
+ `${strays.length > 1 ? 'keys' : 'a key'} ${strays.map((key) => `\`${key}\``).join(', ')} that omega.json5 has no `
|
|
746
|
+
+ `${strays.length > 1 ? 'sections' : 'section'} for, so nothing reads `
|
|
747
|
+
+ `${strays.length > 1 ? 'them' : 'it'}. \`config:\` holds omega.json5 sections ONLY `
|
|
748
|
+
+ `(${[...CONFIG_SECTIONS].join(', ')}); page machinery (meta, schema, redirect) stays bare, and page `
|
|
749
|
+
+ 'CONTENT lives in {% section %} calls in the page body (docs/web/frontmatter.md).',
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// The meta-only guard: real files under pages/ may carry ONLY meta keys
|
|
755
|
+
// in frontmatter. readOwnFrontmatter already filters the plumbing set
|
|
756
|
+
// (layout, permalink, tags, pagination…) and returns null for virtual
|
|
757
|
+
// templates (blueprints, showcase) — so anything left outside the allow
|
|
758
|
+
// set is content in frontmatter, a lane that doesn't exist: the keys are
|
|
759
|
+
// deleted from the cascade before resolution (sections/components never
|
|
760
|
+
// see them) and the build warns. The WHOLE key drops for this page — a
|
|
761
|
+
// directory-data value merged under the same key falls back to section
|
|
762
|
+
// defaults too; deleting the frontmatter key (what the warning says)
|
|
763
|
+
// restores it.
|
|
764
|
+
if (/\/pages\//.test(inputPath)) {
|
|
765
|
+
const own = readOwnFrontmatter(inputPath);
|
|
766
|
+
const ownKeys = own ? Object.keys(own).filter((key) => !PAGE_FRONTMATTER_ALLOW.has(key)) : [];
|
|
767
|
+
|
|
768
|
+
// A config section restated BARE is a build ERROR, not smuggled content
|
|
769
|
+
// (#607): the two namespaces are separate, so a bare `theme:` would look
|
|
770
|
+
// like it overrode omega.json5 and reach nothing. No backwards
|
|
771
|
+
// compatibility — `omega migrate` rewrites the page.
|
|
772
|
+
const bareConfig = ownKeys.filter((key) => isConfigSection(key));
|
|
773
|
+
if (bareConfig.length) {
|
|
774
|
+
throw new Error(
|
|
775
|
+
`[@omega.js/web:engine] ${inputPath}: frontmatter restates the config `
|
|
776
|
+
+ `${bareConfig.length > 1 ? 'sections' : 'section'} ${bareConfig.map((key) => `\`${key}\``).join(', ')} bare. `
|
|
777
|
+
+ 'A page overrides omega.json5 under a `config:` parent '
|
|
778
|
+
+ `(config:\n ${bareConfig[0]}:\n …); no config section keeps a bare spelling. `
|
|
779
|
+
+ 'Run `omega migrate` to move it (docs/web/frontmatter.md).',
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const contentKeys = ownKeys.filter((key) => !isConfigSection(key));
|
|
784
|
+
if (contentKeys.length) {
|
|
785
|
+
for (const key of contentKeys) delete data[key];
|
|
786
|
+
logger.warn(
|
|
787
|
+
`${inputPath}: ignoring frontmatter content keys (${contentKeys.join(', ')}) — `
|
|
788
|
+
+ `consumer page frontmatter is meta-only (layout, permalink, meta, schema, config); `
|
|
789
|
+
+ `content lives in {% section %} calls in the page body (docs/web/sections.md).`,
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// The cascade IS the frontmatter render scope (#542): a value reaching for
|
|
795
|
+
// one of the consumer's own `_data` globals (`{{ brands.size }}` beside a
|
|
796
|
+
// `_data/brands.json`) reads the same globals a template render reads,
|
|
797
|
+
// instead of rendering empty against a site-only scope. The page's own
|
|
798
|
+
// pagination alias defers with the per-page refs (#544).
|
|
799
|
+
frontmatter.resolveData(data, {
|
|
800
|
+
globals: data,
|
|
801
|
+
defer: aliasRoots(data),
|
|
802
|
+
file: inputPath,
|
|
803
|
+
});
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// The meta-file lane's reader (#141): a page's cascade data may still hold
|
|
807
|
+
// RAW Liquid contributed by a LAYOUT (blueprint/updates/update sets
|
|
808
|
+
// `meta.title: "Version {{ page.update.version }} - {{ site.brand.name }}"`)
|
|
809
|
+
// — per-page refs defer at frontmatter time and render in that page's own
|
|
810
|
+
// `resolved` computed. sitemap.xml/pages.json/llms.txt read OTHER pages'
|
|
811
|
+
// data, so they read it through this filter: the value renders against THAT
|
|
812
|
+
// item's scope, exactly as the page's own head rendered it. Reading
|
|
813
|
+
// `item.data.resolved` directly would work too but change the fallbacks —
|
|
814
|
+
// resolved seeds the site sections, so every entry without its own title
|
|
815
|
+
// would inherit the SITE's meta.title instead of the template's default.
|
|
816
|
+
const dataAt = (data, dottedPath) => String(dottedPath).split('.')
|
|
817
|
+
.reduce((node, key) => (node == null ? node : node[key]), data);
|
|
818
|
+
eleventyConfig.addFilter('omega_rendered', (item, dottedPath) => {
|
|
819
|
+
const value = dataAt(item && item.data, dottedPath);
|
|
820
|
+
if (typeof value !== 'string' || !(value.includes('{{') || value.includes('{%'))) return value;
|
|
821
|
+
const resolved = item.data.resolved;
|
|
822
|
+
if (!resolved) return value; // no computed data (never in a real build) — raw beats crashing
|
|
823
|
+
// THAT item's scope, whole: its pagination alias binding (#544) and the
|
|
824
|
+
// `_data` globals its own cascade carries (#542) — a generated page's
|
|
825
|
+
// per-document title has to read the same here as in its own head.
|
|
826
|
+
return frontmatter.render(value, {
|
|
827
|
+
resolved,
|
|
828
|
+
page: { ...resolved, resolved, url: item.url, slug: item.data.page.fileSlug, fileSlug: item.data.page.fileSlug },
|
|
829
|
+
...aliasScope(item.data),
|
|
830
|
+
}, { globals: item.data, file: item.data.page.inputPath });
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
// A body that composes sections is Liquid-HTML, not prose: rendered
|
|
834
|
+
// section markup must never pass through the markdown transform — blank
|
|
835
|
+
// lines inside emitted HTML blocks become empty <p> elements that land
|
|
836
|
+
// as REAL grid children (the 2026-07-19 live find: both brand homepages'
|
|
837
|
+
// bento/stats scattered by phantom <p>s). `omega customize` obeys the
|
|
838
|
+
// same rule by materializing .html copies; hand-authored .md
|
|
839
|
+
// compositions get it automatically here.
|
|
840
|
+
eleventyConfig.addPreprocessor('omega-composition-liquid', 'md', (data, content) => {
|
|
841
|
+
if (data.templateEngineOverride === undefined && /^\s*{%-?\s*(section|composition)\b/m.test(content)) {
|
|
842
|
+
data.templateEngineOverride = 'liquid';
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
// ---- Jekyll conventions + page.resolved equivalent
|
|
847
|
+
// Every template's permalink resolves through THIS — the global computed
|
|
848
|
+
// below and every gated virtual template (renderGate) — so the production
|
|
849
|
+
// /test omission (#554) covers a consumer's own page at a /test URL exactly
|
|
850
|
+
// as it covers the framework default that page took over.
|
|
851
|
+
const resolvePermalink = (data) => {
|
|
852
|
+
const permalink = jekyllPermalink(data, allCollections);
|
|
853
|
+
if (environment === 'production' && typeof permalink === 'string' && DEV_ONLY_URL_RE.test(permalink)) {
|
|
854
|
+
return false;
|
|
855
|
+
}
|
|
856
|
+
return permalink;
|
|
857
|
+
};
|
|
858
|
+
eleventyConfig.addGlobalData('eleventyComputed', {
|
|
859
|
+
permalink: resolvePermalink,
|
|
860
|
+
// Jekyll paginator compat: layouts iterate `paginator.posts` with Jekyll
|
|
861
|
+
// post shapes (post.url, post.post.title), so items are flattened
|
|
862
|
+
// ({ url, date, ...data }) — references, not copies.
|
|
863
|
+
paginator: (data) => {
|
|
864
|
+
const p = data.pagination;
|
|
865
|
+
if (!p || !p.items) return undefined;
|
|
866
|
+
const totalPages = (p.pages || []).length;
|
|
867
|
+
return {
|
|
868
|
+
posts: p.items.map(jekyllDoc),
|
|
869
|
+
page: p.pageNumber + 1,
|
|
870
|
+
per_page: p.items.length,
|
|
871
|
+
total_pages: totalPages,
|
|
872
|
+
previous_page: p.pageNumber > 0 ? p.pageNumber : null,
|
|
873
|
+
previous_page_path: (p.href && p.href.previous) || null,
|
|
874
|
+
next_page: p.pageNumber + 2 <= totalPages ? p.pageNumber + 2 : null,
|
|
875
|
+
next_page_path: (p.href && p.href.next) || null,
|
|
876
|
+
};
|
|
877
|
+
},
|
|
878
|
+
// Per-page asset lookups against the content-hash manifest, keyed by URL
|
|
879
|
+
// alone (spec §7 — the asset_path frontmatter override is dead): `<key>`
|
|
880
|
+
// for flat entries (js/pages/pricing.js), `<key>/index` for per-page dirs
|
|
881
|
+
// (js/pages/pricing/index.js), and [name] wildcard segments for generated
|
|
882
|
+
// page families (js/pages/blog/[slug].js serves every /blog/<slug> post).
|
|
883
|
+
// Each answer is a LIST — every layer that ships a file for this URL, in
|
|
884
|
+
// load order (#624), because page assets never replace one another.
|
|
885
|
+
pageAssets: (data) => {
|
|
886
|
+
const manifest = data.assetManifest || {};
|
|
887
|
+
const trimmed = (data.page.url || '/').replace(/^\/|\/$/g, '');
|
|
888
|
+
const base = trimmed === '' ? 'index' : trimmed;
|
|
889
|
+
return {
|
|
890
|
+
js: resolvePageAsset(manifest.js && manifest.js.pages, base) || [],
|
|
891
|
+
css: resolvePageAsset(manifest.css && manifest.css.pages, base) || [],
|
|
892
|
+
};
|
|
893
|
+
},
|
|
894
|
+
// The same lookup, keyed by LAYOUT instead of URL (#624): a page gets every
|
|
895
|
+
// asset of every layout in its chain, outermost first — the chain, not just
|
|
896
|
+
// `data.layout`, because a blueprint that sits on the redirect layout
|
|
897
|
+
// (blueprint/careers) must still get the redirect layout's script.
|
|
898
|
+
layoutAssets: (data) => {
|
|
899
|
+
const manifest = data.assetManifest || {};
|
|
900
|
+
const chain = layoutChain(data.layout);
|
|
901
|
+
return {
|
|
902
|
+
js: chain.flatMap((name) => resolvePageAsset(manifest.js && manifest.js.layouts, name) || []),
|
|
903
|
+
css: chain.flatMap((name) => resolvePageAsset(manifest.css && manifest.css.layouts, name) || []),
|
|
904
|
+
};
|
|
905
|
+
},
|
|
906
|
+
resolved: (data) => {
|
|
907
|
+
// inject-properties.rb parity: resolved = build facts ← layout chain ←
|
|
908
|
+
// page data, PLUS the config namespace (#607).
|
|
909
|
+
//
|
|
910
|
+
// `resolved.config` is the WHOLE brand config with this page's own
|
|
911
|
+
// `config:` block merged on top — the seed below puts the entire config
|
|
912
|
+
// under the cascade (never a filtered subset: a brand section with no
|
|
913
|
+
// schema rule is config too), and the generic merge that follows lets a
|
|
914
|
+
// layout's or page's `config:` win key by key. `resolved.meta` carries
|
|
915
|
+
// ONE seed and nothing more: meta is page machinery (Ian 2026-08-26), so
|
|
916
|
+
// the walk is the site-wide index floor → page `meta:` → layout `meta`,
|
|
917
|
+
// with head.html falling back to brand.name / brand.description.
|
|
918
|
+
const out = {};
|
|
919
|
+
for (const key of Object.keys(site)) {
|
|
920
|
+
if (!resolvedSiteExclude.has(key)) out[key] = site[key];
|
|
921
|
+
}
|
|
922
|
+
out.config = config;
|
|
923
|
+
|
|
924
|
+
// The site-wide index default (#564, Ian 2026-09-09, one name at both
|
|
925
|
+
// levels): `targets.web.meta.index` is the SAME key a page writes, so
|
|
926
|
+
// the site default and the page override never drift apart. It is a
|
|
927
|
+
// SEED, not a second emission path: every signal reads the one resolved
|
|
928
|
+
// `meta.index` below, so the robots meta, sitemap.xml, llms.txt and
|
|
929
|
+
// pages.json cannot disagree. The merge that follows puts page and
|
|
930
|
+
// layout `meta:` over it, which is how a page's own `meta.index: true`
|
|
931
|
+
// exempts itself from a noindexed site. Consequential-feature doctrine
|
|
932
|
+
// (#527): only the LITERAL false takes a whole site off search — absent
|
|
933
|
+
// or true is today's behavior.
|
|
934
|
+
if (config.meta && config.meta.index === false) out.meta = { index: false };
|
|
935
|
+
|
|
936
|
+
for (const key of Object.keys(data)) {
|
|
937
|
+
if (!RESOLVED_OMIT.has(key)) out[key] = deepMerge(out[key], data[key]);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Parity repair — sidecar lane (#269): the page's own data file is the
|
|
941
|
+
// sanctioned page-level lane for shell-layout band data, so its keys are
|
|
942
|
+
// re-applied here with the arrays-replace rule. It sits BELOW the
|
|
943
|
+
// template's own frontmatter, exactly where the cascade puts it.
|
|
944
|
+
const sidecar = readSidecarData(data.page.inputPath);
|
|
945
|
+
if (sidecar) {
|
|
946
|
+
for (const key of Object.keys(sidecar)) {
|
|
947
|
+
out[key] = deepMerge(out[key], sidecar[key]);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Parity repair — collections lane: Eleventy's cascade merge concats
|
|
952
|
+
// arrays and lets a layout-default object beat a doc scalar — re-apply
|
|
953
|
+
// the template's OWN frontmatter (re-parsed from source) with OUR
|
|
954
|
+
// semantics, so a content entry (_posts/_team/_alternatives docs)
|
|
955
|
+
// always wins its own keys outright: arrays REPLACE. Pages are
|
|
956
|
+
// meta-only (guard above stripped content keys from the cascade) — this
|
|
957
|
+
// lane filters to the allow set so nothing stripped re-enters here.
|
|
958
|
+
const own = readOwnFrontmatter(data.page.inputPath);
|
|
959
|
+
if (own) {
|
|
960
|
+
const isPage = /\/pages\//.test(data.page.inputPath);
|
|
961
|
+
for (const key of Object.keys(own)) {
|
|
962
|
+
if (isPage && !PAGE_FRONTMATTER_ALLOW.has(key)) continue;
|
|
963
|
+
out[key] = deepMerge(out[key], own[key]);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// The index posture's AUTOMATIC exclusions (#564). Everything above is
|
|
968
|
+
// authored: the site default, the layout's `meta:`, the page's own.
|
|
969
|
+
// These are the framework's own, applied last because they are not
|
|
970
|
+
// overridable: a draft is unpublished, a dev surface never ships, an
|
|
971
|
+
// admin screen is private, a redirect stub is a forwarding URL, and
|
|
972
|
+
// pages 2..N of a LISTING are duplicates of the canonical page 1. Every
|
|
973
|
+
// output reads the ONE value they land on, so no template keeps a rule
|
|
974
|
+
// list of its own and the signals cannot disagree.
|
|
975
|
+
//
|
|
976
|
+
// Alias pagination is the other shape the machinery serves (one page per
|
|
977
|
+
// taxonomy TERM, `size: 1` + `alias`), where a page number is an index
|
|
978
|
+
// into terms and says nothing about duplication; those pages carry their
|
|
979
|
+
// own `meta.index` on their layout. Eleventy's computed-data dependency
|
|
980
|
+
// pass probes with proxies, so the number is only a number on a real
|
|
981
|
+
// render.
|
|
982
|
+
const pageNumber = data.pagination && !data.pagination.alias && data.pagination.pageNumber;
|
|
983
|
+
const url = typeof data.page.url === 'string' ? data.page.url : '';
|
|
984
|
+
const layoutName = typeof out.layout === 'string' ? out.layout : '';
|
|
985
|
+
|
|
986
|
+
if (out.draft
|
|
987
|
+
|| DEV_ONLY_URL_RE.test(url)
|
|
988
|
+
|| url.startsWith('/admin/')
|
|
989
|
+
|| layoutName.includes('redirect')
|
|
990
|
+
|| (typeof pageNumber === 'number' && pageNumber > 0)) {
|
|
991
|
+
out.meta = { ...out.meta, index: false };
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Layout-frontmatter Liquid: the preprocessor only sees PAGE frontmatter,
|
|
995
|
+
// so cascade data contributed by layouts (the real base contact layout
|
|
996
|
+
// carries `{{ site.brand.name }}`, real sweet-saucy recipe carries
|
|
997
|
+
// `{{ page.recipe.title }}` in meta values) still holds raw refs here.
|
|
998
|
+
// `resolved` is in context too — layout defaults template on the merged
|
|
999
|
+
// data (base alternative: tagline "The #1 {{ resolved.alternative.
|
|
1000
|
+
// competitor.name }} alternative"); `page.resolved` covers the legacy
|
|
1001
|
+
// spelling in not-yet-migrated consumer frontmatter.
|
|
1002
|
+
// The page's pagination alias is bound here too (#544) — this IS the
|
|
1003
|
+
// render-time pass its refs deferred to — and the cascade rides along as
|
|
1004
|
+
// the globals a `_data` ref resolves against (#542): the sidecar re-apply
|
|
1005
|
+
// above put RAW values back, so this is where they render.
|
|
1006
|
+
// Copy-on-write: shared cascade sub-objects are never mutated, and
|
|
1007
|
+
// pages with no remaining refs return `out` untouched.
|
|
1008
|
+
return frontmatter.renderData(out, {
|
|
1009
|
+
resolved: out,
|
|
1010
|
+
page: { ...out, resolved: out, url: data.page.url, slug: data.page.fileSlug, fileSlug: data.page.fileSlug },
|
|
1011
|
+
...aliasScope(data),
|
|
1012
|
+
}, { globals: data, file: data.page.inputPath });
|
|
1013
|
+
},
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
// page.url parity with Jekyll (legacy UJM): the flat `about.html` output
|
|
1017
|
+
// still reads as '/about' everywhere templates look (canonical, hreflang,
|
|
1018
|
+
// data-page-path, nav active-detection, collection doc.url). index.html
|
|
1019
|
+
// outputs are collapsed to directory URLs by Eleventy before transforms run.
|
|
1020
|
+
eleventyConfig.addUrlTransform(({ url }) => {
|
|
1021
|
+
if (typeof url === 'string' && url.endsWith('.html')) {
|
|
1022
|
+
return url.slice(0, -'.html'.length);
|
|
1023
|
+
}
|
|
1024
|
+
return undefined;
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
// ---- Collections: posts, alternatives, team, blog taxonomy, and the
|
|
1028
|
+
// brand's own collections plus their taxonomies (#207)
|
|
1029
|
+
registerCollections(eleventyConfig, collectionsHolder, dynamicCollections);
|
|
1030
|
+
|
|
1031
|
+
// ---- Dev-mode collection limiting (#190): a brand with thousands of posts
|
|
1032
|
+
// samples them locally so the dev build stays fast — the sampled-out
|
|
1033
|
+
// documents never enter the build at all. The config is validated in EVERY
|
|
1034
|
+
// environment (a typo must fail `omega build` too), but only a development
|
|
1035
|
+
// build samples: a shipped site is always the whole site.
|
|
1036
|
+
applyCollectionLimits(eleventyConfig, {
|
|
1037
|
+
consumerDir: options.consumerDir,
|
|
1038
|
+
limits: config.dev && config.dev.limitCollections,
|
|
1039
|
+
collections: dynamicCollections,
|
|
1040
|
+
environment,
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
// ---- The live decisions (#200 Lane B): which URLs the consumer's own pages
|
|
1044
|
+
// claim, and which collections the brand has real content in. Both are
|
|
1045
|
+
// CONTENT facts, so both are rescan captures — a config-time value would
|
|
1046
|
+
// only refresh through a config reset, and a config reset on every page edit
|
|
1047
|
+
// is exactly what the incremental contract forbids. Everything below asks
|
|
1048
|
+
// this object at render time.
|
|
1049
|
+
const decisions = createDecisions({
|
|
1050
|
+
consumerDir: options.consumerDir,
|
|
1051
|
+
collectionDirs: SAMPLE_SETS.map((set) => set.collectionDir),
|
|
1052
|
+
environment,
|
|
1053
|
+
});
|
|
1054
|
+
// Re-scan before every REBUILD, so a render can never read a decision older
|
|
1055
|
+
// than its own build — whichever watcher saw the file event first. The dev
|
|
1056
|
+
// loop's rescan watcher is the prompt lane (it updates and reports the
|
|
1057
|
+
// moment a file lands, before any rebuild finishes); this is the ordering
|
|
1058
|
+
// guarantee. A production build has exactly one build and one scan.
|
|
1059
|
+
if (environment !== 'production') {
|
|
1060
|
+
eleventyConfig.on('eleventy.before', () => decisions.refresh());
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// ---- Default pages: virtual templates the consumer can take over by
|
|
1064
|
+
// claiming the same permalink. The showcase (development only) rides the
|
|
1065
|
+
// same lane: auto-generated pages over the resolved library — /test/sections
|
|
1066
|
+
// + one page per entry (spec §9). Production builds omit the showcase
|
|
1067
|
+
// entirely: a page rendering EVERY section would keep every section's CSS
|
|
1068
|
+
// alive through the PurgeCSS content scan and quietly defeat §7
|
|
1069
|
+
// self-trimming.
|
|
1070
|
+
// The framework's own /test pages ride that same omission (#554): their
|
|
1071
|
+
// permalinks are config-time facts, so a production build never registers
|
|
1072
|
+
// them at all. A consumer page at a /test URL is a render-time fact and is
|
|
1073
|
+
// shut by resolvePermalink above.
|
|
1074
|
+
const frameworkPages = [
|
|
1075
|
+
...[...collectLayered([path.join(defaultsDir, 'pages')])]
|
|
1076
|
+
.map(([rel, abs]) => ({ virtual: `omega-defaults/${rel}`, label: `defaults/pages/${rel}`, abs })),
|
|
1077
|
+
...(environment === 'production' ? [] : [...collectLayered([path.join(defaultsDir, 'showcase')])]
|
|
1078
|
+
.map(([rel, abs]) => ({ virtual: `omega-defaults/showcase/${rel}`, label: `defaults/showcase/${rel}`, abs }))),
|
|
1079
|
+
].map((page) => {
|
|
1080
|
+
const raw = reads.read(page.abs);
|
|
1081
|
+
return { ...page, raw, url: permalinkOf(raw) };
|
|
1082
|
+
}).filter((page) => !(environment === 'production' && DEV_ONLY_URL_RE.test(page.url || '')));
|
|
1083
|
+
|
|
1084
|
+
for (const page of frameworkPages) {
|
|
1085
|
+
// Registered UNCONDITIONALLY, gated at render time: suppression is a live
|
|
1086
|
+
// answer now, and a template skipped at config time could only come back
|
|
1087
|
+
// through a config reset.
|
|
1088
|
+
eleventyConfig.addTemplate(page.virtual, page.raw, renderGate(() => !decisions.suppresses(page.url), resolvePermalink));
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// ---- Social shortlinks (#429): a redirect page per entry of the socials
|
|
1092
|
+
// block, on the same lane and the same gate as the default pages above — the
|
|
1093
|
+
// permalink is a config-time fact (`/<platform>`), so a consumer page at one
|
|
1094
|
+
// of them takes just that URL over.
|
|
1095
|
+
// ---- Target shortlinks (#561): the same lane again, for the download
|
|
1096
|
+
// platform/artifact URLs (/download/mac, /download/linux/snap) and the
|
|
1097
|
+
// extension store URLs (/extension/chrome) legacy UJM shipped as hand-made
|
|
1098
|
+
// default pages. The declaration is the SAME map /download and /extension
|
|
1099
|
+
// already render from.
|
|
1100
|
+
const shortlinkPages = [
|
|
1101
|
+
...socialPages(readSocials(config.socials)),
|
|
1102
|
+
...targetShortlinkPages(readTargetShortlinks(site)),
|
|
1103
|
+
];
|
|
1104
|
+
for (const page of shortlinkPages) {
|
|
1105
|
+
eleventyConfig.addTemplate(page.virtual, page.raw, renderGate(() => !decisions.suppresses(page.url), resolvePermalink));
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// ---- Dynamic pages (#207): the listing and category pages of every
|
|
1109
|
+
// collection the brand declared, generated as virtual templates on the SAME
|
|
1110
|
+
// lane as the default pages above. Their permalink is only known per RENDER
|
|
1111
|
+
// (the listing's page number, the category's slug), so the gate asks the
|
|
1112
|
+
// live decisions about that page's own URL — a consumer page at any one of
|
|
1113
|
+
// them takes just that URL over.
|
|
1114
|
+
const dynamicPages = dynamicCollections.flatMap(collectionPages);
|
|
1115
|
+
for (const page of dynamicPages) {
|
|
1116
|
+
eleventyConfig.addTemplate(page.virtual, page.raw, {
|
|
1117
|
+
eleventyComputed: {
|
|
1118
|
+
permalink: (data) => {
|
|
1119
|
+
const url = page.urlOf(data);
|
|
1120
|
+
return url && !decisions.suppresses(url) ? `${url}.html` : false;
|
|
1121
|
+
},
|
|
1122
|
+
},
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// The collision picture: the default pages plus every generated page whose
|
|
1127
|
+
// URL is a config-time fact (a listing's own page 1). A category page's URL
|
|
1128
|
+
// is a CONTENT fact — it exists because a document names that term — so it
|
|
1129
|
+
// can only be answered by the render-time gate above.
|
|
1130
|
+
decisions.framework([...frameworkPages, ...shortlinkPages, ...dynamicPages].filter((page) => page.url));
|
|
1131
|
+
|
|
1132
|
+
// ---- Sample content (development only): a content-less brand still gets
|
|
1133
|
+
// living pages locally. Injected as virtual templates under the matching
|
|
1134
|
+
// collection segment so they ride the exact same lane as real content
|
|
1135
|
+
// (tags, permalinks, taxonomy). Dates ROLL — the corpus rhythm re-anchors
|
|
1136
|
+
// to the build day (or the OMEGA_SAMPLE_ANCHOR / sampleAnchor pin), spec
|
|
1137
|
+
// §8. The FIRST consumer file in a collection — or a production build —
|
|
1138
|
+
// removes that collection's samples entirely, and in dev that happens on the
|
|
1139
|
+
// very next render: the gate is the live own-content answer.
|
|
1140
|
+
if (environment !== 'production') {
|
|
1141
|
+
const sampleAnchorMs = resolveAnchor(options.sampleAnchor);
|
|
1142
|
+
for (const set of SAMPLE_SETS) {
|
|
1143
|
+
for (const { name, content } of generateSampleSet(defaultsDir, set, sampleAnchorMs)) {
|
|
1144
|
+
eleventyConfig.addTemplate(
|
|
1145
|
+
`omega-defaults/${set.collectionDir}/${name}`,
|
|
1146
|
+
content,
|
|
1147
|
+
renderGate(() => !decisions.hasOwn(set.collectionDir), resolvePermalink),
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// ---- Globals. ONE instant per build, so every stamp below names the same
|
|
1154
|
+
// moment instead of drifting a millisecond apart.
|
|
1155
|
+
const buildTime = new Date();
|
|
1156
|
+
// site.time: Jekyll's build timestamp, kept as a real build fact (#613) —
|
|
1157
|
+
// `article:modified_time` in the head and the BlogPosting `dateModified` in
|
|
1158
|
+
// the foot both read it, and with nothing setting it both shipped EMPTY on
|
|
1159
|
+
// every post. `site.omega.date.iso` is the same instant by construction.
|
|
1160
|
+
site.time = buildTime.toISOString();
|
|
1161
|
+
// site.omega carries UJM-runtime site values the core includes read
|
|
1162
|
+
// (cache_breaker in the @omega.js/client Configuration, date.year in the
|
|
1163
|
+
// copyright meta, date.iso as the sitemap/feed build stamp,
|
|
1164
|
+
// placeholder.src in lazy-loaded imgs).
|
|
1165
|
+
site.omega = {
|
|
1166
|
+
// The build stamp the runtime lazy-loader appends (cb=) — same value as
|
|
1167
|
+
// omega_cachebreak and the omega-cachebreak-img transform. Was 0 (inert)
|
|
1168
|
+
// until the central cache-breaker landed.
|
|
1169
|
+
cache_breaker: CACHE_TIMESTAMP,
|
|
1170
|
+
// The WEBSITE TARGET's own package version, read by the caller off the target
|
|
1171
|
+
// root's package.json (the engine only ever sees the src dir). It rides the
|
|
1172
|
+
// Configuration block as `version`, which is the release tag every error
|
|
1173
|
+
// report carries — `brand.id@version` (#380). null (a caller that hands
|
|
1174
|
+
// none) leaves @omega.js/client on its build-stamp fallback.
|
|
1175
|
+
version: options.version || null,
|
|
1176
|
+
date: { year: buildTime.getFullYear(), iso: site.time },
|
|
1177
|
+
placeholder: { src: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' },
|
|
1178
|
+
...(config.omega || {}),
|
|
1179
|
+
};
|
|
1180
|
+
// site.characters: the literal-character set templates interpolate rather
|
|
1181
|
+
// than type — head.html's copyright meta reads characters.copyright, and
|
|
1182
|
+
// with nothing defining the set every page fleet-wide shipped a leading
|
|
1183
|
+
// blank. The legacy _config_default.yml set carried over verbatim (#286);
|
|
1184
|
+
// a brand key wins, like site.omega's.
|
|
1185
|
+
site.characters = {
|
|
1186
|
+
asterisk: '*',
|
|
1187
|
+
'brace-left': '{',
|
|
1188
|
+
'brace-right': '}',
|
|
1189
|
+
'brace-both': '{}',
|
|
1190
|
+
'bracket-left': '[',
|
|
1191
|
+
'bracket-right': ']',
|
|
1192
|
+
'bracket-both': '[]',
|
|
1193
|
+
copyright: '©',
|
|
1194
|
+
underscore: '_',
|
|
1195
|
+
...(config.characters || {}),
|
|
1196
|
+
};
|
|
1197
|
+
eleventyConfig.addGlobalData('site', site);
|
|
1198
|
+
// og:locale wants Open Graph's language_TERRITORY form (en → en_US), and the
|
|
1199
|
+
// code → locale map is the devkit language SSOT — the head include cannot
|
|
1200
|
+
// derive it in Liquid, so it arrives as a computed global.
|
|
1201
|
+
eleventyConfig.addGlobalData('ogLocale', ogLocale(config.translation?.default || 'en'));
|
|
1202
|
+
// `dev` rides the jekyll global into the Configuration chrome (N7): `omega
|
|
1203
|
+
// dev` passes { ports } with the resolved map; production builds pass
|
|
1204
|
+
// nothing → null, and @omega.js/client falls back to the classic ports.
|
|
1205
|
+
//
|
|
1206
|
+
// A FUNCTION `dev` is a LIVE map, re-read on every render — the sibling
|
|
1207
|
+
// backend's ports file is pid-stamped and deleted on shutdown, so the map a
|
|
1208
|
+
// boot-time read produced is wrong the moment the emulator boots late or
|
|
1209
|
+
// restarts on bumped numbers, and the browser has no other channel
|
|
1210
|
+
// ([#300](https://github.com/Omega-JS-Stack/omega/issues/300)). Global data
|
|
1211
|
+
// registered as a function is evaluated per build, so every re-render bakes
|
|
1212
|
+
// what is running RIGHT NOW.
|
|
1213
|
+
eleventyConfig.addGlobalData('jekyll', () => ({
|
|
1214
|
+
environment,
|
|
1215
|
+
dev: (typeof options.dev === 'function' ? options.dev() : options.dev) || null,
|
|
1216
|
+
}));
|
|
1217
|
+
// The manifest is a FUNCTION for the same reason (#765): an object handed to
|
|
1218
|
+
// addGlobalData is snapshotted into the data cascade at registration, so a
|
|
1219
|
+
// dev rebuild that folds a new font-preload list into the live manifest was
|
|
1220
|
+
// invisible to every later render — measured: the object form still served
|
|
1221
|
+
// the boot list after a rebuild AND after a template edit, while the
|
|
1222
|
+
// callback form served the new one. The dev lane mutates that object in
|
|
1223
|
+
// place, so the callback re-reads what the last asset build produced.
|
|
1224
|
+
const emptyManifest = { js: { pages: {}, layouts: {} }, css: { pages: {}, layouts: {} } };
|
|
1225
|
+
eleventyConfig.addGlobalData('assetManifest', () => options.assetManifest || emptyManifest);
|
|
1226
|
+
|
|
1227
|
+
// ---- Icon inlining (#619, dev AND prod): every empty `<i>` whose classes
|
|
1228
|
+
// name an icon gets that icon's SVG inlined, so static chrome costs zero
|
|
1229
|
+
// runtime fetches and never flashes. What the pass emits is exactly what the
|
|
1230
|
+
// runtime watcher would have produced (runtime/icons.js), and the stamp it
|
|
1231
|
+
// leaves is what tells the watcher to leave the element alone.
|
|
1232
|
+
const loadIcon = createIconLoader({
|
|
1233
|
+
svgsDirs: [path.join(coreDir, 'icons'), ...fa.svgsDirs],
|
|
1234
|
+
aliasFile: fa.aliasFile,
|
|
1235
|
+
});
|
|
1236
|
+
// Warn ONCE per name per build: a missing icon in packaged chrome is a
|
|
1237
|
+
// framework bug a consumer cannot fix (#86), and the marker the pass leaves
|
|
1238
|
+
// on the element is what the dev audit reports in the browser.
|
|
1239
|
+
const warnedIcons = new Set();
|
|
1240
|
+
eleventyConfig.addTransform('omega-inline-icons', function (content) {
|
|
1241
|
+
if (this.page.outputPath && this.page.outputPath.endsWith('.html')) {
|
|
1242
|
+
return inlineIcons(content, loadIcon, (key) => {
|
|
1243
|
+
if (warnedIcons.has(key)) return;
|
|
1244
|
+
warnedIcons.add(key);
|
|
1245
|
+
logger.warn(`no SVG in the icon set for "${key}" — rendering an empty icon`);
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
return content;
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
// ---- Image cache-breaker (dev AND prod): every local <img>/<source> URL
|
|
1252
|
+
// in a .html output carries ?cb=<build stamp> so image edits show up on
|
|
1253
|
+
// rebuilds. Registered before the minifier (transforms run in order) and
|
|
1254
|
+
// shares the omega_cachebreak filter's stamp — one value per build process.
|
|
1255
|
+
const { cachebreakHtml } = require('./cachebreak-html.js');
|
|
1256
|
+
eleventyConfig.addTransform('omega-cachebreak-img', function (content) {
|
|
1257
|
+
if (this.page.outputPath && this.page.outputPath.endsWith('.html')) {
|
|
1258
|
+
return cachebreakHtml(content, CACHE_TIMESTAMP);
|
|
1259
|
+
}
|
|
1260
|
+
return content;
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
// ---- Base path (#355): a site mounted under a URL path (a GitHub Pages
|
|
1264
|
+
// project site) gets every root-relative URL it emits moved under that path,
|
|
1265
|
+
// plus the stamp the browser half reads (src/path-prefix.js). Registered ONLY
|
|
1266
|
+
// when a prefix is set — a build at the domain root runs no pass at all, so
|
|
1267
|
+
// its output is what it has always been, byte for byte. The dev server serves
|
|
1268
|
+
// at the root and never passes one.
|
|
1269
|
+
const pathPrefix = resolvePathPrefix(options.pathPrefix);
|
|
1270
|
+
if (pathPrefix) {
|
|
1271
|
+
eleventyConfig.addTransform('omega-path-prefix', function (content) {
|
|
1272
|
+
if (this.page.outputPath && this.page.outputPath.endsWith('.html')) {
|
|
1273
|
+
return prefixHtml(content, pathPrefix);
|
|
1274
|
+
}
|
|
1275
|
+
return content;
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// ---- Production HTML minification (the UJM minifyHtml successor). Only
|
|
1280
|
+
// .html outputs — the meta-files (sitemap.xml, feeds, robots.txt, …) ship
|
|
1281
|
+
// exactly as their templates render them.
|
|
1282
|
+
if (environment === 'production') {
|
|
1283
|
+
const { minifyHtml } = require('./minify-html.js');
|
|
1284
|
+
eleventyConfig.addTransform('omega-minify-html', function (content) {
|
|
1285
|
+
if (this.page.outputPath && this.page.outputPath.endsWith('.html')) {
|
|
1286
|
+
return minifyHtml(content);
|
|
1287
|
+
}
|
|
1288
|
+
return content;
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
return { site, config, layers, layoutMap, frontmatter, suppressed: decisions.suppressedUrls(), decisions, collectionsHolder };
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/**
|
|
1296
|
+
* One Eleventy collection item as a JEKYLL document: the flat shape layouts
|
|
1297
|
+
* iterate (`post.url`, `post.date`, `post.post.title`) plus Jekyll's
|
|
1298
|
+
* `post.content` — a reference to the item, never a copy.
|
|
1299
|
+
*
|
|
1300
|
+
* `content` is LAZY and non-enumerable (#598). Eleventy renders collection
|
|
1301
|
+
* content AFTER computed data resolves, so reading it here would throw its
|
|
1302
|
+
* premature-use error, and an enumerable getter would throw the same error in
|
|
1303
|
+
* every data walk that copies the doc (the `resolved` merge, the frontmatter
|
|
1304
|
+
* render pass). Read at RENDER time it is the post's real body — which is
|
|
1305
|
+
* what `{% omega_readtime %}` counts: the blog hub's featured card printed
|
|
1306
|
+
* "1 min read" for a twelve-minute post while the post's own page printed 12.
|
|
1307
|
+
*
|
|
1308
|
+
* BOTH doc lanes flatten through here (#711): `paginator.posts` passes raw
|
|
1309
|
+
* Eleventy items, the `site.<name>` arrays pass the collection holder's docs
|
|
1310
|
+
* (collections.js `toDoc`, which forwards `templateContent` the same lazy
|
|
1311
|
+
* way). `id` is the holder doc's — an item has none — and sits before the data
|
|
1312
|
+
* spread so a document's explicit frontmatter id still wins.
|
|
1313
|
+
* @param {object} item - an Eleventy collection item, or a holder doc
|
|
1314
|
+
* @returns {object} the Jekyll doc shape
|
|
1315
|
+
*/
|
|
1316
|
+
function jekyllDoc(item) {
|
|
1317
|
+
const identity = item.id === undefined ? {} : { id: item.id };
|
|
1318
|
+
const doc = { ...identity, url: item.url, date: item.date, ...item.data };
|
|
1319
|
+
Object.defineProperty(doc, 'content', {
|
|
1320
|
+
get: () => item.templateContent,
|
|
1321
|
+
enumerable: false,
|
|
1322
|
+
configurable: true,
|
|
1323
|
+
});
|
|
1324
|
+
return doc;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* The permalink every template computes — the ONE Jekyll-convention rule, so a
|
|
1329
|
+
* gated virtual template (renderGate below) resolves its URL exactly like an
|
|
1330
|
+
* ungated one.
|
|
1331
|
+
* @param {object} data - the template's data cascade
|
|
1332
|
+
* @param {object[]} collections - the declared collections (#207): `{ dir: '_<name>', base }` — a document under `dir` publishes at `<base>/<slug>`
|
|
1333
|
+
* @returns {string|object|undefined} the permalink Eleventy writes to
|
|
1334
|
+
*/
|
|
1335
|
+
function jekyllPermalink(data, collections) {
|
|
1336
|
+
const inputPath = data.page.inputPath;
|
|
1337
|
+
let permalink = data.permalink;
|
|
1338
|
+
// Collection URLs mirror UJM's Jekyll defaults (permalink: "/<coll>/
|
|
1339
|
+
// :title", with Eleventy's fileSlug stripping the dated-filename part) —
|
|
1340
|
+
// but an EXPLICIT permalink in the doc's frontmatter wins, like Jekyll.
|
|
1341
|
+
// '' counts as absent: Eleventy's computed dependency pass probes with
|
|
1342
|
+
// an empty-string proxy, and that probe value persists into the data.
|
|
1343
|
+
if (permalink === undefined || permalink === '') {
|
|
1344
|
+
const collection = collections.find((entry) => inputPath.includes(`/${entry.dir}/`));
|
|
1345
|
+
if (collection) permalink = `${collection.base}/${data.page.fileSlug}`;
|
|
1346
|
+
}
|
|
1347
|
+
// Jekyll flat URLs (legacy UJM parity): `/about` writes `about.html`,
|
|
1348
|
+
// NOT `about/index.html` — site URLs carry no trailing slash. page.url
|
|
1349
|
+
// stays extensionless ('/about') via configureOmega's .html-stripping
|
|
1350
|
+
// urlTransform, exactly like Jekyll's page.url for extensionless permalinks.
|
|
1351
|
+
// A whitelist of real output extensions, NOT path.extname — dotted slugs
|
|
1352
|
+
// (`/updates/v1.0.0`) must still get their .html. Liquid-carrying
|
|
1353
|
+
// permalinks (pagination/taxonomy) can't be shape-tested as raw strings —
|
|
1354
|
+
// they spell their full shape explicitly (blog.md ends in `.html`) and
|
|
1355
|
+
// pass through untouched.
|
|
1356
|
+
const KNOWN_EXT = /\.(html|xml|txt|json|js|css|webmanifest|svg|ics|pdf)$/i;
|
|
1357
|
+
if (typeof permalink === 'string' && !/[{}]/.test(permalink)
|
|
1358
|
+
&& !KNOWN_EXT.test(permalink) && !permalink.endsWith('/')) {
|
|
1359
|
+
return `${permalink}.html`;
|
|
1360
|
+
}
|
|
1361
|
+
return permalink;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
/**
|
|
1365
|
+
* The render-time gate of a virtual template (#200 Lane B). The template is
|
|
1366
|
+
* registered unconditionally and decides per BUILD whether it SHIPS:
|
|
1367
|
+
* `permalink: false` writes no file and `eleventyExcludeFromCollections` keeps
|
|
1368
|
+
* it out of every collection, so nothing downstream can see it. Eleventy
|
|
1369
|
+
* computes both per build, so a decision that flips between watch rebuilds
|
|
1370
|
+
* lands on the very next render with no config reset.
|
|
1371
|
+
*
|
|
1372
|
+
* What a shut gate does NOT do is skip the render: Eleventy still renders a
|
|
1373
|
+
* `permalink: false` template and throws away the output. The cost is real —
|
|
1374
|
+
* a template error inside a suppressed default page is still a fatal build
|
|
1375
|
+
* error — and it is the price of the lane: a template skipped at config time
|
|
1376
|
+
* could only come back through a config reset.
|
|
1377
|
+
*
|
|
1378
|
+
* The gate is also invisible to PAGINATED templates: Eleventy reads
|
|
1379
|
+
* `eleventyExcludeFromCollections` off the raw frontmatter when it expands
|
|
1380
|
+
* pagination, before any computed value exists. Every paginated default
|
|
1381
|
+
* therefore spells the key in its own frontmatter (pinned by
|
|
1382
|
+
* test/collections-gate.test.js).
|
|
1383
|
+
* @param {function} isActive - () => boolean, asked at data time
|
|
1384
|
+
* @param {function} resolvePermalink - the build's shared permalink resolver (jekyllPermalink plus the production /test omission)
|
|
1385
|
+
* @returns {object} addTemplate data
|
|
1386
|
+
*/
|
|
1387
|
+
function renderGate(isActive, resolvePermalink) {
|
|
1388
|
+
return {
|
|
1389
|
+
eleventyComputed: {
|
|
1390
|
+
// Open gate: the same permalink the global computed would have produced
|
|
1391
|
+
// (a per-template `eleventyComputed.permalink` replaces that key).
|
|
1392
|
+
permalink: (data) => (isActive() ? resolvePermalink(data) : false),
|
|
1393
|
+
// Open gate: the page's OWN frontmatter answer stands VERBATIM — `true`,
|
|
1394
|
+
// the list-of-collections form, or undefined for the pages that never
|
|
1395
|
+
// set it. Narrowing it to a boolean here would quietly rewrite a
|
|
1396
|
+
// template's own answer into one this gate never asked about.
|
|
1397
|
+
eleventyExcludeFromCollections: (data) => (isActive() ? data.eleventyExcludeFromCollections : true),
|
|
1398
|
+
},
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
module.exports = { configureOmega };
|