diff --git a/astro.config.mjs b/astro.config.mjs index 0a63ee92..affb9cb4 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -146,7 +146,7 @@ export default defineConfig({ themes: ['github-dark-dimmed'], styleOverrides: { borderColor: 'transparent', - borderRadius: 'var(--border-radius)' + borderRadius: 'var(--radius)' }, defaultProps: { wrap: true diff --git a/cms/README.md b/cms/README.md index 6d5d94aa..8fd7646b 100644 --- a/cms/README.md +++ b/cms/README.md @@ -319,6 +319,117 @@ The parent selector (`blockquote`) retains Astro's scoped attribute, so styles o See `Blockquote.astro` for an example using Option B. +### Content Preview + +Content authors can preview draft pages before publishing. The preview system uses server-side rendering (SSR) to fetch draft content directly from Strapi at runtime, bypassing the build-time MDX pipeline entirely. + +#### How it works + +1. A content author makes changes in the Strapi admin panel +2. They click **Save / Draft** +3. They click **Open preview** (the button is disabled until the draft is saved or the content is published) +4. Strapi calls the `preview.handler` in `config/admin.ts`, which reads the document from the database and builds a preview URL based on the content type (e.g. `/page-preview?documentId=abc123`) +5. The browser opens `{CLIENT_URL}/page-preview?documentId=abc123` on the Astro dev/SSR server +6. The Astro preview route (`src/pages/page-preview.astro`, which has `prerender = false`) fetches the **draft** content from the Strapi API using the `documentId` +7. The page is rendered at runtime using the `DynamicZone` component, which maps Strapi block types to Astro components +8. The author can edit, save, and re-preview as many times as needed before publishing + +**Note:** Preview requires saving first because Strapi reads the document from its database to generate the preview URL. Unsaved changes only exist in the browser and are not available to the preview handler. + +This is intentionally separate from the published content flow. Published pages are statically generated from MDX files at build time and have no runtime dependency on Strapi. + +#### Setup + +**Strapi side** (`cms/.env`): + +```env +CLIENT_URL=http://localhost:1103 # Must match the Astro dev server URL +``` + +The preview handler is configured in `cms/config/admin.ts`. To add preview support for a new content type, add a case to the `getPreviewPathname` switch statement. + +**Astro side** (`.env` in project root): + +```env +STRAPI_URL=http://localhost:1337 +STRAPI_PREVIEW_TOKEN= +``` + +The token must have read access to the content types you want to preview (including draft status). You can generate one in the Strapi admin under Settings > API Tokens. + +#### Adding preview for a new content type + +1. Add a case in `cms/config/admin.ts` `getPreviewPathname()` that returns the preview route path +2. Create an SSR page in `src/pages/` (e.g. `my-type-preview.astro`) with `export const prerender = false` +3. In that page, use `fetchStrapi()` with `status=draft` to fetch the draft content and render it + +#### Adding a new block to the page dynamic zone + +When you add a new block component to the page content dynamic zone, you **must** also add it to the populate params in `src/pages/page-preview.astro`. In Strapi v5, using `on` (component-specific population) for a dynamic zone acts as a filter — only block types listed in `on` clauses are returned in the API response. Unlisted types are silently excluded. + +For blocks with only scalar fields (richtext, string, enum): + +```js +'populate[content][on][blocks.my-block][populate]': '*' +``` + +For blocks with nested components or relations: + +```js +'populate[content][on][blocks.my-block][populate][myRelation][populate]': '*' +``` + +If you forget this step, the block will not appear in the preview even though it exists in Strapi. + +#### Component architecture: presentational vs block components + +Every Strapi dynamic zone block type needs a corresponding **block component** in `src/components/blocks/` so that the `DynamicZone` component can render it during preview. Whether you also need a separate **presentational component** depends on the data shape. + +**When a single component is enough:** + +If the Strapi API data can be used directly with minimal transformation, one component can serve both preview and published content. For example, `Paragraph.astro` accepts a `content` string and handles the markdown-to-HTML conversion internally — no separate block adapter needed. + +**When you need two components:** + +If the Strapi API returns a different shape than what the presentational component expects (nested objects, markdown fields that need conversion, etc.), you need a block adapter to bridge the gap. For example: + +- `src/components/ambassadors/Ambassador.astro` — **Presentational component**. Accepts simple, flat props (`name`, `description` as HTML string, `photo` as URL string) and renders the UI. Used by both the published site and preview. Has no knowledge of where the data comes from. + +- `src/components/blocks/AmbassadorBlock.astro` — **Block adapter for preview**. Used only by `DynamicZone` during SSR preview. Receives raw Strapi API data (nested `photo` object, markdown `description`) and transforms it into the simple props the presentational component expects (extracts `photo.url`, converts markdown to HTML via `marked`). + +For published content, the MDX lifecycle hook in Strapi handles these transformations at publish time, so the presentational component is used directly in the generated MDX. The block adapters are only needed for preview. + +**Rule of thumb:** If you need to transform Strapi's API response before rendering (flatten nested objects, convert markdown to HTML, resolve relations), create a block adapter in `src/components/blocks/` that does the transformation and delegates to a presentational component. Otherwise, a single component in `src/components/blocks/` is fine. + +#### Styling rendered HTML from `set:html` + +Components that render Strapi richtext fields use `set:html` to inject HTML converted from markdown. Since this injected HTML doesn't receive Astro's scoped data attributes, child elements can inherit unwanted styles from page-level prose selectors (e.g. `[&_strong]:text-primary`). + +There are two approaches to control styling of `set:html` content: + +**Option A — Tailwind arbitrary variants on container elements:** + +```html +
+``` + +Consistent with the pattern used in `[...page].astro` and `Paragraph.astro`. Keeps everything in the template but can get verbose with many overrides. + +**Option B — Astro scoped ` +``` + +The parent selector (`blockquote`) retains Astro's scoped attribute, so styles only apply within that component — they won't leak to other parts of the page. `:global()` removes scoping from the child selector so it can reach the injected HTML. Cleaner when there are multiple overrides. + +See `Blockquote.astro` for an example using Option B. + ## Development Workflow 1. **Start the CMS**: diff --git a/cms/src/components/blocks/cta-button.json b/cms/src/components/blocks/cta-button.json new file mode 100644 index 00000000..11af55ec --- /dev/null +++ b/cms/src/components/blocks/cta-button.json @@ -0,0 +1,23 @@ +{ + "collectionName": "components_blocks_cta_buttons", + "info": { + "displayName": "CTA Button", + "icon": "cursor", + "description": "Call-to-action button with link. Special tokens: (home), (text only), + ) +} + +{ + /* token - plain text, no interactive styling */ + isNoLink && ( + + {text} + + ) +} diff --git a/src/components/logos/AnimatedSummitLogo.astro b/src/components/logos/AnimatedSummitLogo.astro index cb666256..2e5f47d2 100644 --- a/src/components/logos/AnimatedSummitLogo.astro +++ b/src/components/logos/AnimatedSummitLogo.astro @@ -4,8 +4,8 @@ const { class: className } = Astro.props @@ -88,7 +88,7 @@ const { class: className } = Astro.props d="M142.246 19.543c4.605 0 8.339-3.722 8.339-8.313 0-4.59-3.734-8.312-8.339-8.312-4.606 0-8.339 3.722-8.339 8.312 0 4.591 3.733 8.313 8.339 8.313Z" > - + - + diff --git a/src/components/pages/SummitFooter.astro b/src/components/pages/SummitFooter.astro index a95dedcf..d9badd1a 100644 --- a/src/components/pages/SummitFooter.astro +++ b/src/components/pages/SummitFooter.astro @@ -3,7 +3,7 @@ const currentYear = new Date().getFullYear() ---
-

+

© 2020–{currentYear}, Interledger Foundation. All rights reserved.

-
    +
    • Terms of service
    • Privacy policy
    • Contact us
    • diff --git a/src/content/ambassadors/caroline-sinders.json b/src/content/ambassadors/caroline-sinders.json new file mode 100644 index 00000000..85673354 --- /dev/null +++ b/src/content/ambassadors/caroline-sinders.json @@ -0,0 +1,11 @@ +{ + "name": "Caroline Sinders", + "slug": "caroline-sinders", + "description": "

      Caroline Sinders is an award-winning critical designer, researcher, and artist. They’re the founder of the human rights and design lab Convocation Research.

      ", + "descriptionPlainText": "Caroline Sinders is an award-winning critical designer, researcher, and artist. They’re the founder of the human rights and design lab Convocation Research.", + "photo": "/uploads/Caroline_Sinders_jpg_96ac18fe07.webp", + "photoAlt": "Caroline Sinders", + "linkedinUrl": "https://www.linkedin.com/in/carosine/", + "grantReportUrl": null, + "order": 0 +} diff --git a/src/content/ambassadors/erica-hargreave.json b/src/content/ambassadors/erica-hargreave.json new file mode 100644 index 00000000..3f2ed654 --- /dev/null +++ b/src/content/ambassadors/erica-hargreave.json @@ -0,0 +1,11 @@ +{ + "name": "Erica Hargreave", + "slug": "erica-hargreave", + "description": "

      Erica Hargreave is an international award-winning storyteller and educator interested in helping independent creatives and educators develop open educational resources

      ", + "descriptionPlainText": "Erica Hargreave is an international award-winning storyteller and educator interested in helping independent creatives and educators develop open educational resources", + "photo": "/uploads/erica_jpg_01cd056540.webp", + "photoAlt": "Erica Hargreave", + "linkedinUrl": "https://www.linkedin.com/in/ericahargreave/", + "grantReportUrl": "https://community.interledger.org/ericahargreave/making-connections-helping-others-to-catalyze-potential-opportunities-explore-new-pathways-final-ambassadorship-report-3j4c", + "order": -1 +} diff --git a/src/content/ambassadors/jeremiah-lee.json b/src/content/ambassadors/jeremiah-lee.json new file mode 100644 index 00000000..f614bcbf --- /dev/null +++ b/src/content/ambassadors/jeremiah-lee.json @@ -0,0 +1,11 @@ +{ + "name": "Jeremiah Lee", + "slug": "jeremiah-lee", + "description": "

      Jeremiah Lee is a software engineer who has worked with social network APIs since the original Facebook Platform and advocates for federated social networks.

      ", + "descriptionPlainText": "Jeremiah Lee is a software engineer who has worked with social network APIs since the original Facebook Platform and advocates for federated social networks.", + "photo": "/uploads/jerimiah_jpg_b159d465e9.webp", + "photoAlt": "Jeremiah Lee", + "linkedinUrl": "https://www.linkedin.com/in/jeremiah-x-lee/", + "grantReportUrl": "https://community.interledger.org/jeremiahlee/web-monetization-on-the-social-web-ilf-grant-final-report-4i4k", + "order": 0 +} \ No newline at end of file diff --git a/src/content/ambassadors/kokayi-issa.json b/src/content/ambassadors/kokayi-issa.json new file mode 100644 index 00000000..2ff93726 --- /dev/null +++ b/src/content/ambassadors/kokayi-issa.json @@ -0,0 +1,11 @@ +{ + "name": "Kokayi Issa", + "slug": "kokayi-issa", + "description": "

      Kokayi Issa is a preeminent Improvisational Vocalist, Author, Producer, GRAMMY-nominated musician, and multi-disciplinary fine artist and 2023 Guggenheim Fellow for Music Composition.

      ", + "descriptionPlainText": "Kokayi Issa is a preeminent Improvisational Vocalist, Author, Producer, GRAMMY-nominated musician, and multi-disciplinary fine artist and 2023 Guggenheim Fellow for Music Composition.", + "photo": "/uploads/kokayi_jpg_6c1c421f22.webp", + "photoAlt": "Kokayi Issa", + "linkedinUrl": "https://www.linkedin.com/in/kokayi/", + "grantReportUrl": null, + "order": 0 +} \ No newline at end of file diff --git a/src/content/foundation-pages/test-page.mdx b/src/content/foundation-pages/test-page.mdx new file mode 100644 index 00000000..418c14e9 --- /dev/null +++ b/src/content/foundation-pages/test-page.mdx @@ -0,0 +1,10 @@ +--- +slug: "test-page" +title: "test page" +--- + +import CtaButton from "../../components/buttons/CtaButton.astro" + +For creators, publishers, and community-run platforms, building a sustainable future on the web has become increasingly difficult. Advertising revenues continue to decline, audiences are experiencing subscription fatigue, and smaller publishers are often left choosing between paywalls, platform dependence, or giving content away for. + + diff --git a/src/layouts/LanderLayout.astro b/src/layouts/LanderLayout.astro index c879bf65..90cfcb59 100644 --- a/src/layouts/LanderLayout.astro +++ b/src/layouts/LanderLayout.astro @@ -42,7 +42,8 @@ const { title, description, gradient = 'option1' } = Astro.props >
      diff --git a/src/pages/[...page].astro b/src/pages/[...page].astro index 0a1e8665..23a8b4f6 100644 --- a/src/pages/[...page].astro +++ b/src/pages/[...page].astro @@ -11,6 +11,7 @@ import Ambassador from '../components/ambassadors/Ambassador.astro' import AmbassadorGrid from '../components/ambassadors/AmbassadorGrid.astro' import Blockquote from '../components/blockquote/Blockquote.astro' import CalloutText from '../components/callout/CalloutText.astro' +import CtaButton from '../components/buttons/CtaButton.astro' export async function getStaticPaths() { return getPagePaths('foundation-pages', { excludeSlug: 'home' }) @@ -34,12 +35,13 @@ const title = mdxData?.title || slug const description = mdxData?.description || null const mdxHeroTitle = mdxData?.heroTitle || mdxData?.title || '' const mdxHeroDescription = mdxData?.heroDescription || '' +const pillar = mdxData?.pillar || undefined const isNotFound = !MdxContent if (isNotFound) Astro.response.status = 404 --- -
      +
      { isNotFound ? (
      @@ -73,13 +75,14 @@ if (isNotFound) Astro.response.status = 404 )}
      -
      +
      diff --git a/src/pages/blog/es.astro b/src/pages/blog/es.astro index 5de3d998..29e5001c 100644 --- a/src/pages/blog/es.astro +++ b/src/pages/blog/es.astro @@ -17,7 +17,8 @@ const esEntries = await getCollection('foundation-blog', ({ data }) => {
        diff --git a/src/pages/developers/blog/es.astro b/src/pages/developers/blog/es.astro index 346bd369..8bd29432 100644 --- a/src/pages/developers/blog/es.astro +++ b/src/pages/developers/blog/es.astro @@ -17,7 +17,8 @@ const esEntries = await getCollection('developers-blog', ({ data }) => {
          diff --git a/src/pages/hackathon-2023.astro b/src/pages/hackathon-2023.astro index 55a84028..bc4db0f0 100644 --- a/src/pages/hackathon-2023.astro +++ b/src/pages/hackathon-2023.astro @@ -28,7 +28,7 @@ import LanderLayout from '../layouts/LanderLayout.astro' id="mobileNav" aria-hidden="true" aria-labelledby="toggleNav" - class="h-full max-h-screen overflow-y-auto p-space-xs text-step--1 transition-transform duration-300 max-[849px]:fixed max-[849px]:top-0 max-[849px]:z-[1] max-[849px]:h-full max-[849px]:-translate-x-full max-[849px]:bg-black/75 min-[850px]:sticky min-[850px]:top-0 min-[850px]:self-start min-[850px]:bg-[hsla(162,86%,12%,1)] data-[visible=true]:max-[849px]:translate-x-0 [&_ul]:list-none [&_li]:mb-space-3xs [&_ul>li>ul]:mt-space-3xs [&_a]:no-underline [&_nav>ul]:ps-0" + class="h-full max-h-screen overflow-y-auto p-space-xs text-step--1 transition-transform duration-300 max-[849px]:fixed max-[849px]:top-0 max-[849px]:z-[1] max-[849px]:h-full max-[849px]:-translate-x-full max-[849px]:bg-black/75 min-[850px]:sticky min-[850px]:top-0 min-[850px]:self-start min-[850px]:bg-[hsla(162,86%,12%,1)] data-[visible=true]:max-[849px]:translate-x-0 [&_ul]:list-none [&_li]:mb-space-3xs [&_ul>li>ul]:mt-space-3xs [&_nav>ul]:ps-0" data-visible="false" >