From 60f35d65a6ef99bb13fafbbc312c332213ab5a70 Mon Sep 17 00:00:00 2001 From: Sarah Rainsberger Date: Mon, 6 Mar 2023 19:41:25 -0400 Subject: [PATCH] [reorg] new Recipes section; Basics streamlined (#2550) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * initial sidebar reorg * tutorials key * new (hidden) recipes page * move ui frameworks back to guides * add astro syntax hidden page * update recipes page * create initial nanostores recipe page from sharing state * Update pages for content collections * falso -> false * new captcha recipe pulled from endpoints * add yaml support recipe * move some component content out * slimmed down components page * some simplifying/editing edits to routing * update links to astro-syntax * attempt CI fail fixes * nav.ts indentation? * update CLI page and put in Learn menu * more edits to CLI page intro for friendliness * update i18n nav.ts for sidebar updates * final nav.ts cleanup * more user-friendly CLI intro * even friendlier CLI! * hack so CLI is in Learn category for sidebar highlighting * some Components page tidying - slots! * minor tidying of Astro syntax as a standalone page * Highlight recipes when on a page in recipes folder * Recipe components * i18n(ja): Update nav.ts (#2693) * move Routing and MD/MDX pages into guides * rename new page Astro Template Syntax * recipes folder * Move fonts back to guides * all recipes -> more recipes * remove back button on recipe index * Fix nav key type issue * Move main recipes file to avoid `index.mdx` * i18n(zh-cn): Update nav.ts based on reorg-incl-recipes (#2687) * i18n(zh-cn): Update nav.ts * i18n(zh-cn): Update nav.ts * Update nav.ts --------- Co-authored-by: Sarah Rainsberger * update of nav (#2725) * kill nanostores, replace with orig sharing state * add commented out original content back in to CLI * Make logos optional in `` * Add optional `description` support to `` * Configure content collection schema for recipes (no tags yet) * Add `type: recipe` to recipes & write missing descriptions * Very basic `` — needs tag, i18n support & styling * Replace `` with `` * Fix es nav type error * sharing state -> share state between islands * Tweak card grid to work better for recipe cards * Style description text in `` * Remove unneeded styles in `` * Fix margins on `` * Fix `` links & remove tags * Add `` * move CLI Reference page back to reference, remove Learn cateogry * add links back to recipes from original pages * nit edits in integrations-guide * links no longer notes; use emoji * related recipes bold text instead of heading; might delete * updated links to recipes - one line * fix broken link, move recipe to proper page! * Curated -> Official * [i18nIgnore] Add new subpage handling logic (#2758) * [i18nIgnore] Add new subpage handling logic * Revert "[i18nIgnore] Add new subpage handling logic" This reverts commit 5fce612aef206855e497e8e0b8fac198b6a03264. * Refactor subpage highlighting logic * Add missing frontmatter fields from translations * Avoid using `import.meta.url` --------- Co-authored-by: Chris Swithinbank * handle forms recipe + skeleton * build forms with api routes - 3 steps, examples for 5 frameworks * captcha we -> you and formatting * incorporate changes from PR2404 into Astro Syntax * incorporated changes from 2404 into Astro Components * update code example to show an Astro component vs UI framework * prereqs * yaml -> vite or rollup * Sarah edits to HTML forms - check carefully for still needs! * tiny Sarah edits * Dan's improved instructions Co-authored-by: Dan Jutan * no typos under Yan's watch! Co-authored-by: Yan Thomas <61414485+Yan-Thomas@users.noreply.github.com> * removing inelegance! Co-authored-by: Dan Jutan * Update src/content/docs/en/recipes/build-forms.mdx Co-authored-by: Yan Thomas <61414485+Yan-Thomas@users.noreply.github.com> * Delete clipboard-check.svg * Remove duplicate nav entry * tweak order of guides * nod to script and style tags in components, removed dup from script page * remove forms recipes * Update PT-BR `nav.ts` file * nav tweaks esp routing * bye bye, "sits the template" * Use translations correct in `` * Add `lang="en"` to English fallback content in `` * Update PT-BR `rss.mdx` description * Also update the PT-BR `title` prop! --------- Co-authored-by: Chris Swithinbank Co-authored-by: Shinya Fujino Co-authored-by: Dan Jutan Co-authored-by: 李瑞丰 Co-authored-by: Paul Valladares <85648028+dreyfus92@users.noreply.github.com> Co-authored-by: Yan Thomas <61414485+Yan-Thomas@users.noreply.github.com> --- src/components/CMSGuidesNav.astro | 13 - src/components/LeftSidebar/LeftSidebar.astro | 2 +- .../LeftSidebar/SidebarContent.astro | 9 +- src/components/NavGrid/Card.astro | 4 +- src/components/NavGrid/CardsNav.astro | 16 +- src/components/NavGrid/Grid.astro | 6 +- src/components/RecipesNav.astro | 46 +++ src/content.ts | 3 +- src/content/config.ts | 22 +- .../en/core-concepts/astro-components.mdx | 282 ++---------------- .../docs/en/core-concepts/astro-syntax.mdx | 191 ++++++++++++ .../docs/en/core-concepts/endpoints.mdx | 62 +--- .../en/core-concepts/framework-components.mdx | 2 +- src/content/docs/en/core-concepts/layouts.mdx | 2 +- src/content/docs/en/core-concepts/routing.mdx | 32 +- .../docs/en/core-concepts/sharing-state.mdx | 4 +- .../docs/en/guides/client-side-scripts.mdx | 24 ++ src/content/docs/en/guides/data-fetching.mdx | 2 +- src/content/docs/en/guides/imports.mdx | 35 +-- .../docs/en/guides/integrations-guide.mdx | 6 +- .../guides/migrate-to-astro/from-jekyll.mdx | 2 +- src/content/docs/en/guides/rss.mdx | 5 +- .../docs/en/guides/server-side-rendering.mdx | 2 +- .../docs/en/guides/troubleshooting.mdx | 4 +- src/content/docs/en/guides/upgrade-to/v1.mdx | 2 +- src/content/docs/en/recipes.mdx | 18 ++ .../docs/en/recipes/add-yaml-support.mdx | 36 +++ src/content/docs/en/recipes/captcha.mdx | 72 +++++ .../docs/en/reference/cli-reference.mdx | 135 ++++++++- .../en/reference/directives-reference.mdx | 4 +- .../static-client-address-not-available.mdx | 2 +- src/content/docs/en/tutorial/2-pages/3.mdx | 2 +- src/content/docs/en/tutorial/2-pages/4.mdx | 2 +- src/content/docs/en/tutorial/2-pages/5.mdx | 2 +- .../docs/en/tutorial/3-components/4.mdx | 2 +- src/content/docs/en/tutorial/6-islands/2.mdx | 2 +- .../docs/es/core-concepts/sharing-state.mdx | 2 + src/content/docs/es/guides/rss.mdx | 1 + .../docs/fr/core-concepts/sharing-state.mdx | 2 + src/content/docs/fr/guides/rss.mdx | 1 + src/content/docs/ja/guides/rss.mdx | 1 + .../pt-br/core-concepts/sharing-state.mdx | 4 +- src/content/docs/pt-br/guides/rss.mdx | 5 +- src/content/docs/ru/guides/data-fetching.mdx | 2 +- .../zh-cn/core-concepts/sharing-state.mdx | 2 + src/content/docs/zh-cn/guides/rss.mdx | 1 + src/i18n/de/nav.ts | 4 +- src/i18n/en/nav.ts | 61 ++-- src/i18n/en/ui.ts | 2 + src/i18n/es/nav.ts | 59 ++-- src/i18n/fr/nav.ts | 4 +- src/i18n/ja/nav.ts | 47 +-- src/i18n/pl/nav.ts | 6 +- src/i18n/pt-br/nav.ts | 24 +- src/i18n/ru/nav.ts | 8 +- src/i18n/zh-cn/nav.ts | 64 ++-- src/i18n/zh-tw/nav.ts | 6 +- src/layouts/LayoutSwitcher.astro | 5 + src/layouts/RecipeLayout.astro | 27 ++ src/util.ts | 15 - src/util/getPageCategory.ts | 2 +- src/util/isSubPage.ts | 49 +++ 62 files changed, 905 insertions(+), 554 deletions(-) create mode 100644 src/components/RecipesNav.astro create mode 100644 src/content/docs/en/core-concepts/astro-syntax.mdx create mode 100644 src/content/docs/en/recipes.mdx create mode 100644 src/content/docs/en/recipes/add-yaml-support.mdx create mode 100644 src/content/docs/en/recipes/captcha.mdx create mode 100644 src/layouts/RecipeLayout.astro create mode 100644 src/util/isSubPage.ts diff --git a/src/components/CMSGuidesNav.astro b/src/components/CMSGuidesNav.astro index ad7367b218419..d9d9b6e19bbaa 100644 --- a/src/components/CMSGuidesNav.astro +++ b/src/components/CMSGuidesNav.astro @@ -41,17 +41,4 @@ const links = enPages .cms-nav > :global(*) { margin-top: -2rem; } - - .cms-nav > :global(* + *) { - margin-top: -3rem; - } - - .cms-nav :global(.scope) { - font-weight: normal; - color: var(--theme-text-lighter); - } - - h3 { - margin-bottom: 0; - } diff --git a/src/components/LeftSidebar/LeftSidebar.astro b/src/components/LeftSidebar/LeftSidebar.astro index c3e73a7c94935..edf6e18ec6b90 100644 --- a/src/components/LeftSidebar/LeftSidebar.astro +++ b/src/components/LeftSidebar/LeftSidebar.astro @@ -35,7 +35,7 @@ let activeTab: 'learn' | 'api' = 'learn'; // Certain pages are not in the sidebar nav, so we manually set the active tab based on other factors (e.g. Algolia page category). const isReference = ['Error Reference', 'Reference'].includes( - getPageCategory(new URL(currentPage, import.meta.url)) + getPageCategory({ pathname: currentPage }) ); if (isReference) { activeTab = 'api'; diff --git a/src/components/LeftSidebar/SidebarContent.astro b/src/components/LeftSidebar/SidebarContent.astro index 6afd711d034ab..22eb038d2334b 100644 --- a/src/components/LeftSidebar/SidebarContent.astro +++ b/src/components/LeftSidebar/SidebarContent.astro @@ -1,5 +1,6 @@ --- -import { getLanguageFromURL, removeSubpageSegment } from '../../util'; +import { getLanguageFromURL } from '~/util'; +import { isSubPage } from '~/util/isSubPage'; export interface Props { type: TabType; @@ -44,11 +45,7 @@ const lang = getLanguageFromURL(Astro.url.pathname); {isFallback && EN} diff --git a/src/components/NavGrid/Card.astro b/src/components/NavGrid/Card.astro index 5c08ae5b2c74c..8bb6fae92a1a6 100644 --- a/src/components/NavGrid/Card.astro +++ b/src/components/NavGrid/Card.astro @@ -4,7 +4,7 @@ import BrandLogo from '../BrandLogo.astro'; export interface Props { href: string; - logo: LogoKey; + logo?: LogoKey; current?: boolean; minimal?: boolean; [key: string]: any; @@ -14,7 +14,7 @@ const { href, logo, current, minimal, class: classes, ...attrs } = Astro.props a ---
  • - + {logo && }

    diff --git a/src/components/NavGrid/CardsNav.astro b/src/components/NavGrid/CardsNav.astro index 067f707faf4db..e8c30deaebc7e 100644 --- a/src/components/NavGrid/CardsNav.astro +++ b/src/components/NavGrid/CardsNav.astro @@ -8,10 +8,13 @@ export interface Props { minimal?: boolean; links: { title: string; + description?: string; href: string; - logo: LogoKey; + logo?: LogoKey; /** Map of tag IDs to translated tag display text, e.g. `{ static: 'Statisch' }`. */ tags?: Record; + /** The language of the content if it differs from the main page language. */ + lang?: string; }[]; class?: string; } @@ -25,14 +28,15 @@ const currentPage = new URL(Astro.request.url).pathname; { - links.map(({ href, logo, title, tags }) => ( + links.map(({ description, href, logo, title, tags, lang }) => ( + {description &&

    {description}

    } {tags && (
    {Object.values(tags).map((tag) => ( @@ -53,6 +57,12 @@ const currentPage = new URL(Astro.request.url).pathname; accent-color: var(--theme-accent-secondary); } + .description { + margin-top: 0.25rem; + color: var(--theme-text-lighter); + font-size: var(--theme-text-sm); + } + .tags { display: flex; gap: 0.5rem; diff --git a/src/components/NavGrid/Grid.astro b/src/components/NavGrid/Grid.astro index c5f3d025a6bbb..7aeba2e04ab1b 100644 --- a/src/components/NavGrid/Grid.astro +++ b/src/components/NavGrid/Grid.astro @@ -18,19 +18,19 @@ const { minimal } = Astro.props as Props; display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--column-min-width), 1fr)); grid-template-columns: repeat(auto-fill, minmax(min(var(--column-min-width), 100%), 1fr)); - gap: 1.5rem; + gap: 2rem; list-style: none; + align-items: start; } @media (min-width: 37.75em) { .fluid-grid { - gap: 1.5rem 2.5rem; + gap: 2rem 2.5rem; } } .fluid-grid--minimal { --column-min-width: 6rem; gap: 1.5rem 0.75rem; - align-items: start; } diff --git a/src/components/RecipesNav.astro b/src/components/RecipesNav.astro new file mode 100644 index 0000000000000..dbcfbde5c451b --- /dev/null +++ b/src/components/RecipesNav.astro @@ -0,0 +1,46 @@ +--- +import { recipePages } from '~/content'; +import { createIsLangEntry, isEnglishEntry } from '~/content/config'; +import { getLanguageFromURL, stripLangFromSlug } from '~/util'; +import CardsNav from './NavGrid/CardsNav.astro'; + +export interface Props { + minimal?: boolean; +} + +const lang = getLanguageFromURL(Astro.url.pathname); +const langRecipes = recipePages.filter(createIsLangEntry(lang)); +const englishRecipes = recipePages.filter(isEnglishEntry); + +/** An array of all recipes, using translations if available and falling back to English otherwise. */ +const recipes = englishRecipes.map((fallback) => { + const slug = stripLangFromSlug(fallback.slug); + const translation = langRecipes.find((doc) => stripLangFromSlug(doc.slug) === slug); + return translation || fallback; +}); +--- + +
    + { + const linkLang = slug.split('/').shift(); + return { + title: data.title, + description: data.description, + // Fallback entries will have a slug starting with 'en/', + // so we replace that to link to the correct language. + href: '/' + slug.replace('en/', `${lang}/`) + '/', + // Fallback content will be in English, unlike the page, + // so we set an explicit `lang="en"` for these entries. + lang: linkLang !== lang ? linkLang : undefined, + }; + })} + /> +
    + + diff --git a/src/content.ts b/src/content.ts index 3317815db1e74..37f1fd19a7ced 100644 --- a/src/content.ts +++ b/src/content.ts @@ -1,6 +1,7 @@ import { getCollection } from 'astro:content'; -import { isEnglishEntry, isTutorialEntry } from './content/config'; +import { isEnglishEntry, isRecipeEntry, isTutorialEntry } from './content/config'; export const allPages = await getCollection('docs'); export const tutorialPages = allPages.filter(isTutorialEntry); +export const recipePages = allPages.filter(isRecipeEntry); export const englishPages = allPages.filter(isEnglishEntry); diff --git a/src/content/config.ts b/src/content/config.ts index d31de1a389894..23d2b1eacd55e 100644 --- a/src/content/config.ts +++ b/src/content/config.ts @@ -45,6 +45,11 @@ export const tutorialSchema = baseSchema.extend({ unitTitle: z.string().optional(), }); +export const recipeSchema = baseSchema.extend({ + type: z.literal('recipe'), + description: z.string(), +}); + export type DeployEntry = CollectionEntry<'docs'> & { data: z.infer; }; @@ -65,6 +70,10 @@ export type TutorialEntry = CollectionEntry<'docs'> & { data: z.infer; }; +export type RecipeEntry = CollectionEntry<'docs'> & { + data: z.infer; +}; + export type IntegrationCategory = z.infer['category']; export function isCmsEntry(entry: CollectionEntry<'docs'>): entry is CmsEntry { @@ -83,10 +92,18 @@ export function isMigrationEntry(entry: CollectionEntry<'docs'>): entry is Migra return entry.data.type === 'migration'; } -export function isEnglishEntry(entry: CollectionEntry<'docs'>): boolean { - return entry.slug.startsWith('en/'); +export function isRecipeEntry(entry: CollectionEntry<'docs'>): entry is RecipeEntry { + return entry.data.type === 'recipe'; } +export function createIsLangEntry(lang: string) { + return function isLangEntry(entry: CollectionEntry<'docs'>): boolean { + return entry.slug.startsWith(lang + '/'); + }; +} + +export const isEnglishEntry = createIsLangEntry('en'); + const docs = defineCollection({ schema: z.union([ baseSchema, @@ -95,6 +112,7 @@ const docs = defineCollection({ migrationSchema, tutorialSchema, deploySchema, + recipeSchema, ]), }); diff --git a/src/content/docs/en/core-concepts/astro-components.mdx b/src/content/docs/en/core-concepts/astro-components.mdx index a53292a93a65f..151599aed27c0 100644 --- a/src/content/docs/en/core-concepts/astro-components.mdx +++ b/src/content/docs/en/core-concepts/astro-components.mdx @@ -4,20 +4,18 @@ description: An intro to the .astro component syntax. i18nReady: true --- -**Astro components** are the basic building blocks of any Astro project. They are HTML-only templating components with no client-side runtime. - -**If you know HTML, you already know enough to write your first Astro component.** - -Astro component syntax is a superset of HTML. The syntax was [designed to feel familiar to anyone with experience writing HTML or JSX](#differences-between-astro-and-jsx), and adds support for including components and JavaScript expressions. You can spot an Astro component by its file extension: `.astro`. +**Astro components** are the basic building blocks of any Astro project. They are HTML-only templating components with no client-side runtime. You can spot an Astro component by its file extension: `.astro`. Astro components are extremely flexible. Often, an Astro component will contain some **reusable UI on the page**, like a header or a profile card. At other times, an Astro component may contain a smaller snippet of HTML, like a collection of common `` tags that make SEO easy to work with. Astro components can even contain an entire page layout. -The most important thing to know about Astro components is that they **render to HTML during your build.** Even if you run JavaScript code inside of your components, it will all run ahead of time, stripped from the final page that you send to your users. The result is a faster site, with zero JavaScript footprint added by default. +The most important thing to know about Astro components is that they **don't render on the client**. They render to HTML either at build-time or on-demand using [server-side rendering (SSR)](/en/guides/server-side-rendering/). You can include JavaScript code inside of your component frontmatter, and all of it will be stripped from the final page sent to your users' browsers. The result is a faster site, with zero JavaScript footprint added by default. + +When your Astro component does need client-side interactivity, you can add [standard HTML ` -``` -::: - -### Dynamic HTML - -Local variables can be used in JSX-like functions to produce dynamically-generated HTML elements: - -```astro title="src/components/DynamicHtml.astro" "{item}" ---- -const items = ["Dog", "Cat", "Platypus"]; ---- -
      - {items.map((item) => ( -
    • {item}
    • - ))} -
    -``` - -Astro can conditionally display HTML using JSX logical operators and ternary expressions. - -```astro title="src/components/ConditionalHtml.astro" "visible" ---- -const visible = true; ---- -{visible &&

    Show me!

    } - -{visible ?

    Show me!

    :

    Else show me!

    } -``` - -### Dynamic Tags - -You can also use dynamic tags by setting a variable to an HTML tag name or a component import: - -```astro title="src/components/DynamicTags.astro" /Element|(?Hello! - -``` - -When using dynamic tags: - -- **Variable names must be capitalized.** For example, use `Element`, not `element`. Otherwise, Astro will try to render your variable name as a literal HTML tag. - -- **Hydration directives are not supported.** When using [`client:*` hydration directives](/en/core-concepts/framework-components/#hydrating-interactive-components), Astro needs to know which components to bundle for production, and the dynamic tag pattern prevents this from working. - -### Fragments & Multiple Elements - -An Astro component template can render multiple elements with no need to wrap everything in a single `
    ` or `<>`, unlike JavaScript or JSX. - -```astro title="src/components/RootElements.astro" ---- -// Template with multiple elements ---- -

    No need to wrap elements in a single containing element.

    -

    Astro supports multiple root elements in a template.

    -``` - -However, when using an expression to dynamically create multiple elements, you should wrap these elements inside a **fragment** as you would in JavaScript or JSX. Astro supports using either ` ` or the shorthand `<> `. - -```astro title="src/components/FragmentWrapper.astro" "<>" "" ---- -const items = ["Dog", "Cat", "Platypus"]; ---- -
      - {items.map((item) => ( - <> -
    • Red {item}
    • -
    • Blue {item}
    • -
    • Green {item}
    • - - ))} -
    -``` - -Fragments can also be useful to avoid wrapper elements when adding [`set:*` directives](/en/reference/directives-reference/#sethtml), as in the following example: - -```astro title="src/components/SetHtml.astro" "Fragment" ---- -const htmlString = '

    Raw HTML content

    '; ---- - -``` - -### Differences between Astro and JSX - -Astro component syntax is a superset of HTML. It was designed to feel familiar to anyone with HTML or JSX experience, but there are a couple of key differences between `.astro` files and JSX. - -#### Attributes - -In Astro, you use the standard `kebab-case` format for all HTML attributes instead of the `camelCase` used in JSX. This even works for `class`, which is not supported by React. - -```jsx del={1} ins={2} title="example.astro" -
    -
    -``` - -#### Comments - -In Astro, you can use standard HTML comments or JavaScript-style comments. - -```astro title="example.astro" ---- ---- - -{/* JS comment syntax is also valid */} -``` - -:::caution -HTML-style comments will be included in browser DOM, while JS ones will be skipped. To leave TODO messages or other development-only explanations, you may wish to use JavaScript-style comments instead. -::: ## Component Props @@ -313,7 +138,6 @@ const name = "Astro"

    I hope you have a wonderful day!

    ``` - You can also define your props with TypeScript with a `Props` type interface. Astro will automatically pick up the `Props` interface in your frontmatter and give type warnings/errors. These props can also be given default values when destructured from `Astro.props`. ```astro ins={3-6} @@ -378,7 +202,7 @@ import Wrapper from '../components/Wrapper.astro'; ``` -This pattern is the basis of an Astro layout component: an entire page of HTML content can be “wrapped” with `` tags and sent to the Layout component to render inside of common page elements. +This pattern is the basis of an [Astro layout component](/en/core-concepts/layouts/): an entire page of HTML content can be “wrapped” with `` tags and sent to the component to render inside of common page elements defined there. @@ -386,6 +210,8 @@ This pattern is the basis of an Astro layout component: an entire page of HTML c An Astro component can also have named slots. This allows you to pass only HTML elements with the corresponding slot name into a slot's location. +Slots are named using the `name` attribute: + ```astro // --- // src/components/Wrapper.astro @@ -406,6 +232,9 @@ const { title } = Astro.props
    ``` +To inject HTML content into a particular slot, use the `slot` attribute on any child element to specify the name of the slot. All other child elements of the component will be injected into the default (unnamed) ``. + + ```astro /slot=".*?"/ --- // src/pages/fred.astro @@ -450,61 +279,10 @@ const { title } = Astro.props
    ``` -## CSS Styles - -CSS ` - -

    Hello, world!

    -``` - -:::caution -The styles defined here apply only to content written directly in the component's own component template. Children, and any imported components will **not** be styled by default. -::: - -📚 See our [Styling Guide](/en/guides/styling/) for more information on applying styles. - -## Client-Side Scripts - -Astro components support adding client-side interactivity using standard HTML ` -``` - -By default, Astro processes and bundles ` +``` +::: + +### Dynamic HTML + +Local variables can be used in JSX-like functions to produce dynamically-generated HTML elements: + +```astro title="src/components/DynamicHtml.astro" "{item}" +--- +const items = ["Dog", "Cat", "Platypus"]; +--- +
      + {items.map((item) => ( +
    • {item}
    • + ))} +
    +``` + +Astro can conditionally display HTML using JSX logical operators and ternary expressions. + +```astro title="src/components/ConditionalHtml.astro" "visible" +--- +const visible = true; +--- +{visible &&

    Show me!

    } + +{visible ?

    Show me!

    :

    Else show me!

    } +``` + +### Dynamic Tags + +You can also use dynamic tags by setting a variable to an HTML tag name or a component import: + +```astro title="src/components/DynamicTags.astro" /Element|(?Hello! + +``` + +When using dynamic tags: + +- **Variable names must be capitalized.** For example, use `Element`, not `element`. Otherwise, Astro will try to render your variable name as a literal HTML tag. + +- **Hydration directives are not supported.** When using [`client:*` hydration directives](/en/core-concepts/framework-components/#hydrating-interactive-components), Astro needs to know which components to bundle for production, and the dynamic tag pattern prevents this from working. + +### Fragments & Multiple Elements + +An Astro component template can render multiple elements with no need to wrap everything in a single `
    ` or `<>`, unlike JavaScript or JSX. + +```astro title="src/components/RootElements.astro" +--- +// Template with multiple elements +--- +

    No need to wrap elements in a single containing element.

    +

    Astro supports multiple root elements in a template.

    +``` + +However, when using an expression to dynamically create multiple elements, you should wrap these elements inside a **fragment** as you would in JavaScript or JSX. Astro supports using either ` ` or the shorthand `<> `. + +```astro title="src/components/FragmentWrapper.astro" "<>" "" +--- +const items = ["Dog", "Cat", "Platypus"]; +--- +
      + {items.map((item) => ( + <> +
    • Red {item}
    • +
    • Blue {item}
    • +
    • Green {item}
    • + + ))} +
    +``` + +Fragments can also be useful to avoid wrapper elements when adding [`set:*` directives](/en/reference/directives-reference/#sethtml), as in the following example: + +```astro title="src/components/SetHtml.astro" "Fragment" +--- +const htmlString = '

    Raw HTML content

    '; +--- + +``` + +### Differences between Astro and JSX + +Astro component syntax is a superset of HTML. It was designed to feel familiar to anyone with HTML or JSX experience, but there are a couple of key differences between `.astro` files and JSX. + +#### Attributes + +In Astro, you use the standard `kebab-case` format for all HTML attributes instead of the `camelCase` used in JSX. This even works for `class`, which is not supported by React. + +```jsx del={1} ins={2} title="example.astro" +
    +
    +``` + +#### Comments + +In Astro, you can use standard HTML comments or JavaScript-style comments. + +```astro title="example.astro" +--- +--- + +{/* JS comment syntax is also valid */} +``` + +:::caution +HTML-style comments will be included in browser DOM, while JS ones will be skipped. To leave TODO messages or other development-only explanations, you may wish to use JavaScript-style comments instead. +::: + + diff --git a/src/content/docs/en/core-concepts/endpoints.mdx b/src/content/docs/en/core-concepts/endpoints.mdx index a184c183ae475..379b1339cd61e 100644 --- a/src/content/docs/en/core-concepts/endpoints.mdx +++ b/src/content/docs/en/core-concepts/endpoints.mdx @@ -210,64 +210,4 @@ export async function get({ params, redirect }) { } ``` -### Example: Verifying a captcha -Server endpoints can be used as REST API endpoints to run functions such as authentications, database access, and verifications without exposing sensitive data to the client. - -In the example below, an API route is used to verify Google reCAPTCHA v3 without exposing the secret to clients. - -On the server, we define a post method that accepts recaptcha data, then verifies it with reCAPTCHA's API. Here, we can safely define secret values or read environment variables. - -```js title="src/pages/recaptcha.js" -export async function post({ request }) { - const data = await request.json(); - - const recaptchaURL = 'https://www.google.com/recaptcha/api/siteverify'; - const requestBody = { - secret: "YOUR_SITE_SECRET_KEY", // This can be an environment variable - response: data.recaptcha // The token passed in from the client - }; - - const response = await fetch(recaptchaURL, { - method: "POST", - body: JSON.stringify(requestBody) - }); - - const responseData = await response.json(); - - return new Response(JSON.stringify(responseData), { status: 200 }); -} -``` - -Then, we access our endpoint using `fetch` from a client script: - -```astro title="src/pages/index.astro" - - - - - - - - - - - -``` +**Related recipe:** [Use server endpoints to verify a CAPTCHA](/en/recipes/captcha/) diff --git a/src/content/docs/en/core-concepts/framework-components.mdx b/src/content/docs/en/core-concepts/framework-components.mdx index bb348a0e9c896..c8dfdd8bbee60 100644 --- a/src/content/docs/en/core-concepts/framework-components.mdx +++ b/src/content/docs/en/core-concepts/framework-components.mdx @@ -224,7 +224,7 @@ If you try to hydrate an Astro component with a `client:` modifier, you will get [Astro components](/en/core-concepts/astro-components/) are HTML-only templating components with no client-side runtime. But, you can use a ` +``` + +By default, Astro processes and bundles ` + + + + + + + + + ``` diff --git a/src/content/docs/en/reference/cli-reference.mdx b/src/content/docs/en/reference/cli-reference.mdx index e6045c112525d..78b828ac446f1 100644 --- a/src/content/docs/en/reference/cli-reference.mdx +++ b/src/content/docs/en/reference/cli-reference.mdx @@ -1,5 +1,5 @@ --- -title: CLI Reference +title: CLI Commands i18nReady: true --- import Since from '~/components/Since.astro'; @@ -7,6 +7,137 @@ import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro' +You can use the Command-Line Interface (CLI) provided by Astro to develop, build, and preview your project from a terminal window. + +### `astro` commands + +Use the CLI by running one of the **commands** documented on this page with your preferred package manager, optionally followed by any **flags**. Flags customize the behavior of a command. + +One of the commands you'll use most often is `astro dev`. This command starts the development server and gives you a live, updating preview of your site in a browser as you work: + + + + ```shell + # start the development server + npx astro dev + ``` + + + ```shell + # start the development server + pnpm astro dev + ``` + + + ```shell + # start the development server + yarn astro dev + ``` + + + +You can type `astro --help` in your terminal to display a list of all available commands: + + + + ```shell + npx astro --help + ``` + + + ```shell + pnpm astro --help + ``` + + + ```shell + yarn astro --help + ``` + + + +The following message will display in your terminal: + +```bash +astro [command] [...flags] + +Commands + add Add an integration. + build Build your project and write it to disk. + check Check your project for errors. + dev Start the development server. + docs Open documentation in your web browser. + preview Preview your build locally. + sync Generate content collection types. + telemetry Configure telemetry settings. + +Global Flags + --config Specify your config file. + --root Specify your project root folder. + --site Specify your project site. +--base Specify your project base. + --verbose Enable verbose logging. + --silent Disable all logging. + --version Show the version number and exit. + --help Show this help message. +``` + +### `package.json` scripts + +You can also use scripts in `package.json` for shorter versions of these commands. Using a script allows you to use the same commands that you may be familiar with from other projects, such as `npm run build`. + +The following scripts for the most common `astro` commands (`astro dev`, `astro build`, and `astro preview`) are added for you automatically when you create a project using [the `create astro` wizard](/en/install/auto/#1-run-the-setup-wizard). + +When you follow the instructions to [install Astro manually](/en/install/manual/#2-install-astro), you are instructed to add these scripts yourself. You can also add more scripts to this list manually for any commands you use frequently. + +```json title="package.json" +{ + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview" + } +} +``` + +You will often use these `astro` commands, or the scripts that run them, without any flags. Add flags to the command when you want to customize the command's behavior. For example, you may wish to start the development server on a different port, or build your site including draft pages. + + + + ```shell + # run the dev server on port 8080 using the `start` script in `package.json` + npm run start -- --port 8080 + + # build your site including draft pages using the `build` script in `package.json` + npm run build -- --drafts + ``` + (The extra `--` before the `--port` flag is necessary for `npm` to pass your flags to the `astro` command.) + + + ```shell + # run the dev server on port 8080 using the `start` script in `package.json` + pnpm start --port 8080 + + # build your site including draft pages using the `build` script in `package.json` + pnpm build --drafts + ``` + + + ```shell + # run the dev server on port 8080 using the `start` script in `package.json` + yarn start --port 8080 + + # build your site including draft pages using the `build` script in `package.json` + yarn build --drafts + ``` + + + +{/* + +ORIGINAL CONTENT That We Can Always revert to if new stuff is too friendly + You can use the Command-Line Interface (CLI) provided by Astro to develop, build, and preview your project from a terminal window. Use the CLI by running one of the **commands** documented on this page, optionally followed by any **flags**. Flags customize the behavior of a command. For example, to start the development server on port `8080`, you would combine the `astro dev` command with the `--port` flag: `astro dev --port 8080`. @@ -55,6 +186,8 @@ If you started your project using [the `create astro` wizard](/en/install/auto/# +*/} + ## `astro dev` Runs Astro's development server. This is a local HTTP server that doesn't bundle assets. It uses Hot Module Replacement (HMR) to update your browser as you save changes in your editor. diff --git a/src/content/docs/en/reference/directives-reference.mdx b/src/content/docs/en/reference/directives-reference.mdx index d5234f03f71bb..7a6ea4a0f0944 100644 --- a/src/content/docs/en/reference/directives-reference.mdx +++ b/src/content/docs/en/reference/directives-reference.mdx @@ -110,7 +110,7 @@ These directives control how [UI Framework components](/en/core-concepts/framewo By default, a UI Framework component is not hydrated in the client. If no `client:*` directive is provided, its HTML is rendered onto the page without JavaScript. -A client directive can only be used on a UI framework component that is directly imported into a `.astro` component. Hydration directives are not supported when using [dynamic tags](/en/core-concepts/astro-components/#dynamic-tags) and [custom components passed via the `components` prop](/en/guides/markdown-content/#custom-components-with-imported-mdx). +A client directive can only be used on a UI framework component that is directly imported into a `.astro` component. Hydration directives are not supported when using [dynamic tags](/en/core-concepts/astro-syntax/#dynamic-tags) and [custom components passed via the `components` prop](/en/guides/markdown-content/#custom-components-with-imported-mdx). ### `client:load` @@ -221,7 +221,7 @@ The `is:inline` directive is implied whenever any attribute other than `src` is ``` -📚 See how [client-side scripts](/en/core-concepts/astro-components/#client-side-scripts) work in Astro components. +📚 See how [client-side scripts](/en/guides/client-side-scripts/) work in Astro components. ### `define:vars` diff --git a/src/content/docs/en/reference/errors/static-client-address-not-available.mdx b/src/content/docs/en/reference/errors/static-client-address-not-available.mdx index 3956aa4141f41..816aef0175011 100644 --- a/src/content/docs/en/reference/errors/static-client-address-not-available.mdx +++ b/src/content/docs/en/reference/errors/static-client-address-not-available.mdx @@ -18,7 +18,7 @@ import DontEditWarning from '~/components/DontEditWarning.astro' ## What went wrong? The `Astro.clientAddress` property is only available when [Server-side rendering](/en/guides/server-side-rendering/) is enabled. -To get the user's IP address in static mode, different APIs such as [Ipify](https://www.ipify.org/) can be used in a [Client-side script](/en/core-concepts/astro-components/#client-side-scripts) or it may be possible to get the user's IP using a serverless function hosted on your hosting provider. +To get the user's IP address in static mode, different APIs such as [Ipify](https://www.ipify.org/) can be used in a [Client-side script](/en/guides/client-side-scripts/) or it may be possible to get the user's IP using a serverless function hosted on your hosting provider. **See Also:** - [Enabling SSR in Your Project](/en/guides/server-side-rendering/#enabling-ssr-in-your-project) diff --git a/src/content/docs/en/tutorial/2-pages/3.mdx b/src/content/docs/en/tutorial/2-pages/3.mdx index 04d9872d7fa40..b3765c1f3413c 100644 --- a/src/content/docs/en/tutorial/2-pages/3.mdx +++ b/src/content/docs/en/tutorial/2-pages/3.mdx @@ -276,4 +276,4 @@ For each Astro template expression, can you predict the HTML (if any!) that will ### Resources -- [Dynamic expressions in Astro](/en/core-concepts/astro-components/#jsx-like-expressions) +- [Dynamic expressions in Astro](/en/core-concepts/astro-syntax/#jsx-like-expressions) diff --git a/src/content/docs/en/tutorial/2-pages/4.mdx b/src/content/docs/en/tutorial/2-pages/4.mdx index 13c4e8497e93a..11d4d21478585 100644 --- a/src/content/docs/en/tutorial/2-pages/4.mdx +++ b/src/content/docs/en/tutorial/2-pages/4.mdx @@ -194,7 +194,7 @@ const textCase = "uppercase"; ### Resources -- [Astro syntax vs JSX - comparison](/en/core-concepts/astro-components/#differences-between-astro-and-jsx) +- [Astro syntax vs JSX - comparison](/en/core-concepts/astro-syntax/#differences-between-astro-and-jsx) - [Astro ` diff --git a/src/util.ts b/src/util.ts index 83bd081a26bbc..0b43ae52668ce 100644 --- a/src/util.ts +++ b/src/util.ts @@ -21,18 +21,3 @@ export const stripLangFromSlug = (slug: CollectionEntry<'docs'>['slug']) => /** Get a page’s lang tag from its slug (e.g. `'en/migrate'` => `'en'`). */ export const getLangFromSlug = (slug: CollectionEntry<'docs'>['slug']) => slug.split('/')[0]; - -/** Remove the subpage segment of a URL string */ -export function removeSubpageSegment(path: string) { - // Include new pages with subpages as part of this regex. - const regex = /(?:install|deploy|integrations-guide|tutorial|migrate-to-astro|cms)\//; - const matches = regex.exec(path); - - if (matches) { - const matchIndex = matches.index; - // Get the first slash index after the main page path segment. - const slashIndex = path.slice(matchIndex).indexOf('/') + matchIndex; - return path.slice(0, slashIndex); - } - return path; -} diff --git a/src/util/getPageCategory.ts b/src/util/getPageCategory.ts index 17ded868b6d61..d7c5af7815b49 100644 --- a/src/util/getPageCategory.ts +++ b/src/util/getPageCategory.ts @@ -16,7 +16,7 @@ const categories = [ * @param url URL for the current page. * @returns The category for the current page as used by Algolia DocSearch to group search results. */ -export function getPageCategory(url: URL) { +export function getPageCategory(url: { pathname: string }) { const langAgnosticPath = url.pathname.replace(/\/\w\w(-\w\w)?\//, ''); for (const [path, label] of categories) { if (langAgnosticPath.startsWith(path)) return label; diff --git a/src/util/isSubPage.ts b/src/util/isSubPage.ts new file mode 100644 index 0000000000000..9de1b984f019c --- /dev/null +++ b/src/util/isSubPage.ts @@ -0,0 +1,49 @@ +import type { CollectionEntry } from 'astro:content'; +import { englishPages } from '~/content'; +import { getPageCategory } from './getPageCategory'; + +/** Remove the sub-page segment of a URL string */ +export function removeSubPageSegment(path: string) { + // Include new pages with sub-pages as part of this regex. + const regex = /(?:install|deploy|integrations-guide|tutorial|migrate-to-astro|recipes|cms)\//; + const matches = regex.exec(path); + + if (matches) { + const matchIndex = matches.index; + // Get the first slash index after the main page path segment. + const slashIndex = path.slice(matchIndex).indexOf('/') + matchIndex; + return path.slice(0, slashIndex); + } + return path; +} + +const typeIndexes: Partial['data']['type'], string>> = { + recipe: 'recipes', +}; + +const categoryIndex: Partial, string>> = { + 'Error Reference': 'reference/error-reference', +}; + +/** + * Test if `currentPage` is considered a sub-page of `parentSlug`. + * @param currentPage The full slug for the current page, e.g. `'en/guides/rss'` + * @param parentSlug The language-less slug for the parent to test against e.g. `'guides/content-collections'` + */ +export function isSubPage(currentPage: string, parentSlug: string): boolean { + // Test 1: do the two pages share a base URL segment? + if (removeSubPageSegment(currentPage).endsWith(removeSubPageSegment(parentSlug))) { + return true; + } + // Test 2: is there a known parent page for this page category? + const category = getPageCategory({ pathname: '/' + currentPage + '/' }); + if (categoryIndex[category] === parentSlug) { + return true; + } + // Test 3: is there a known parent page for this page type? + const type = englishPages.find(({ slug }) => slug === currentPage)?.data.type; + if (type && typeIndexes[type] === parentSlug) { + return true; + } + return false; +}