diff --git a/docs/media/rich-text-bio.png b/docs/media/rich-text-bio.png index fd62d1be..d3441e5b 100644 Binary files a/docs/media/rich-text-bio.png and b/docs/media/rich-text-bio.png differ diff --git a/docs/media/rich-text-no-macro.png b/docs/media/rich-text-no-macro.png new file mode 100644 index 00000000..2e6afd37 Binary files /dev/null and b/docs/media/rich-text-no-macro.png differ diff --git a/docs/media/rich-text-result.png b/docs/media/rich-text-result.png new file mode 100644 index 00000000..be380bbf Binary files /dev/null and b/docs/media/rich-text-result.png differ diff --git a/docs/rich-text.adoc b/docs/rich-text.adoc index 0f8c8c47..5414fbdf 100644 --- a/docs/rich-text.adoc +++ b/docs/rich-text.adoc @@ -12,22 +12,23 @@ Enonic boasts a rich text field field type aka https://developer.enonic.com/docs == Sample content -Luckily, our sample data set contains everyting we need to get going. The Bio field for persons is rich text, and some of the content is even populated with relevant content, like you see below: +Luckily, our sample data set contains everyting we need to get going. +The `Bio` field for persons is rich text, and some of the content is even populated with relevant content, like you see below: -image::rich-text-bio.png[title="Lea Seydoux with rich text bio content",width=672px] +image::rich-text-bio.png[title="Rich text bio field for Lea Seydoux",width=662px] == Task: Rich text rendering -Let's take `bio` input field from `Person` https://developer.enonic.com/docs/content-studio/stable/content-types[content type] and see how to render it. +Let's take this `bio` field from `Person` https://developer.enonic.com/docs/content-studio/stable/content-types[content type] and see how we can render it. . *Update the Person query* + -To properly render Rich text, we need additional metadata about links and images. +To properly render Rich text, we need additional metadata about links, images and macros in it. + To simplify things, Next.XP provides a `richTextQuery` helper function that generates the query for us. Also, this way we don't have to repeat the query for every rich text field. + -*Add a new query* file to your project: +Add a new query file to your project with the following content: + .src/components/queries/getPersonWithBio.ts [source,Typescript] @@ -35,22 +36,23 @@ To simplify things, Next.XP provides a `richTextQuery` helper function that gene include::{sourcedir}/components/queries/getPersonWithBio.ts[] ---- + -NOTE: The difference is this line: `${richTextQuery('bio')}``, and that getPerson must now be a function. +IMPORTANT: Since we use `${richTextQuery('bio')}`` now, that is dependant on registered macros, `getPerson` query must now be a function! +This guarantees that it is executed when querying for data, and all the macros are already registered. + For further insight, visit the https://developer.enonic.com/docs/developer-101/stable/rich-text[rich text chapter] in the Developer 101 tutorial . *The RichTextView component* + -We also need an updated view component that will render the `bio`. +Now that we got the data, we need to use `RichTextView` component that knows how to render it. +All we need to do is pass `bio` data from the query response to the `RichTextView` component. + -NOTE: It reads the `bio` data from the query response, and passes it to the `RichTextView` component. +Create new component with the following content: + .src/components/views/PersonWithBio.tsx [source,tsx] ---- include::{sourcedir}/components/views/PersonWithBio.tsx[] ---- -+ . *Update the mapping* + Finally, we need to update the mappings to use the new query and view. @@ -58,48 +60,69 @@ Finally, we need to update the mappings to use the new query and view. Simply replace the `getPerson` query with `getPersonWithBio` and `Person` view with `PersonWithBio`. It should look something like this: + .src/components/_mappings.ts -[source,Typescript,linenums] +[source,Typescript] ---- +import getPersonWithBio from './queries/getPersonWithBio'; +import PersonWithBio from './views/PersonWithBio'; + ComponentRegistry.addContentType(`${APP_NAME}:person`, { - query: getPersonWithBio(), + query: getPersonWithBio, view: PersonWithBio }); ---- + -That's it! - -The result should look something like this: -TODO Screenshot - +. *That's it!* ++ +The resulting page for http://localhost:3000/persons/lea-seydoux[Lea Seydoux] should look like this: ++ +image::rich-text-no-macro.png[title="Person content rendered with rich text",width=1080px] == Macros -https://developer.enonic.com/docs/xp/stable/cms/macros[Macros] enable you to add custom components inside your rich text. In order to render in your front-end, every macro need to be to registered as a component. +https://developer.enonic.com/docs/xp/stable/cms/macros[Macros] enable you to add custom components inside your rich text. +As you can see from the image above, the content is rendered, but the `FactBox` macro is not. +In order to render in your front-end, every macro needs to be registered as a component.` -=== Filmography macro +=== FactBox macro -In your Enonic project, a `filmography` macro has been pre-defined. You can find it in `src/main/resources/site/macros/filmography/filmography.xml` +In your Enonic project, a `FactBox` macro has been pre-defined. +You can find it in `src/main/resources/site/macros/FactBox.xml` -If you have a closer look at the Léa Seydoux content once more, you should see the following text snippet at the end of the bio text: +If you have a closer look at the Léa Seydoux content once more, you should see the following text snippet in the bio field: - [filmography heading="Lea's Movies"/] +---- +[factbox] + French actress: Léa Seydoux is a prominent French actress known for her roles in both French and international films. + Cannes Film Festival winner: She won the prestigious Palme d'Or at the Cannes Film Festival in 2013 for her role in Blue Is the Warmest Colour. + James Bond franchise: Seydoux portrayed Dr. Madeleine Swann in the James Bond films Spectre (2015) and No Time to Die (2021). + Family ties to cinema: She comes from a family with strong ties to the French film industry; her grandfather, Jérôme Seydoux, is the chairman of Pathé, a major French film production company. + Diverse filmography: Her acting range spans from independent arthouse films like The Lobster (2015) to blockbuster hits like Mission: Impossible – Ghost Protocol (2011). +[/factbox] +---- -This means, an editor has added the `filmography` macro, with a single `heading` parameter to the rich text. +This means, an editor has added the `factbox` macro with a body. === Task: Render a macro -But there is no trace of it in the preview pane. -That is because we need to create a React component that will render it. +Let's create a React component that will render the `FactBox` macro. -. *Add Filmography component* +. *Add FactBox component* + -This React component will automatically be invoked by the `RichTextView` component, if it encounters the filmography macro tag. +This React component will automatically be invoked by the `RichTextView` component, if it encounters corresponding macro tag. + -.src/components/macros/FilmographyMacro.tsx +.src/components/macros/FactBox.tsx [source,jsx] ---- -include::{sourcedir}/components/macros/Filmography.tsx[] +include::{sourcedir}/components/macros/FactBox.tsx[] +---- ++ +To make it look nice, you also need to add a CSS module accompanying the component: ++ +.src/components/macros/FactBox.module.css +[source,css] +---- +include::{sourcedir}/components/macros/FactBox.module.css[] ---- + . *Register macro* @@ -110,10 +133,10 @@ In order for the `RichTextView` to be aware of your new macro, you must register [source,jsx] ---- ... -import Filmography from './macros/Filmography'; +import FactBox from './macros/FactBox'; ... -ComponentRegistry.addMacro(`${APP_NAME}:filmography`, { - view: Filmography, +ComponentRegistry.addMacro(`${APP_NAME}:factbox`, { + view: FactBox, configQuery: '{ heading }' }); ---- @@ -124,6 +147,8 @@ IMPORTANT: Macro must be registered before any component that uses it! Best prac + . *Congratulations!* + -You should now be able to see macro render `Lea's Movies` within the rich text: http://localhost:3000/persons/lea-seydoux[http://localhost:3000/persons/lea-seydoux]! +You should now see http://localhost:3000/persons/lea-seydoux[Lea Seydoux] updated with the `FactBox` macro: + +image::rich-text-result.png[title="Person content rendered with rich text and macro",width=1142px] In the next chapter we'll make it possible to <>. diff --git a/package-lock.json b/package-lock.json index a2df1ecb..722d61cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@enonic/nextjs-adapter": "^3.1.0-B3", + "@enonic/nextjs-adapter": "^3.1.0-B5", "cross-env": "^7.0.3", "html-react-parser": "^5.1.16", "next": "^14.1.4", @@ -44,16 +44,16 @@ "peer": true }, "node_modules/@enonic/nextjs-adapter": { - "version": "3.1.0-B3", - "resolved": "https://registry.npmjs.org/@enonic/nextjs-adapter/-/nextjs-adapter-3.1.0-B3.tgz", - "integrity": "sha512-OdhNfcNzVwTYBe48MpQogIQnnxNvlTvi9YU7JJ5rIpDwhBF+y+Fp/bQi5J/AFVYjUU7yUXaBwwlVMj/8NgO/vA==", + "version": "3.1.0-B5", + "resolved": "https://registry.npmjs.org/@enonic/nextjs-adapter/-/nextjs-adapter-3.1.0-B5.tgz", + "integrity": "sha512-G+pGj8BFp3JkxmyzXwrLz6sLZ/tpAfRaDhXhqHVsjKAilwvGA3m9th8LgaaIIkm/8owwVaZ8xaoR/2YC2jBDsQ==", "dependencies": { "@enonic/react-components": "^5.0.0-RC5", "@formatjs/intl-localematcher": "^0.5.4", "gqlmin": "^0.2.2", - "html-react-parser": "^5.1.16", + "html-react-parser": "^5.1.18", "negotiator": "^0.6.3", - "next": "^14.2.13", + "next": "^14.2.14", "react": "18.3.1", "react-dom": "18.3.1", "unescape": "^1.0.1" @@ -2736,14 +2736,14 @@ } }, "node_modules/html-react-parser": { - "version": "5.1.16", - "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-5.1.16.tgz", - "integrity": "sha512-OtVPEQRwa4eelyMbHmUfMSw5VwJsVGSVsfa8I+M8xuV87n91cF3PHpvT/z0Frf1uG34atqh3dxgjaGIsmqVsRA==", + "version": "5.1.18", + "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-5.1.18.tgz", + "integrity": "sha512-65BwC0zzrdeW96jB2FRr5f1ovBhRMpLPJNvwkY5kA8Ay5xdL9t/RH2/uUTM7p+cl5iM88i6dDk4LXtfMnRmaJQ==", "dependencies": { "domhandler": "5.0.3", "html-dom-parser": "5.0.10", "react-property": "2.0.2", - "style-to-js": "1.1.14" + "style-to-js": "1.1.16" }, "peerDependencies": { "@types/react": "0.14 || 15 || 16 || 17 || 18", @@ -2825,9 +2825,9 @@ "dev": true }, "node_modules/inline-style-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz", - "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==" }, "node_modules/internal-slot": { "version": "1.0.7", @@ -4489,19 +4489,19 @@ } }, "node_modules/style-to-js": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.14.tgz", - "integrity": "sha512-+FGNddHGLPY4NOPneEEdFj8dIy+oV4mHGrPZpB38P+YXrCAG9mp70dbcsAWnM8BFZULkJRvMqD0CXRjZLOYJFA==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", + "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", "dependencies": { - "style-to-object": "1.0.7" + "style-to-object": "1.0.8" } }, "node_modules/style-to-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.7.tgz", - "integrity": "sha512-uSjr59G5u6fbxUfKbb8GcqMGT3Xs9v5IbPkjb0S16GyOeBLAzSRK0CixBv5YrYvzO6TDLzIS6QCn78tkqWngPw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", "dependencies": { - "inline-style-parser": "0.2.3" + "inline-style-parser": "0.2.4" } }, "node_modules/styled-jsx": { diff --git a/package.json b/package.json index d4b3f3f4..244f2ac2 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "lint": "next lint" }, "dependencies": { - "@enonic/nextjs-adapter": "^3.1.0-B3", + "@enonic/nextjs-adapter": "^3.1.0-B5", "cross-env": "^7.0.3", "html-react-parser": "^5.1.16", "next": "^14.1.4", diff --git a/src/components/_mappings.ts b/src/components/_mappings.ts index 9f0987fe..68b0285a 100644 --- a/src/components/_mappings.ts +++ b/src/components/_mappings.ts @@ -1,4 +1,4 @@ -import {APP_NAME, APP_NAME_UNDERSCORED, ComponentRegistry, richTextQuery} from '@enonic/nextjs-adapter' +import {APP_NAME, ComponentRegistry} from '@enonic/nextjs-adapter' import TwoColumnLayout from './layouts/TwoColumnLayout'; import MainPage from './pages/Main'; import ChildList, {childListProcessor, getChildList} from './parts/ChildList'; @@ -15,28 +15,12 @@ import PersonWithBio from './views/PersonWithBio'; ComponentRegistry.setCommonQuery([commonQuery, commonVariables]); // Macro mappings (should come first as may be used in other components) -/* -ComponentRegistry.addMacro(`${APP_NAME}:filmography`, { - view: Filmography, - configQuery: `{ - heading - movies { - ... on ${APP_NAME_UNDERSCORED}_Movie { - displayName - _path(type: siteRelative) - } - } - }` -}); -*/ - ComponentRegistry.addMacro(`${APP_NAME}:factbox`, { view: FactBox, - configQuery: `{ - header - ${richTextQuery('body')} - }` -});/* + configQuery: `{ header }` +}); + +/* // Following macros come from com.enonic.app.panelmacros app that you need to install separately ComponentRegistry.addMacro(`com.enonic.app.panelmacros:panel`, macroPanelConfig); ComponentRegistry.addMacro(`com.enonic.app.panelmacros:info`, macroPanelConfig); @@ -57,7 +41,7 @@ ComponentRegistry.addMacro('com.enonic.app.socialmacros:youtube', { // Content type mappings ComponentRegistry.addContentType(`${APP_NAME}:person`, { - query: getPersonWithBio(), + query: getPersonWithBio, view: PersonWithBio }); diff --git a/src/components/macros/FactBox.module.css b/src/components/macros/FactBox.module.css index ff62a483..cb68c3ae 100644 --- a/src/components/macros/FactBox.module.css +++ b/src/components/macros/FactBox.module.css @@ -27,6 +27,7 @@ -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; + clear: both; -webkit-box-shadow: 0 2px 3px rgba(0, 0, 0, .3); -moz-box-shadow: 0 2px 3px rgba(0, 0, 0, .3); diff --git a/src/components/macros/FactBox.tsx b/src/components/macros/FactBox.tsx index e5011e77..23241c82 100644 --- a/src/components/macros/FactBox.tsx +++ b/src/components/macros/FactBox.tsx @@ -2,9 +2,8 @@ import type {MacroProps} from '@enonic/nextjs-adapter'; import React from 'react' import styles from './FactBox.module.css'; -import RichTextView from '@enonic/nextjs-adapter/views/RichTextView'; -const FactBox = ({name, config, meta}: MacroProps) => { +const FactBox = ({name, children, config, meta}: MacroProps) => { // macro is used inside a

tag so we can't use any dom tags const header = config.header.length ? config.header : 'Fact Box'; return <> @@ -12,7 +11,7 @@ const FactBox = ({name, config, meta}: MacroProps) => { {header} - + {children} }; diff --git a/src/components/macros/Filmography.tsx b/src/components/macros/Filmography.tsx deleted file mode 100644 index e4346386..00000000 --- a/src/components/macros/Filmography.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import {MacroProps} from '@enonic/nextjs-adapter'; -import React from 'react' -import Link from 'next/link'; - -const FilmographyMacro = ({name, config, meta}: MacroProps) => { - // macro is used inside a

tag so we can't use any block tags - const prefix = meta.baseUrl + - (meta.locale && meta.locale !== meta.defaultLocale ? meta.locale + '/' : ''); - const heading = config.heading || 'Filmography'; - - return config.movies.length ? - <> -

{heading}

- - : null -}; - -export default FilmographyMacro; - diff --git a/src/components/views/PersonWithBio.module.css b/src/components/views/PersonWithBio.module.css index cc21b229..4fae4081 100644 --- a/src/components/views/PersonWithBio.module.css +++ b/src/components/views/PersonWithBio.module.css @@ -16,6 +16,11 @@ font-size: 12px; } +.bio figure > img { + width: 100%; + height: auto; +} + .photos { padding: 0 20px; display: grid;