Topics covered: Confluence Rest API, Confluence Storage Format, User Parameters
Intro
The Problem
Uploading the image to 100 pages… Huh, tough job, but the task is done!
Designers changed the logo which used on 100 pages 🤯
Or imagine you have a dozen UI screenshots all around the documentation, and the product design has been changed. How to update it in all places at once?
Who am I?
Hi, my name is Roma. I’m the founder of Wombats Corp.
I will help you to solve some REAL problems in Confluence.
Request
Christia K_ has asked on the community.atlassian.com
how to link to often used images without uploading them on every single page?
We have a lot of pages with the same image (logo of a customer, screenshots etc.)
How do I incorporate them on my < 100 pages without uploading them on every single page?
When changing the logo, I could change it on 1 single instance on the server version / old editor without uploading it again on < 100 pages?
The Real Problem!
Let’s take a look at replays:
Marc from Devoteam suggested using page templates, but it does not make work easier.
Barbara Szczesniak suggested Excerpt Include macros for each image. It might work partially. But not for all use cases. Also, she mentioned an open suggestion for years CONFCLOUD-65344
Maybe User Macro can deal with it?
Logo Library macro
Let’s do the macro for multiple logos of our partners.
Create the simplest image macro
It will be a static, inline macro, without a body.
So, in the admin panel new User Macro was created, and an additional Confluence page will be needed to test the result.
To start, let's insert an image. We'll do this by using a macro, which gives us more control than a direct upload.
The simplest version. With an image from seeklogo.com.
As we create a static macro, we have to stick to the Confluence Storage Format.
The template for rendering an image:
<ac:image> <ri:url ri:value="https://images.seeklogo.com/logo-png/18/1/sap-logo-png_seeklogo-180805.png"/> </ac:image>
Open a test page, full reload (Ctrl/Cmd + R) to pick up the newly created macro, and add /logo macro to the page.
Adding User Parameter
It works, but the image is too big. Adding a parametrized size will help in future macro usage.
Image sizing is done via ac:height and ac:width attributes. However, as we talk about the logo, we will use only one parameter, and another will be adopted proportionally. It will simplify sizing and does not distort proportions.
Click on the + Add user parameter under the Variables block.
It will be a Size parameter with type of Int:
The parameter is not required, and if the user skips it logo will be in its original size.
Updated template:
<ac:image ac:height="$parameters['size']"> <ri:url ri:value="https://images.seeklogo.com/logo-png/18/1/sap-logo-png_seeklogo-180805.png"/> </ac:image>
Now it’s better:
Adding a list of images
Kind of working, however, there is a list of partners, and macro should support each.
The list of partners is enumeration, and below is a User Parameter for it.
It must be required, as there is no logical default option.
For mapping partner name with logo URL map from Apache Velocity is used.
It looks more elegant than having a bunch of ifelses.
Variable $images is a map with key-value pairs.
Variable $image is an actual URL that we need.
Now the image URL is parameterized instead of hardcoded.
As a bonus ac:alt and ac:title attributes will be used to follow best practices of image usage.
#set ($images = {
"SAP": "https://images.seeklogo.com/logo-png/18/1/sap-logo-png_seeklogo-180805.png",
"McDonalds": "https://images.seeklogo.com/logo-png/30/1/mcdonalds-logo-png_seeklogo-301644.png",
"NASA": "https://images.seeklogo.com/logo-png/9/1/nasa-logo-png_seeklogo-97034.png",
"Lego": "https://images.seeklogo.com/logo-png/8/1/lego-logo-png_seeklogo-83157.png",
"Volkswagen": "https://images.seeklogo.com/logo-png/15/1/volkswagen-logo-png_seeklogo-150537.png",
"AMD": "https://images.seeklogo.com/logo-png/0/1/amd-logo-png_seeklogo-7775.png?v=1954738441079066064"
})
#set ($image = $images.get($parameters["partner"]))
<ac:image ac:height="$parameters['size']"
ac:alt="$parameters['partner'] logo"
ac:title="$parameters['partner'] logo">
<ri:url ri:value="$image"/>
</ac:image>
With an output type of inline, multiple logos can be lined up one by one, as shown in the screenshot below:
The end (?)
Seems that the macro has been completed and uploaded to our macro examples.
However, it appears that Christia K_ wants a bit more:
…for a Technical User Manual you need a lot of screenshots of your software, some are used multiple times such as menubar, accept button etc. But others are only used once.
So it is very helpful to have the linked pictured because you only update your screenshot once and it will be updated on all different places, without the need to search them all.
Not a problem! Let’s do this one also 😎
Image from Attachments
Required changes
For this one, we have to make some changes.
The map of URLs is no longer needed.
Instead of the Partner parameter, Attachment is needed.
It will be a type of String. As the Attachment type is limited by the current space and is less universal.
To make the code cleaner searchName is assigned to the name variable:
#set ( $name = $parameters["searchName"] )
Requesting Confluence
Now that we have an attachment name, the next step is to get attachment’s URL programmatically.Get Attachments endpoint will do the job:
limit=1— will bring only one filesort=-modified-date— will bring the last updated attachment to the topstatus=current— it will filter out archived/deleted/drafted attachmentsfilename=$name— file name from User Parameter
#set ( $results = $ConfluenceManager.get("/wiki/api/v2/attachments?limit=1&sort=-modified-date&status=current&filename=$name").results)
{
"results": [
{
"id": "<string>",
"status": "<current | draft | archived | historical | trashed | deleted | any>",
"title": "<string>",
"createdAt": "<string>",
"pageId": "<string>",
"downloadLink": "<string>",
...
}
],
"_links": {
"next": "<string>",
"base": "<string>"
}
}
Object structure has a root element results. That’s why, after getting attachments first action is to access the results, which will go to the $results variable.
As the request already has limit=1 parameter, $results will have only one element with index 0.
And we are looking for the downloadLink attribute. downloadLink is a relative link, to make it valid, prefix it with $baseUrl.
Working macro
Let’s put everything together, and here is the template:
#set ( $name = $parameters["searchName"] )
## Make the search
#set ( $results = $ConfluenceManager.get("/wiki/api/v2/attachments?limit=1&sort=-modified-date&status=current&filename=$name").results)
## Add an image in Storage Format
#set ( $url = "$baseUrl$results[0].downloadLink" )
<ac:image
ac:alt="$name"
ac:title="$name"
ac:height="$parameters['size']">
<ri:url ri:value="$url"/>
</ac:image>
It works 🔥
Updating the attachment with painting.png name will update the image on this page automatically.
Final improvements
Based on user input, two edge cases are possible:
It’s not an image.
Search result is empty as there is no attachment with the given name.
To improve user experience, warning blocks have been added for edge cases.
It’s notable that only inline CSS styles are applicable to the Storage Format:
## Check for incompitable filetype
#set ( $name = $parameters["searchName"] )
#if(!$StringUtils.endsWithAny($name.toLowerCase(), ".png", ".jpg", "jpeg", ".gif" ))
<div style="background-color: #fef7c8; padding: 10px; border-radius: 3px;">
<div style="display: inline-block; width: 95%;">
<div style="margin: 1px 0 5px 0;">
<ac:emoticon ac:name="warning" ac:width="30px" /> Image [$name] must be PNG, JPG, or GIF format. Update the Attachment parameter
</div>
</div>
</div>
#stop
#end
## Make the search
#set ( $results = $ConfluenceManager.get("/wiki/api/v2/attachments?limit=1&sort=-modified-date&status=current&filename=$name").results)
## Show a warning if nothing found
#if(!$results)
<div style="background-color: #fef7c8; padding: 10px; border-radius: 3px;">
<div style="display: inline-block; width: 95%;">
<div style="margin: 1px 0 5px 0;">
<ac:emoticon ac:name="warning" ac:width="30px" /> No [$name] attachments has been found (Hint: <i>search is case-sensitive</i>)
</div>
</div>
</div>
#stop
#end
## Add an image in Storage Format
#set ( $url = "$baseUrl$results[0].downloadLink" )
<ac:image
ac:alt="$name"
ac:title="$name"
ac:height="$parameters['size']">
<ri:url ri:value="$url"/>
</ac:image>
Now it is even better and uploaded to the Library: https://wombatscorp.com/user-macros-examples/image-from-attachments-confluence-macro
The REAL problem has been solved with User Macro for Confluence Cloud
