This section describes a Forge preview feature. Preview features are deemed stable; however, they remain under active development and may be subject to shorter deprecation windows. Preview features are suitable for early adopters in production environments.
We release preview features so partners and developers can study, test, and integrate them prior to General Availability (GA). For more information, see Forge release phases: EAP, Preview, and GA.
This tutorial demonstrates how you can use rich-text bodied macros in Forge. It shows how to configure your manifest and render rich body content, using the ADF Renderer React component, or the Confluence convert content body APIs.
You can add simple configuration to a macro using UI Kit components, as described here.
You can add custom configuration to a macro, as described here.
This tutorial assumes you're already familiar with the basics of Forge development. If this is your first time using Forge, see Getting started first.
To complete this tutorial, you need the latest version of Forge CLI. To update your CLI version, run npm install -g @forge/cli@latest
on the command line.
An Atlassian cloud developer site lets you install and test your app on Confluence and Jira products set up for you. If you don't have one yet, set it up now:
You can install your app to multiple Atlassian sites. However, app data won't be shared between separate Atlassian sites, products, or Forge environments.
The limits on the numbers of users you can create are as follows:
The Atlassian Marketplace doesn't currently support cross-product apps. If your app supports multiple products, you can publish two separate listings on the Marketplace, but your app won't be able to make API calls across different products and instances/installations.
To set your macro as a bodied macro, navigate to the app's manifest.yml
file and add the line layout: bodied
in the macro module properties.
1 2macro: - key: my-macro ... layout: bodied
The macro body is the content that the user enters in the editor. This can be retrieved from the context provided by useProductContext() or view.getContext().
In a UI Kit app, we can use useProductContext()
to extract the macro body.
1 2import React from 'react'; import ForgeReconciler, { useProductContext } from '@forge/react'; const App = () => { const context = useProductContext(); const macroBody = context?.extension?.macro?.body; // ... } ForgeReconciler.render( <React.StrictMode> <App /> </React.StrictMode> );
The ADF body can be rendered in two ways; using the renderer components, or by rendering the raw HTML.
The following content types are supported by the HTML export, but not supported by the ADF renderer component:
adfExport
functionThe following content types are not supported at all by the HTML export or the ADF renderer component:
adfExport
functionSee detailed documentation for the AdfRenderer.
For UI Kit, you can use the AdfRenderer
component, while for custom UI, you can use the ReactRenderer
component.
Both the AdfRenderer
and ReactRenderer
component require the prop document
, which is an ADF document with the following structure:
1 2"body": { "type": "doc", "version": 1, "content": [ // ADF content ] }
We can pass the macro body that we extracted previously into the document
prop of AdfRenderer
and ReactRenderer
components for UI Kit and custom UI respectively.
To render in UI Kit, you can use the AdfRenderer
component from @forge/react.
1 2import { AdfRenderer } from "@forge/react"; const App = () => { // ... return macroBody && <AdfRenderer document={macroBody} /> }
Alternatively, you can use requestConfluence() to make a request to the Confluence Cloud platform REST API to convert the macro body into HTML. We can render this HTML directly in our Forge app.
The following code imports requestConfluence
, and extracts contentId
and macroBody
from the context
obtained in
step 2.
1 2import { requestConfluence } from "@forge/bridge"; const App = () => { const context = useProductContext(); const macroBody = context?.extension?.macro?.body; const contentId = context?.extension?.content?.id; ... }
Next, we can construct the async API call for the macro body in HTML form using the Asynchronously convert content body call.
1 2async function convertMacroBody(to, macroBody, contentId) { const response = await requestConfluence( `/wiki/rest/api/contentbody/convert/${to}?contentIdContext=${contentId}`, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ value: macroBody, representation: "atlas_doc_format", }), } ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return (await response.json()).asyncId; }; const asyncId = await convertMacroBody( "styled_view", macroBody, contentId );
Use "atlas_doc_format"
for the representation
parameter in body
. This indicates that the macro body is in ADF format.
Use "styled_view"
for the to
parameter. This will convert the macro body to an HTML document with embedded styles.
We can obtain the HTML body from the id
that is returned from convertMacroBody()
using the
Get asynchronously converted content body from the id or the current status of the task call.
1 2async function fetchConvertedMacroBody(id) { const response = await requestConfluence( `/wiki/rest/api/contentbody/convert/async/${id}`, { headers: { 'Accept': 'application/json' } } ); const { status, value, error } = response.json(); if (status === "FAILED") { throw new Error(`Conversion failed: ${error}`) } else if (status === "COMPLETED") { return value; } // Keep polling until completed return fetchConvertedMacroBody(id); }; const htmlBody = await fetchConvertedMacroBody(asyncId);
Finally, we can directly render the HTML.
For UI Kit, we can render the HTML in a Frame
component. Please see
Frame for more details.
First define the Frame
in the resources
section of the manifest.yml
file. Please see an example
manifest structure here.
1 2resources: ... - key: html-frame path: resources/html-frame/build
Next, create an index.js
and index.html
file in the path specified in the manifest resources.
See here for more information on setting up resources.
The onPropsUpdate
function will be called each time the parent component provides HTML, which we will then render directly inside our Frame
.
1 2import { view } from "@forge/bridge"; events.on("PROPS", ({ html }) => { document.documentElement.innerHTML = html; });
This frame can now be used in your UI Kit app to render the HTML body. This is your src/frontend/index.jsx
file.
The following code uses createFrame.
1 2import { Frame } from "@forge/react" const App = () => { useEffect(() => { events.emit("PROPS", { html: htmlBody }); }, [htmlBody]); return <Frame resource="html-frame" /> }
adfExport
functionWhen the page is exported to PDF, Word, or viewed in the page history, you can specify how the app should be displayed.
This is done by specifying an adfExport
function, and referencing it in your app's manifest.yml
file.
When there is no export function defined, by default, the macro body will be returned.
Accessing the rich text body is done via the extensionPayload.macro.body
property.
If you would like to customise the export:
Please see the full adfExport
tutorial here.
1 2import { doc, p } from '@atlaskit/adf-utils/builders'; export function adfExport(payload) { const macroBody = payload.extensionPayload.macro.body; return doc( p("This is my export function. Here's the macro content:"), ...macroBody.content ) }
Rate this page: