- …
Header
Content
{% endblock %} +``` + +```jsx +Header
} + main={Content
} +/> +``` + +`` and `{__t('project:articleBlurb')}
; +} +``` + +## Filter substitutions + +| Filter | JSX equivalent | +| --- | --- | +| `build` | `apos.url.build(...)` | +| `escape` | Unnecessary — JSX auto-escapes | +| `safe` | `dangerouslySetInnerHTML={{ __html: value }}` | +| `nlbr` | `value.split('\n').map((line, i) => <>{i > 0 &&}{line}>)` | +| `date` | Import `dayjs` — **verify format tokens and locale**, they differ | +| `striptags` | No safe one-liner; a regex mishandles attributes containing `>` and comments. Prefer stripping server-side in a module method | +| `clonePermanent`, `css`, `json`, `jsonAttribute`, `merge`, `nlp`, `query` | No documented equivalent — leave marked as TODO rather than inventing one | +```` + +## What this will not do for you + +The instructions make an assistant substantially more reliable on Apostrophe-specific mechanics. They do not make it correct. + +Expect to review carefully around: anything touching template filters or `super()`, where no clean equivalent exists and the assistant is guessing; macro conversions, where the whole importer set has to move together; and attribute-level details, which are exactly what silent failures look like. + +If your project is large, convert one module, review it thoroughly, and only then continue. The first module tells you how much you can trust the rest. diff --git a/docs/guide/jsx-templates.md b/docs/guide/jsx-templates.md index 56e8c4a6..74edfb7c 100644 --- a/docs/guide/jsx-templates.md +++ b/docs/guide/jsx-templates.md @@ -1,6 +1,6 @@ # JSX templates -Apostrophe page, widget, and component templates can be written in **JSX** as an alternative to [Nunjucks](/guide/templating.md). For developers already comfortable with React or another JSX-aware framework, this means modern editor support, real JavaScript control flow, and accurate error reporting with source maps, without standing up a separate front-end project. +Apostrophe page, widget, and component templates can be written in **JSX** as an alternative to [Nunjucks](/guide/nunjucks-templates.md). For developers already comfortable with React or another JSX-aware framework, this means modern editor support, real JavaScript control flow, and accurate error reporting with source maps, without standing up a separate front-end project. ::: info This guide assumes you have written JSX before. It focuses on the Apostrophe-specific equivalents of Nunjucks features rather than on JSX itself. @@ -10,6 +10,10 @@ JSX templates are a server-side rendering option. They do **not** imply React: t JSX interoperates with Nunjucks in one direction: a `.jsx` template can extend or include a `.html` template (with block overrides where appropriate), but a `.html` template cannot extend or include a `.jsx` template. In practice this means you migrate a project from the leaves up, converting individual page and widget templates to JSX while keeping `layout.html` and the core Nunjucks templates in place. See [Migration order](#migration-order) for the rules. +::: tip Converting an existing project? +[Migrating templates with an AI assistant](/guide/jsx-migration-assistant.md) provides instructions you can copy into your project's `CLAUDE.md` or `AGENTS.md`, so a coding assistant follows the rules on this page instead of guessing at them. +::: + ::: info Right now, the easiest way to get a peek at a working project with JSX templates is: ```bash @@ -18,6 +22,7 @@ cd public-demo-jsx git checkout jsx npm install npm run dev +``` ::: ## File location and naming @@ -160,7 +165,7 @@ React-flavored attributes like `key` and `ref` are accepted but ignored. They ex
+
+ {!user && Login}
+ -
- {# List of locales, looping over data.localizations #}
- {% for localization in data.localizations %}
-
- - {# - Linking the locale name only when it exists in the locale and it's - not the current locale - #} - {% if localization._url and not localization.current %} - - {% endif %} - {# Using both the label and the locale code #} - {{ localization.label }} ({{ localization.locale }}) - {% if localization._url and not localization.current %} - - {% endif %} - - {% endfor %} -
-
+ {/* List of locales, looping over localizations */}
+ {localizations.map((localization) => {
+ /* Using both the label and the locale code */
+ const label = `${localization.label} (${localization.locale})`;
+
+ return (
+
- + {/* + Link the locale name only when it exists in that locale and is + not the current locale + */} + {localization._url && !localization.current ? ( + + {label} + + ) : ( + label + )} + + ); + })} +
Related articles
{# 👈 We need to localize this. #} --
- {% for post in data.piece._related %}
-
- {{ post.title }} - {% endfor %} -
Related articles
{/* 👈 We need to localize this. */} +-
+ {piece._related.map((post) => (
+
- {post.title} + ))} +
{{ __t('Related articles') }}
{# 🎉 It's localized! #} --
- {% for post in data.piece._related %}
-
- {{ post.title }} - {% endfor %} -
{__t('Related articles')}
{/* 🎉 It's localized! */} +-
+ {piece._related.map((post) => (
+
- {post.title} + ))} +
{{ __t('relatedArticles') }}
+```jsx +{__t('relatedArticles')}
``` This method is better if your team prefers to maintain all hard-coded strings in the same way across locales (treating the default locale the same as others). Using the original text as the key, as in our previous example, might be better so that translators can see the original text alongside their translations in the JSON files. It mostly depends how you prefer to work. The important thing is to be consistent. @@ -110,13 +118,13 @@ Template example:__t('ourTeam:relatedArticles')
+```jsx +{__t('ourTeam:relatedArticles')}
``` Apostrophe will then treat keys with the namespace differently from the same key without the namespace (`ourTeam:relatedArticles` vs. `relatedArticles`). If someone uses the version *without* the namespace it will not overwrite the version *with* the namespace. @@ -237,7 +245,7 @@ Avoid using namespaces that begin with `apos`. The core team uses namespaces tha As a reminder, namespacing is primarily necessary for *installable modules* and not for project-level localization. ::: -## Localizing the Apostrophe user interface +## Localizing the ApostropheCMS user interface ### Core UI localization diff --git a/docs/guide/logging.md b/docs/guide/logging.md index 7c98b09b..ed87a873 100644 --- a/docs/guide/logging.md +++ b/docs/guide/logging.md @@ -1,4 +1,4 @@ -# Logging in Apostrophe +# Logging in ApostropheCMS Logging is a fundamental aspect of any web application, especially those that require user authentication. It serves as a vital tool for monitoring system behavior, troubleshooting issues, and maintaining security. By recording various events and transactions within the system, logs provide insights into user interactions, system performance, and potential errors. diff --git a/docs/guide/media.md b/docs/guide/media.md index a47d34cd..87152568 100644 --- a/docs/guide/media.md +++ b/docs/guide/media.md @@ -42,15 +42,15 @@ module.exports = { ### Using an image widget in templates -If presenting an image using the image widget and its template, the process is not different from rendering any other area in a template. Use the `area` template tag, including the context reference and the area field name. +If presenting an image using the image widget and its template, the process is not different from rendering any other area in a template. Use the `Area` component, including the context reference and the area field name.{{ data.page.title }}
+``` + +Values are HTML-escaped by default. To render trusted markup unescaped, use the `safe` filter: + +``` nunjucks +{{ data.widget.content | safe }} +``` + +## Core syntax + +Nunjucks statements use `{% %}`, output uses `{{ }}`, and comments use `{# #}`. + +``` nunjucks +{# A comment — not rendered #} + +{% if data.user %} + Log out +{% else %} + Log in +{% endif %} + +{% for product in data.products %} +{{ data.page.headline | replace("foo", "bar") | upper }}
+``` + +Nunjucks supplies a set of [built-in filters](https://mozilla.github.io/nunjucks/templating.html#filters), and Apostrophe adds its own — `build`, `date`, `nlbr`, `striptags`, `json`, and others. You can register project-specific filters with `self.apos.template.addFilter()`. + +See [template filters](/guide/template-filters.md) for the full reference and for how to write your own. + +::: info +Filters are a Nunjucks feature. They are not available in JSX templates, which use ordinary JavaScript function calls instead. If you are planning a migration, note that project-specific filters registered with `addFilter()` are most portable if the underlying function is also exposed as a helper via `addHelpers()`. +::: + +## How templates work together + +No template is an island. Templates compose through the `extends`, `include`, and [`import`](/guide/fragments.md) tags. + +### Extending templates + +`{% extends %}` inherits all the markup and blocks of the template it extends. Blocks defined in the extending template replace matching blocks in the extended one. + +A layout template is typically structured like this: + +``` nunjucks +{# views/layout.html #} +{% extends data.outerLayout %} + +{% block beforeMain %} + {# Page header markup and the main content area opening tag... #} +{% endblock %} + +{% block main %}{% endblock %} + +{% block afterMain %} + {# The main content area closing tag and page footer... #} +{% endblock %} +``` + +Page type templates extend that layout: + +{{ data.piece.title or data.page.title }}
+{% endblock main %} +``` + +a home page template can extend the layout and keep that `h1`: + + above #}
+
+ {# ... additional home page content #}
+
+{% endblock %}
+```
+
+modules/@apostrophecms/home-page/views/page.html
+
+
Contact info
+{% endblock %} +``` + +That tells Apostrophe to use the `page.html` file belonging to the `default-page` module. The same pattern works with `include`. + +## Working alongside JSX + +The two template languages interoperate in **one direction**: + +> A `.jsx` template can extend, include, or import a `.html` template. A `.html` template **cannot** extend, include, or import a `.jsx` template. + +Nunjucks has no way to invoke the JSX renderer. In practice this means a project migrates from the leaves up: individual page and widget templates become JSX while `layout.html` and the core Nunjucks templates stay in place. Apostrophe's core `outerLayoutBase.html` remains Nunjucks for the foreseeable future. + +If you are not migrating, none of this affects you — a wholly Nunjucks project continues to work exactly as before. + +## Further reading + +- [Template tag reference](/reference/template-tags.md) — every Apostrophe tag in detail +- [Template filters](/guide/template-filters.md) — built-in and custom filters +- [Fragments](/guide/fragments.md) — Apostrophe's async-capable alternative to macros +- [Layout template](/guide/layout-template.md) — the root layout and `data.outerLayout` +- [Nunjucks documentation](https://mozilla.github.io/nunjucks/templating.html) — the language itself +- [JSX templates](/guide/jsx-templates.md) — the alternative, and what migration involves diff --git a/docs/guide/pages.md b/docs/guide/pages.md index a1a683ab..8478ff27 100644 --- a/docs/guide/pages.md +++ b/docs/guide/pages.md @@ -82,35 +82,68 @@ We can add functionality to the default home page type by adding a configuration Each page type requires a template. The only exception to that rule is if a page type extends another page type that already has a template. -Page templates are added in a `views` directory for the page type as `page.html`. The template for the previous example's default page would be `modules/default-page/views/page.html`. A very simple page template for the Default page might look like this: +Page templates are added in a `views` directory for the page type as `page.jsx`. The template for the previous example's default page would be `modules/default-page/views/page.jsx`. A very simple page template for the Default page might look like this: -``` nunjucks -{# modules/default-page/views/page.html #} -{% extends "layout.html" %} - -{% block main %} -{{ data.page.title }}
- {% if data.page.subtitle %} -{{ data.page.subtitle }}
- {% endif %} -{page.title}
+ {page.subtitle &&{page.subtitle}
} +{{ data.page.subtitle }}
-{% endif %} +```jsx +{page.subtitle &&{page.subtitle}
} ``` -Nunjucks offers additional tags, including the [`{% if %}` conditional tag](https://mozilla.github.io/nunjucks/templating.html#if), to help work with data in templates. +Conditionals are ordinary JavaScript. `&&` renders the right-hand side only when the left is truthy, and a ternary covers if/else. See [Conditionals](/guide/jsx-templates.md#conditionals) for the full set of idioms. ::: tip If you want to know what is available in a template object, you can log it in your terminal using the template method `apos.log()`. This looks like: -``` nunjucks -{{ apos.log(data.page) }} +```jsx +{apos.log(page)} ``` ::: -### The widget area is added using the `area` tag +### The widget area is added using the `Area` component -``` nunjucks -{% area data.page, 'main' %} +```jsx + ``` -This is a special tag in Apostrophe used to let editors add and manage content widgets to the page. After the `area` tag name, we pass the tag the field's context, which is our page, followed by the field name. We [configured it in the `index.js` file](#creating-a-page-type) to use two widget types. While editing the page, the user will have access to a menu to add widgets of those types. +`Area` is one of the helpers on the second argument of the template function. It lets editors add and manage content widgets on the page. Pass the field's context as `doc`, which is our page, and the field name as `name`. We [configured it in the `index.js` file](#creating-a-page-type) to use two widget types. While editing the page, the user will have access to a menu to add widgets of those types.  @@ -235,16 +267,44 @@ Pages can be organized into a page tree hierarchy while adding them or through t Apostrophe templates have data available to add navigation based on the page tree. This includes: -| Data object | What is it? | -| ------ | ------ | -| `data.home` | Home page data. It is similar to the data on `data.page`, but always references the home page. | -| `data.home._children` | Page data for pages one level below the home page in the page tree. | -| `data.page._ancestors` | Page data for the ancestors of the active page, starting with the home page. | -| `data.page._children` | Page data for pages one level *below* the active page. | - -By default, one level of children are available on each ancestor, as well as on the home page and `data.page`. +| Data property | Nunjucks | What is it? | +| ------ | ------ | ------ | +| `home` | `data.home` | Home page data. It is similar to the data on `page`, but always references the home page. | +| `home._children` | `data.home._children` | Page data for pages one level below the home page in the page tree. | +| `page._ancestors` | `data.page._ancestors` | Page data for the ancestors of the active page, starting with the home page. | +| `page._children` | `data.page._children` | Page data for pages one level *below* the active page. | + +By default, one level of children are available on each ancestor, as well as on the home page and `page`. + +With that available data, we could construct navigation for the website header. In JSX this is a `.map()` over the children — and `home` arrives the same way `page` does, as a property of the data object passed to every template, layout included: + +```jsx +export default function({ home, main }, { Extend }) { + return ( +